无论用户传递了何种参数到网页上, 都不要直接显示, 最好与服务端交互一次, 最次也要在js中全部转义, 永远不要直接渲染.

TL;DR

基本需求

我们在网页上查看商品时, 有可能只是看到一个名字, 而后我们想要点击详情, 在新的页面就可以看到详细信息.

我的偷懒

因为我在写程序时, 首页其实已经请求了详细信息, 只是显示一部分而已, 如果把这 一部分信息继续加载到详情页面, 就可以省一次到后端的请求. 也就是说, 当前页面中的 内容要传递到下一个页面中.

我参考了html页面跳转传递参数中的方式. 将页面中的内容编码, 传递到下一个 页面中, JS代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// index.js
let data={
"name": "a good",
"price": 21.0,
"desc": "a good only use to test",
};

$("#detail").on("click", function() {// 点击按钮时, 网页跳转带上一个get参数
let data_pretty = JSON.stringify(data); // 序列化成字符串
let info = btoa(data_pretty); // base64编码
window.location = `detail.html?info=${info}`;
});

xss1

点击按钮后, 请求的链接大概是这样的: http://127.0.0.1:8000/detail.html?info=eyJuYW1lIjoiYSBnb29kIiwicHJpY2UiOjIxLCJkZXNjIjoiYSBnb29kIG9ubHkgdXNlIHRvIHRlc3QifQ==

而后在detail.html中, 设法将参数取出, 就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
// detail.js
// 接受从其他页面来的请求参数
function getParams(key) {
var reg = new RegExp("(^|&)" + key + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}

let info = getParams("info"); // 取出get参数.
let data_info = JSON.parse(atob(info));

然后, 渲染到页面上只是很简单的一件事情了.

xss1

上述的代码放在Github仓库中, 可以自行查看.

我在示例代码中做了简化, 首页只显示某个商品名称, 点击detail按钮后, 跳转的页面 可以显示详细的信息.

假如用户分享了链接, 他就需要把那么长的一串url分享给别人, 别人也可以通过 点击链接获取的商品信息, 这是皆大欢喜的事情, 为什么为有漏洞呢?

如何注入

将信息编码之后传递, 的确是一件省事的事情, 但是假如有黑客将URL修改了呢. 我们根本无法分享链接的传播. 当我们知道了网页的渲染方式之后, 就可以简单的 进行注入了.

假如我们要传递的商品信息换成了下面这种.

1
2
3
let xss_data = {
"data" : "Comment#><img src=x onerror=alert(1)//>"
};

xss3

编码之后, 把info的信息替换掉, 再次请求网页. http://127.0.0.1:8000/detail.html?info=eyJkYXRhIjoiQ29tbWVudCM+PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpLy8+In0=

xss5

alert(1)就开始工作了, 也就意味着, 你可以写任何形式的js代码在网页上工作了.

假如有心人篡改了url, 将其注入自己的脚本, 可以轻松的获取到cookie (只要他带上cookie向自己的服务器发送一次请求就好).

有关XSS注入我并不是很精通, 参考了这个网站xss-bypass的注入方式.

总结

我们绝对不能贪图省事, 将URL中存在的信息直接渲染到HTML上, 即使你将自己真正的 信息加密了, 由于JavaScript的运行也是在用户一侧, 黑客可以有各种方式撩爆你的JS. 然后加入自己想要的任何脚本, 而你将会眼睁睁看着自己的用户点那些不该点的链接.