跟着蘑菇老师学前端安全第一弹[二哈]

地址:

http://server.n0tr00t.com/n0js/case1.html

主要代码:

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
function test(a1, a2, a3) {
console.log(`${a1}`);
eval(`${a2}`);
console.warn(`${a3}`)
};
var x = location.hash;
l1 = escape(x.split('#')[1]);
l2 = x.split('#')[2];
l3 = x.split('#')[3];
if (l1 === 'undefined' || l1.length >= 30) {
a1
};
if (l2 === 'undefined' || l2.length >= 35) {
a2
};
var v = ['1', l2, '3'];
if (l1.indexOf(';') >= 0 || l2.indexOf('(') >= 0) {
a3
};
if (l2.indexOf('/') >= 0) {
b1
};
if (l3.indexOf(']') >= 0 || l3.length >= 2) {
c3
}; {
d = l1 + 'v' + l3.trim();
eval('test(' + d + ')')
};

解题过程

查看页面源码可以看到我们需要分析的js。

代码分析

做类似的题目第一步都是要理清js的逻辑。这一次的题目可以把代码分为四部分来看。

I. test函数定义:第二个参数a2传入eval执行,可能就是我们要利用的点

1
2
3
4
5
function test(a1, a2, a3) {
console.log(`${a1}`);
eval(`${a2}`);
console.warn(`${a3}`)
};

II. 获取hash生成变量进行处理,第一个变量l1进行了escape编码,会转化为unicode编码值,因此不能包含部分特殊符号

1
2
3
4
var x = location.hash;
l1 = escape(x.split('#')[1]);
l2 = x.split('#')[2];
l3 = x.split('#')[3];

III. 第三部分算是过滤,如果执行到判断体中的未定义变量,js就不能往下执行了。所以不能使任意一个条件满足。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (l1 === 'undefined' || l1.length >= 30) {
// 第一个hash不得为空,限制payload长度小于30
a1
};
if (l2 === 'undefined' || l2.length >= 35) {
// 第二个hash不得为空,限制payload长度小于35
a2
};
if (l1.indexOf(';') >= 0 || l2.indexOf('(') >= 0) {
// 第一个hash内不得有';'和'('
a3
};
if (l2.indexOf('/') >= 0) {
// 第二个hash不得有'/'
b1
};
if (l3.indexOf(']') >= 0 || l3.length >= 2) {
// 第三个哈希是只能有一个字符而且不得为']'
c3
};

IV. 主要考察点,把第二个hash放入数组v的第二个元素,前后拼接另外两个hash字符串传入test函数中。

1
2
3
4
5
var v = ['1', l2, '3'];
{
d = l1 + 'v' + l3.trim();
eval('test(' + d + ')');
};

思路

这里主要考察如何把payload放到test的第二个参数里去执行。

我原先构造了: #2+#prompt(1)#,想利用 2 + [1,’prompt(1)’, 3] = “21,prompt(1),3” 的方式把逗号带入eval中。

但是这里最后的拼接d = l1 + 'v' + l3.trim();使用的是'v'而不是v。所以整个字符串连同逗号一起被传入了test的第一个参数中,压根就影响不到第二个参数。

思考了很久都没有很好地办法给第二个参数赋值。

后来开始翻ES6文档,蘑菇说ES6这个方向是对的,就认真的翻了几个新特性。

翻了很久,看到了这个:
https://leanpub.com/understandinges6/read#leanpub-auto-the-spread-operator

理清楚代码逻辑我们知道最尾部执行的eval其实是这样的eval('test('+'第一个hash:l1'+'v'+'第三个hash:l3')');,v是一个数组,该数组中第二个元素受到第二个hash:l2控制,刚好test的第二个参数被直接传入eval内进行执行。上面提到的ES6特性,可以把一个数组中的元素分发到函数的多个参数中。

例如:

1
2
3
4
5
6
7
function log(a, b, c) {
console.log(`${a}`);
console.info(`${b}`);
console.warn(`${c}`);
};
str_list = ['log','info','warn'];
log(...str_list)

结果如:

其实这个特性在其他语言中也很常见,例如在python中:

再回到我们的n0js-case1中来,很显然我们可以构造test(...v)使我们没有经过编码的l2带着我们所需的payload进入test函数内的eval执行。只需把第一个hash设置为...即可。escape会把除了ASCII字母、数字、标点符号”@ * _ + - . /“以外的字符进行编码。显然.是不会被编码的。

这里还有一点,l2不允许含有括号’(‘。可以使用ES6的另一个特性,使用function_name``代替functon_name().

因此可以构造最后的payload:

http://server.n0tr00t.com/n0js/case1.html#...#prompt`1`#

最后

好吧第三个hash确实没有用到。出题人说第三个hash是为了给各位做题的留空间,看看能不能激发出新的解题方式。嗯这个解释不错,但是为什么我手中的菜刀就是在闪闪发光呢?

我最初还想在最下面的eval直接构造prompt执行,不经过test函数内的eval。但是失败了2333

ps. 新一期的[n0js] case2已经开始了,据说上一次蘑菇同学守着邮箱很久没有人解出来,心中很失落。这一次也很有趣,欢迎来玩啊。url:http://server.n0tr00t.com/n0js/case2.html

ps. 看到ES6扩展运算符和python特性的朋友们是否会想到最近pwnhub史上第一道web题里的’func(**{key:value})’呢?虽然不是一样的题目却是相似的特性。这些新特性在方便开发者的同时,增加程序灵活性的同时,也会产生新的安全问题,想必也是更加有趣的安全研究方向。

再ps. 上一次解出来之后,我就把所看的ES6文档放到了 http://wiki.ioin.in/ 上,这次做不出来为什么不翻翻Sec-News呢?

(连着给你们打三个广告我容易吗?)

本文URL: https://blog.neargle.com/2016/12/15/n0js-case1-writeup/index.html