Struts2漏洞利⽤原理及OGNL机制
Struts2漏洞利⽤原理及OGNL机制研究
概述
在MVC开发框架中,数据会在MVC各个模块中进⾏流转。⽽这种流转,也就会⾯临⼀些困境,就是由于数据在不同MVC层次中表现出不同的形式和状态⽽造成的:
View层—表现为字符串展⽰
struts框架是干什么的数据在页⾯上是⼀个扁平的、不带数据类型的字符串,⽆论数据结构有多复杂,数据类型有多丰富,到了展⽰的时候,全都⼀视同仁的成为字符串在页⾯上展现出来。数据在传递时,任何数据都都被当作字符串或字符串数组来进⾏。
Controller层—表现为java对象
在控制层,数据模型遵循java的语法和数据结构,所有的数据载体在Java世界中可以表现为丰富的数据结构和数据类型,你可以⾃⾏定义你喜欢的类,在类与类之间进⾏继承、嵌套。我们通常会把这种模型称之为复杂的对象树。数据在传递时,将以对象的形式进⾏。
可以看到,数据在不同的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 技术内幕)。
关于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]访问,这⾥的类名要带着包名。如下
//访问Resource类中名为img的属性值
@Resource@img
//调⽤Resource类中名为get的⽅法
@Resource@get()
访问调⽤:
//调⽤root对象中的group属性中users的size()⽅法
group.users.size()
//调⽤root对象中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如下:
mydomain/MyStruts.action?('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&(aaa)(('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean("false")))&
在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’)执⾏任意命令。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论