Java 表达式注入是一个总称,指用户输入进入 OGNL、SpEL、JEXL、MVEL、AviatorScript、Groovy 等表达式或脚本引擎后,被当成可执行表达式解释,从而访问对象、调用方法,甚至实现远程代码执行。
- SpEL
- OGNL
- JEXL
- MVEL
- AviatorScript
- Groovy 表达式 / 脚本
表达式注入不一定一开始就能直接执行系统命令,但它通常是通往 RCE 的前置入口。
只要表达式能力足够强,或者上下文对象暴露过多,攻击者就可能:
- 访问应用对象
- 调用类和方法
- 执行脚本
- 触发命令执行
- 把用户输入直接传给表达式引擎
- 在规则引擎、权限判断、模板渲染、动态计算中使用可控表达式
- 后台支持“自定义规则”“自定义公式”“自定义模板”
- 误以为只允许数学表达式,但实际暴露了完整对象上下文
- 读取配置和运行时对象
- 访问 Bean、Context、Request、Session
- 调用危险类方法
- 命令执行
- 作为进入框架内部对象图的跳板
不同引擎语法不同,利用方式差异很大。
排查重点:
- 依赖包
- 报错信息
- 方法名
- 模板和表达式语法特征
关键在于:
- 用户能否控制表达式本身
- 用户能否影响表达式上下文
如果能访问:
- Request
- Session
- Spring Context
- BeanFactory
- ClassLoader
风险会明显提高。
- 规则引擎
- 流程引擎
- 权限表达式
- 搜索过滤器
- 报表公式
- 低代码平台
- 模板预览和自定义页面
- 关键字黑名单绕过
- 字符串拼接绕过
- 借助上下文对象间接访问危险类
- 利用引擎特有语法规避过滤
这是根本原则。
若业务必须支持表达式,应限制为固定字段、固定运算、固定函数。
不要把应用运行时对象直接暴露给表达式环境。
表达式注入绕过空间通常很大,黑名单容易失效。
- 先识别具体引擎,不要把所有表达式问题混在一起
- 先找表达式入口,再看上下文对象
- 看是否能访问类、方法、Bean、运行时对象
- 高优先排查规则引擎、低代码平台、后台动态配置功能
- 一旦可控表达式成立,就继续判断能否升级为 RCE