将一段恶意的JavaScript代码隐藏在一张PNG图像中并发上推特,然后通过利用XSS绕过网站的CSP(Content-Security-Policy,内容安全策略),将其包含在易受攻击的网站中。这不是科幻的片段,只要用到HTML 的canvas标签就能够实现。
使用HTML 的canvas标签,你可以将每个源代码字符转化成像素,从而将任何的JavaScript代码(或者整个库)隐藏到PNG图像中。然后将图像上传到受信任的网站上,例如Twitter或者Google(通常被CSP列入白名单的网站)。最后,使用canvas的getImageData方法,从图像中提取出隐藏的JavaScript代码并执行它。有时这样的注入可以绕过CSP,让攻击者能够运行整个内部和外部JavaScript库。
由于CSP响应头能有效地抵挡XSS攻击、点击劫持和其他注入攻击,其使用越来越频繁。然而,
网站宽松的设置增加了漏报率
,允许访问全部内容,而不是特定资源被授权;又或者是内部不安全的设置及未经验证的外联,这都可能导致安全策略被绕过。
Mike Parsons 写了一篇关于他是如何使用HTML canvas标签在一张PNG图像中“存储”JavaScript代码的文章,这篇文章使我想到这可能是完美的绕过CSP的技术 ,包括利用XSS漏洞的恶意JavaScript的外部库。这篇文章主要是讲关于使用CanvasRenderingContext2D 的putImageData方法和getImageData方法以及使用String.charCodeAt方法来表示隐藏文本字符串的每个字符。
Canvas 2D API的CanvasRenderingContext2D.putImageData()方法将给定ImageData 对象中的数据绘制到canvas上。如果提供了脏矩形,则仅绘制该矩形中的像素。 此方法不受画布转换矩阵的影响。
Canvas 2D API的CanvasRenderingContext2D 的 getImageData()方法返回一个ImageData 对象,该对象表示画布的指定部分的基础像素数据。 此方法不受画布转换矩阵的影响。 如果指定的矩形延伸到画布的边界之外,则画布外的像素在返回的ImageData对象中为透明的黑色。
在下面的例子中,你可以复制并粘贴到你浏览器的JavaScript的控制台上,从给定的字符串开始创建PNG图像,例如:“ Hello ,World ! ”。打开一个新标签并转到about:blank,然后打开你的JavaScript控制台并粘贴以下代码:
上面的代码使用putImageData方法创建一个图像,该方法将“Hello,World”按3个字符一组的形式分组,并以此作为每个像素中RGB(Red,Green,,Blue;红绿蓝)的值。在浏览器控制台上运行以上代码,在左上角看到一张小图像。
正如我前面所说的,上面图像的每个像素分别代表“隐藏字符串”的三个字符。使用charCodeAt 函数将每个字符转换成0到65535之间的整数,代表每个字符UTF-16的码元。在单像素中,红色通道代表第一个字符,绿色通道代表第二个字符,蓝色通道代表第三个字符。第四个值在我们实例中一直是255的显著水平。例如:
这样我们就得到一张代表” Hello, World !” 字符串的图像,你可以将以下代码复制并粘贴到JavaScript控制台中,将生成的PNG图像转化回原始的文本字符串。
如你上面所见,JavaScript代码选定刚创建的图像元素,然后使用getImageData方法将其转换成原始文本字符串。使用getImageData方法时,将得到一个ImageData对象,该对象的data属性包含一个大数组。如前面所见,每个像素中,ImageData数组有四个元素:r,g , b和alpha。这些数组看起来像 [pixel1R, pixel1G, pixel1B, pixel1Alpha, ..., pixelNR, pixelNG, pixelNB, pixelNAlpha]. 正
现在我们知道如何在一张图像中存储文本字符串和怎么把图像还原文本字符串。假设隐藏的JavaScript代码不是简单的文本字符串,然后将生成的图像上传到推特。这样就可以绕过内容安全策略,即绕过Twitter的白名单图像,并可以利用XSS从“受信用”来源加载JavaScript内容。
我创建了一个易受攻击的web应用程序 去测试上述提及的技术。该web应用程序使用CSP防止加载JavaScript外部资源。
XSS易受攻击的web应用程序
如你所见,该应用程序使用CSP,该策略允许来自“ self ” 和推特的图像,并允许来自“self“,”inline“和”eval 的JavaScript。遗憾的是,有许多网站配置script-src指令,允许使用unsafe-inline和unsafe-eval来避免评估。而且,许多网站将整个域列入白名单,而不是将特定资源列入白名单。
如果你认为这样配置的网站很少见,并且没有人在script-src 指令上使用“unsafe-inline“和”unsafe-eval”,那么就大错特错了。通过分析对Alexa Top 1m 网站的爬取信息,似乎有超过5000个网站 在script-src或者default-src指令上使用带有“unsafe-inline“和”unsafe-eval“的CSP。
该漏洞位于lang请求的参数上,该参数不能清除由于用户输入而导致的HTML注入和反应XSS。为了在测试网站上使用XSS漏洞,我需要注入HTML语法以闭合src属性并添加SCRIPT标签,然后注入JavaScript代码,例如:
利用XSS漏洞
现在我使用代码
去绕过CSP并且加载JavaScript外部库
CSP阻挡外部的JavaScript代码
上面的截图展示了CSP是如何在example.com/evil.js处阻止外部JavaScript文件,这是因为它违反了script-src指令。
经过一些测试,我发现当上传的图片太小时推特会报错。因此,我使用很长的JavaScript代码(包括随机注释)来创建更大的图像。
然后我上传到Twitter上
通过将来自Twitter的图像加载到IMG标签中,我可以将其转化为隐藏的JavaScript代码。如下面截图所示,该图像包含一个JavaScript函数,该函数执行后弹出document.cookie值的警告框:
现在来尝试在我的易受攻击的web应用程序上eval这段JavaScript代码。
语句去闭合src属性
红色部分是我们放在推特上恶意图像的URL。蓝色部分是注入id属性,以便更轻松地选择IMG标签。绿色部分是A标签,仅用于防止破坏HTML语法。现在我需要注入一些JavaScript代码,将图像转化成JavaScript外部库并绕过CSP:
我已经对其使用base64编码,现在在eval(atob("base64-encoded-js"))中使用它,并将其放入注入的onload属性中:
发送整个payload时,执行getImageData时出现报错,信息为“画布已被跨域数据污染 “。
通过阅读文档 ,当跨域加载图像时,似乎我的浏览器有意阻止了诸如getImageData之类的方法。 看来我还需要注入crossorigin属性:
HTML为图像提供了crossorigin 属性,该图像与CORS 标头结合使用,允许<img> 元素定义的外部来源加载的图像在<canvas> 中使用,就像从当前来源加载图像一样。
由于画布位图中的像素可能有多种来源,包括从其他主机检索到的图像或视频,因此不可避免地会出现安全问题。 一旦将任何未经CORS批准从其他来源加载的数据绘制到画布中,画布就会被污染 。 脏画布是一种不再被认为是安全的画布,任何从画布中检索图像数据的尝试都将引发异常。 如果外部内容的来源是HTML <img> 或SVG <svg> 元素,则不允许尝试检索画布的内容。
幸运的是,Twitter向所有上传的图像添加了Access-Control-Allow-Origin: *,这意味着我只需要将crossorigin属性注入到IMG标签中并带有“ anonymous”值即可。
通过将crossorigin属性注入值“ anonymous”,所有功能均按预期工作,并且我成功执行alert(document.cookie)这个功能,如下面截图所示:
来自Twitter图像的警告框
下面是我使用完整的Payload:
这是测试BeFF’s hook.js 转换成一张PNG图像。BeEF是一个浏览器开发框架,这是一个专注于Web浏览器的渗透测试工具(您可以在BeEF的网站上找到更多信息)。 我首先使用curl -s 'http://localhost:3000/hook.js' | base64 -w0进行了base64编码,然后我创建了如下所示的图像。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)