Struts2漏洞利用原理及OGNL机制

Struts2漏洞利用原理及OGNL机制研究java

 

概述

在MVC开发框架中,数据会在MVC各个模块中进行流转。而这种流转,也就会面临一些困境,就是因为数据在不一样MVC层次中表现出不一样的形式和状态而形成的:正则表达式

View层—表现为字符串展现

数据在页面上是一个扁平的、不带数据类型的字符串,不管数据结构有多复杂,数据类型有多丰富,到了展现的时候,全都一视同仁的成为字符串在页面上展示出来。数据在传递时,任何数据都都被看成字符串或字符串数组来进行。数据库

Controller层—表现为java对象

在控制层,数据模型遵循java的语法和数据结构,全部的数据载体在Java世界中能够表现为丰富的数据结构和数据类型,你能够自行定义你喜欢的类,在类与类之间进行继承、嵌套。咱们一般会把这种模型称之为复杂的对象树。数据在传递时,将以对象的形式进行。express

能够看到,数据在不一样的MVC层次上,扮演的角色和表现形式不一样,这是因为HTTP协议与java的面向对象性之间的不匹配形成的。若是数据在页面和Java世界中互相传递,就会显得不匹配。因此也就引出了几个须要解决的问题:数组

1.当数据从View层传递到Controller层时,咱们应该保证一个扁平而分散在各处的数据集合能以必定的规则设置到Java世界中的对象树中去。同时,可以灵活的进行由字符串类型到Java中各个类型的转化。安全

2.当数据从Controller层传递到View层时,咱们应该保证在View层可以以某些简易的规则对对象树进行访问。同时,在必定程度上控制对象树中的数据的显示格式。数据结构

咱们稍微深刻思考这个问题就会发现,解决数据因为表现形式的不一样而发生流转不匹配的问题对咱们来讲其实并不陌生。一样的问题会发生在Java世界与数据库世界中,面对这种对象与关系模型的不匹配,咱们采用的解决方法是使用如hibernate,iBatis等框架来处理java对象与关系数据库的匹配。框架

如今在Web层一样也发生了不匹配,因此咱们也须要使用一些工具来帮助咱们解决问题。为了解决数据从View层传递到Controller层时的不匹配性,Struts2采纳了XWork的一套完美方案。而且在此的基础上,构建了一个完美的机制,从而比较完美的解决了数据流转中的不匹配性。就是表达式引擎OGNL。它的做用就是帮助咱们完成这种规则化的表达式与java对象的互相转化(以上内容参考自Struts2 技术内幕)。dom

关于OGNL

OGNL(Object Graph Navigation Language)即对象图形导航语言,是一个开源的表达式引擎。使用OGNL,你能够经过某种表达式语法,存取Java对象树中的任意属性、调用Java对象树的方法、同时可以自动实现必要的类型转化。若是咱们把表达式看作是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。咱们能够轻松解决在数据流转过程当中所遇到的各类问题。
OGNL进行对象存取操做的API在Ognl.java文件中,分别是getValue、setValue两个方法。getValue经过传入的OGNL表达式,在给定的上下文环境中,从root对象里取值:函数



setValue经过传入的OGNL表达式,在给定的上下文环境中,往root对象里写值:

 



OGNL同时编写了许多其它的方法来实现相同的功能,详细可参考Ognl.java代码。OGNL的API很简单,不管何种复杂的功能,OGNL会将其最终映射到OGNL的三要素中经过调用底层引擎完成计算,OGNL的三要素即上述方法的三个参数expression、root、context。

OGNL三要素

Expression(表达式):

Expression规定OGNL要作什么,其本质是一个带有语法含义的字符串,这个字符串将规定操做的类型和操做的内容。OGNL支持的语法很是强大,从对象属性、方法的访问到简单计算,甚至支持复杂的lambda表达式。

Root(根对象):

OGNL的root对象能够理解为OGNL要操做的对象,表达式规定OGNL要干什么,root则指定对谁进行操做。OGNL的root对象其实是一个java对象,是全部OGNL操做的实际载体。

Context(上下文):

有了表达式和根对象,已经可使用OGNL的基本功能了。例如,根据表达式对root对象进行getvalue、setvalue操做。
不过事实上在OGNL内部,全部的操做都会在一个特定的数据环境中运行,这个数据环境就是OGNL的上下文。简单说就是上下文将规定OGNL的操做在哪里进行。
OGNL的上下文环境是一个MAP结构,定义为OgnlContext,root对象也会被添加到上下文环境中,做为一个特殊的变量进行处理。

OGNL基本操做

对root对象的访问:

针对OGNL的root对象的对象树的访问是经过使用‘点号’将对象的引用串联起来实现的。经过这种方式,OGNL实际上将一个树形的对象结构转化为一个链式结构的字符串来表达语义,以下:
//获取root对象中的name属性值
name
//获取root对象department属性中的name属性值
department.name
//获取root对象department属性中的manager属性中的name属性值
department.manager.name

