# Java代码审计之Struts2-003、005(三)

**URL:** <https://forum.90sec.com/t/topic/1186>\
**Category:** 代码审计\
**Created:** [2020 年7 月 6 日 15:53 UTC](https://forum.90sec.com/t/topic/1186 "2020-07-06T15:53:53Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![mochazz](https://forum.90sec.com/user_avatar/forum.90sec.com/mochazz/32/2270_2.png) [@mochazz](https://forum.90sec.com/u/mochazz)\
发布日期： [2020 年7 月 6 日 15:53 UTC](https://forum.90sec.com/t/topic/1186/1 "2020-07-06T15:53:53Z")

</div>

本系列文章将详细分析 **Java** 流行框架 **Struts2** 的历史漏洞，今后关于 **Struts2** 新的漏洞分析，也将更新于 [Struts2-Vuln](https://github.com/Mochazz/Struts2-Vuln) 项目上。该系列仅是笔者初学 **Java代码审计** 的一些记录，也希望能够帮助到想学习 **Java代码审计** 的朋友 。如有任何问题，欢迎 **issue** 。分析文章均来自 [**个人博客**](https://mochazz.github.io) ，转载请注明出处。

## 漏洞概要

Struts2-003是一个远程代码执行漏洞，Struts2-005为Struts2-003补丁绕过。

影响版本： **Struts 2.0.0 - Struts 2.1.8.1** 。更多详情可参考官方通告：[https://cwiki.apache.org/confluence/display/WW/S2-003](https://cwiki.apache.org/confluence/display/WW/S2-003)

## 漏洞环境

Struts2-003：Apache Tomcat/6.0.10+struts-2.0.8 Struts2-005：Apache Tomcat/6.0.10+struts-2.0.12

下载地址：[http://archive.apache.org/dist/struts/binaries/struts-2.0.8-all.zip](http://archive.apache.org/dist/struts/binaries/struts-2.0.8-all.zip)

[https://archive.apache.org/dist/tomcat/tomcat-6/v6.0.10/bin/apache-tomcat-6.0.10.zip](https://archive.apache.org/dist/tomcat/tomcat-6/v6.0.10/bin/apache-tomcat-6.0.10.zip)

## 漏洞分析

本次漏洞是通过绕过 **ParameterInterceptor** 拦截器的过滤，进而执行 **OGNL** 表达式，所以我们先在 **ParameterInterceptor:doIntercept()** 方法中下断点。 此时，存放 **HTTP** 数据的 **parameters** 变量将通过 **setParameters** 方法设置到值栈中，我们跟进该方法。

![1](https://forum.90sec.com/uploads/default/original/2X/7/7ea87d1f6a330d2959dbcd319d9385c6a9119695.png)

**setParameters** 方法会调用 **acceptableName** 方法判断参数是否含有非法字符，可以发现非法字符中包含 **#** 号，所以我们的 **payload** 需要将 **#** 号编码成 **\u0023** 。通过校验后，会调用 **setValue** 方法将值添加进值栈，我们继续跟进该方法。

![2](https://forum.90sec.com/uploads/default/original/2X/c/cd505446eec59df0f4e83e7faa7c6a4a0c01455a.png)

在 **setValue** 方法中，会调用 **compile** 方法对字符串进行解析。

![3](https://forum.90sec.com/uploads/default/original/2X/9/9682ba54ebd69334818b031709ecbe1a36cbc149.png)

在解析字符串时，程序会对 **\u** 字符进行解码，具体处理代码在 **JavaCharStream:readChar()** 中。

![4](https://forum.90sec.com/uploads/default/original/2X/0/042fc30494e33248524c863b83a4226281fd4b4b.png)

经过 **compile** 方法解码后的字符串，又会回到 **Ognl.setValue()** ，表达式的解析执行也正是在此处。

![5](https://forum.90sec.com/uploads/default/original/2X/5/5972aa2821735ce962cd25e643812726bf1a56cc.png)

我们来尝试弹个计算器：

```
login.action?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec(\'deepin-calculator\')')(bla)(bla)

```

![6](https://forum.90sec.com/uploads/default/original/2X/4/4e7a5065110583d7ad01a9cc6ce50753e60d1a49.png)

## 漏洞修复

下图右边为官方修复后的代码（左图struts-2.0.8，右图为struts-2.0.12）。

![7](https://forum.90sec.com/uploads/default/original/2X/5/514bdf895c27abf502a67a669aa4eb2f58677c44.png)

![8](https://forum.90sec.com/uploads/default/original/2X/4/4cb325c03e43e0f90d05763aa0a789dd13dc1672.png)

我们会发现，修复代码主要引入控制静态方法调用开关 **allowStaticMethodAccess** 变量，以及用于控制成员变量的访问权限的 **SecurityMemberAccess** 类对象。然而通过 **OGNL** 表达式，我们完全可以控制这两个变量的值，这也导致 **Struts2-003** 补丁可以被绕过，即后来的 **Struts2-005** 漏洞。

```
# struts-005 poc

login.action?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(bla)(bla)&('\u0023_memberAccess.allowStaticMethodAccess\u003dtrue')(bla)(bla)&('\u0023_memberAccess.excludeProperties\u003d@java.util.Collections@EMPTY_SET')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec(\'deepin-calculator\')')(bla)(bla)

```

![9](https://forum.90sec.com/uploads/default/original/2X/6/64d21b8997de1ffd290331b6907f230cbc6beb61.png)

这里有个小小的疑问，在 **Struts2-005** 的 **poc** 中有这么一个设置：`#_memberAccess.excludeProperties=@java.util.Collections@EMPTY_SET` ，在调试代码时会发现 `#_memberAccess.excludeProperties` 的默认值为 **dojo\..\*** (如下图)。

![10](https://forum.90sec.com/uploads/default/original/2X/3/357bb4ff693edd7b290dfb64c5d5b6bca2142e23.png)

而这个正则一般不会匹配到我们的 **poc** ，也就是说其实我这里不设置`#_memberAccess.excludeProperties=@java.util.Collections@EMPTY_SET` 也可以。然而实际测试结果是，没有设置这个，后面的 **java.lang.Runtime@getRuntime().exec** 就不能执行成功，这里不懂是什么原因。

最后再来看下 **Struts2-005** 的修复代码，下图右边为官方修复后的代码（左图struts-2.0.12，右图为struts-2.2.1）。修复代码最终使用了更加严格的正则 `[a-zA-Z0-9\\.\\]\\[\\(\\)_'\\s]+` 来校验参数名的合法性。

![11](https://forum.90sec.com/uploads/default/original/2X/7/7e1ae5ffab381ba4e493dbdcafafd20accd3ff80.png)

## 参考

[Struts2基于OGNL的RCE漏洞全解析](https://www.freebuf.com/vuls/217482.html)  
[191204-Ognl 使用实例手册](http://blog.hhui.top/hexblog/2019/12/04/191204-Ognl-%E4%BD%BF%E7%94%A8%E5%AE%9E%E4%BE%8B%E6%89%8B%E5%86%8C/)  
[【Struts2-命令-代码执行漏洞分析系列】S2-003和S3-005](https://xz.aliyun.com/t/2323)  
[Struts2漏洞分析与研究之S2-005漏洞分析](https://blog.csdn.net/u011721501/article/details/41626959)  
[Struts2漏洞系列之【S2-005】 参数名过滤不严谨导致代码执行](https://mp.weixin.qq.com/s/xaVxdYPRIhlxiFLy9WhUHA)
