引言

之前在一步小心留下的XSS漏洞就讨论过XSS注入的问题, 当时分析了一种情况, 是复用了其他页面传输来的数据, 而这份数据的不可控性, 导致了有心人将数据篡改 进行XSS注入.

Rank页面的实现

但是rank页面与XSS注入又有什么关系呢?

这里, 有一点请大家注意, rank页面中的排序结果来自于LeanCloud中的数据, 本站点是一个由hexo创建的静态站点, 统计功能当然也是依赖于LeanCloud.

本页的主要逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
var Counter = AV.Object.extend('Counter');
var query = new AV.Query(Counter);
query.addDescending('time');
query.limit(10); // 最多返回 10 条结果

query.find().then((data) => {
data.forEach((rlt) => {
// rlt.get('title'); // 获取标题
// rlt.get('time'); // 获取访问次数
});
});

直接使用返回值存在的安全问题

我不知道大家注意到了没有, 我们在前端页面中暴露了LeanCloudApp IDApp Key, 可以由任何人将数据传递任何数据.

这种情况下, 仅仅是存储数据, 是没有问题的, 但是问题就存在于读取时了, 如果读取到的数据是已经被篡改的, 而后由我们直接渲染到了页面上, 就很可能出现XSS注入的 情况.

解决方案 – (加一层过滤)

如果你去了rank页中, 查看了其中的代码, 就会发现其中有这么一段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 该map中存储了已知的所有标题
var m = new Map();

query.find().then((data) => {
data.forEach((rlt) => {
// 尝试获取对应的标题
let real_blog = m.get(rlt.get('title'));

// 如果该标题无法对应已知的标题, 将该字段跳过.
if(!real_blog) {
console.log("内容已经出错, 出错内容为:", rlt);
return;
}

// 获取访问次数, 也需要确认是数字类型的数据, 不可以直接使用
let real_blog_time = parseInt(rlt.get('time'));
insertPairs(tbl_rank, real_blog['title'], real_blog['url'], real_blog_time);
});
});

上面的代码中, 一个重要的思路就是, 不能信任来自LeanCloud中的数据, 需要对这一层数据 的有效性进行一次校验, 避免有心人修改数据库.