对上下文环境的访问:

Context上下文是一个map结构,访问上下文参数时须要经过#符号加上链式表达式来进行,从而表示与访问root对象的区别,以下:
//获取上下文环境中名为introduction的对象的值
introduction
//获取上下文环境中名为parameters的对象中的user对象中名为name属性的值
#parameters.user.name

对静态变量、方法的访问:

在OGNL中,对于静态变量、方法的访问经过@[class]@[field/method]访问,这里的类名要带着包名。以下
//访问com.example.core.Resource类中名为img的属性值
@com.example.core.Resource@img
//调用com.example.core.Resource类中名为get的方法
@com.example.core.Resource@get()

访问调用:

//调用root对象中的group属性中users的size()方法
group.users.size()
//调用root对象中group中的containsUser方法,并传递上下文环境中名为user值做为参数
group.containsUser(#user)

构造对象

OGNL支持直接经过表达式来构造对象,构造的方式主要包括三种:

1.构造List:使用{},中间使用逗号隔开元素的方式表达列表

2.构造map:使用#{},中间使用逗号隔开键值对,并使用冒号隔开key和value

3.构造对象:直接使用已知的对象构造函数来构造对象

能够看到OGNL在语法层面上所表现出来的强大之处,不只可以直接对容器对象构造提供语法层面支持,还可以对任意java对象提供支持。然而正由于OGNL过于强大,它也形成了诸多安全问题。恶意攻击者经过构造特定数据带入OGNL表达式解析并执行,而OGNL能够用来获取和设置Java对象的属性,同时也能够对服务端对象进行修改,因此只要绕过沙盒恶意攻击者甚至能够执行系统命令进行系统攻击。

深刻OGNL实现类

在OGNL三要素中,context上下文环境为OGNL提供计算运行的环境,这个运行环境在OGNL内部由OgnlContext类实现,它是一个map结构。在OGNL.java中能够看到OgnlContext会在OGNL计算时动态建立,同时传入map结构的context,并根据传入的参数设定OGNL计算的一些默认行为以及设置root对象。

 


这里看到MemberAccess属性,在struts2漏洞利用中常常看到。转到OgnlContext类中查看。

 


MemberAccess指定OGNL的对象属性、方法访问策略,这里初始默认状况下是flase,即默认不容许访问。在struts2中SecurityMemberAccess类封装了DefaultMeberAccess类并进行了扩展,指定是否支持访问静态方法以及经过指定正则表达式来规定某些属性是否可以被访问。其中allowStaticMethodAccess默认为flase,即默认禁止访问静态方法。

 



另外在OGNL实现了MethodAccessor及PropertyAccessor类规定OGNL在访问方法和属性时的实现方式,详细可参考代码文件。

 

 


在struts2中,XWorkMethodAccessor类对MethodAccessor进行了封装,其中在callStaticMethod方法中能够看到,程序首先从上下文获取到DENY_METHOD_EXECUTION值并转换为布尔型,若是为false则能够执行静态方法,若是为true则不执行静态发放并返回null。

 

Struts2漏洞利用原理

上文已经详细介绍了OGNL引擎,由于OGNL过于强大,它也形成了诸多安全问题。恶意攻击者经过构造特定数据带入OGNL表达式便可能被解析并执行,而OGNL能够用来获取和设置Java对象的属性,同时也能够对服务端对象进行修改,因此只要绕过Struts2的一些安全策略,恶意攻击者甚至能够执行系统命令进行系统攻击。如struts2远程代码执行漏洞s2-005。

虽然Struts2出于安全考虑,在SecurityMemberAccess类中经过设置禁止静态方法访问及默认禁止执行静态方法来阻止代码执行。即上面说起的denyMethodExecution为true,MemberAccess为false。但这两个属性均可以被修改从而绕过安全限制执行任意命令。s2-005 POC以下:

http://mydomain/MyStruts.action?('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&(aaa)(('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean("false")))&(asdf)(('\u0023rt.exec(‘ipconfig’)')(\u0023rt\[email]u003d@java.lang.Runt[/email]ime@getRuntime()))=1

在POC中\u0023是由于S2-003对#号进行了过滤,但没有考虑到unicode编码状况,而经过#_memberAccess[\'allowStaticMethodAccess\']')(meh)=true,能够设置容许访问静态方法,由于getRuntime方法是静态的,经过context[\'xwork.MethodAccessor.denyMethodExecution\']=#foo')(#foo=new%20java.lang.Boolean("false")设置拒绝执行方法为false,也就是容许执行静态方法。最后调用rt.exec(‘ipconfig’)执行任意命令。

相关文章
相关标签/搜索