Non-alpha Javascript
+[]
也是一段 JS 代码。 运行的结果是什么呢?
求值
+[]
的运行结果是 0 。 如果不相信,按 F12 打开浏览器的调试器,按 ESC 调出控制台。输入 +[]
,回车就可以看到结果。
结果为什么这么反直觉?
想知道运算过程中发生了什么,一个最好不过的方式就是去看语言的规范。
比如想看 +
操作符的运算过程,打开 JS 规范。
转到一元 + 操作符 这一节。
The unary + operator converts its operand to Number type.
The production UnaryExpression : + UnaryExpression is evaluated as follows:
- Let expr be the result of evaluating UnaryExpression.
- Return ToNumber(GetValue(expr)).
也就是求 ToNumber(GetValue([]))
。可以简单地认为求的是 ToNumber(false)
。
false
转成数字自然是 0 。
所以 +[]
的结果为 0 。
全家福
1 | [][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][[]]+[])[+[]]+([][[]]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][[]]+[])[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+([]+{})[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][([]+{})[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+[]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+([]+{})[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][[]]+[])[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]])()(([]+{})[+[]])[+[]]+(!+[]+!![]+!![]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[]))+([][[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]] |
这段代码的输出结果是 “Hello” 。是不是有点不可思议?代码中没有包含任何 “Hello” 字母。
JS 可以做到没有任何字母和数字就能编写代码。
只需要用到 !
,+
,[]
,{}
和 ()
8个字符就可以编写大部分代码。这样没有数字字母的 JS 代码称为 non-alpha Javascript 。
数字
non-alpha Javascript 是怎么做到没有数字字母也能写代码的呢?
其实本质是将数字字母转化为上述的八个字符。
上面分析了 +[]
等于 0
那么如果是 2 ,5 ,8 这种数字该怎么表达呢?
光用 0 是没有办法得到其他数字的。
1 + 1 = 2 。
所以只需要再得到 1 即可得到其他所有数字。
很容易联想到 false 对应的是 0 ,那么 true 对应的应该是 1 。
怎么得到 true ?已有的条件是 false 。那只需要取反即可。
也就是 !false
。在 JS 中,[]
被当作 false
。所以带入前式得 ![]
。
还要使用 +
将 ![]
将结果转为数字。
所以 +![]
就会得到 1 。
要是这么想就大错特错了。JS 可不是凡人能够理解的。
+![]
的结果是 0 ,跟 +[]
一样。
为什么是这样哪里出错了?
看代码就清楚了
1 | [] == false |
这里面是有深坑的(捂眼痛哭),我比较懒就暂时不深究了。
从上面可以看到 !![] == true
。所以 +true
等于 !![]
使用 + 将结果强制转化为数字。
+!![]
的结果就是 1 。
所以 2 = 1 + 1
也就是 2 = +!![]
+ +!![]
利用进行 + 运算时只要一边是整数类型,就会把另一边也强制转化为整数类型,上式可以简化为
2 = !![]
+ !![]
1 的表达式不是唯一的。
注意到 !0 == true
。
所以 +!0 == 1
。
也就是 +!+[]
的值为 1
所以 2 还可以表示为 +!+[]
+ +!+[]
简化一下得 !+[]
+ !+[]
现在知道 JS 有多灵活了吧。
类似的可以得到所有数字
数字 | JS |
---|---|
0 | +[] |
1 | !+[] |
2 | !+[] + !+[] |
3 | !+[] + !+[] + !+[] |
4 | !+[] + !+[] + !+[] + !+[] |
5 | !+[] + !+[] + !+[] + !+[] + !+[] |
6 | !+[] + !+[] + !+[] + !+[] + !+[] + !+[] |
7 | !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[] |
8 | !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[] |
9 | !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[] + !+[] |
现在已经拥有所有数字了。假设想得到 12 。用 9 + 4 即可。
字母
如果说得到数字的过程还算正常的话,那么下面介绍得到字母的方法只能说是奇技淫巧。
基本的思想是从 JS 内置的关键字中按索引得到单一的字母。
比如 JS 有一个 NaN
表示非数字。
从 NaN
中可以得到 N
和 a
想要得到 a
只需要 "NaN"[1]
"NaN"
等于 +{}+[]
所以 "NaN"[1]
等于 (+{}+[])[1]
因为 1 等于 +!+[]
所以最后结果是 (+{}+[])[+!+[]]
类似地,从 true
中可以得到 t
,r
,u
,e
从 undefine
可以得到 n
,d
,f
,i
还有一种获取字母的通用方法。
至此已经获得了所有的数字字母。可以愉快的编程了。
后记
non-alpha javascript 也不知道是谁先想出来的。真的是太有才了。有的人用它绕过 XSS 的过滤。这种思路写过滤器的人估计也没考虑到吧。也从侧面证明了 JS 的类型转化太灵活了,存在太多种特例。平时写代码要避免这些奇怪的边角情况。