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 的类型转化太灵活了,存在太多种特例。平时写代码要避免这些奇怪的边角情况。