原文地址:http://www.jazcash.com/a-javascript-journey-with-only-six-characters
JavaScript是一门奇妙而且有趣的语言,它可以让我们写一些疯狂但仍然有效合理的代码,它试图通过把事情转换到基于我们如何对待他们的特定类型上来帮助我们。
如果我们添加一个字符串,它会假定我们希望是文本形式表示,所以它将为我们转换成一个字符串。
如果我们添加一个加号或者是减号前缀,它会假定我们希望是数值形式呈现,如果可能的话那么就会为我们把一个字符串转换成数字。
如果我们添加一个否定符号,它会将字符串转换为一个布尔值。
我们可以用Javascript中的[
,]
,(
,)
,!
and+
这六个符号写一些神奇的代码。
如果你现在不是在移动设备上,你可以打开你浏览器的JS控制台跟着下面的步骤一起操作,你可以将任何示例代码拷贝粘贴到JS控制台,并且代码值应该都为true。
让我们从最基础的开始,记住一些黄金规则:
前面加
!
会被转换成布尔值前面加
+
会被转换成数值添加
[]
会被转换成字符串
这里他们是相等的:
1 | ![] === false |
另一件事你应该知道的是,它可以从字符串使用方括号检索特定的字母:
1 | "hello"[0] === "h" |
还可以使多个数字通过添加字符串表示在一起,然后把整个表达式转换成一个数字:
1 | +("1" + "1") === 11 |
好的,接下来让我们把一些东西结合在一起最后得到字母a
:
1 | ![] === false |
我们可以通过true
和false
得到相似的字母a
,e
,f
,l
,r
,s
,t
,u
。那么我们可以从其他地方得到字母吗?
我们可以通过一些特别的式子像[]
、[[]]
得到undefined
,用我们上面的黄金法则得到另外的字母d
,i
和n
。
1 | [][[]] + [] === "undefined" |
到目前为止我们获得的所有字母,我们可以拼fill
,filter
和find
。当然我们也可以拼一些其他的单词,这些单词最重要的是他们都是Array methods。这也就意味着他们是Array
对象的一部分,可以直接调用数组实例(如[2,1].sort()
)。
现在了解另一件重要的JS的特性是一个对象的属性可以通过点符号.
或方括号[]
访问。上述数组方法是数组对象本身的属性,我们可以使用方括号代替点符号调用这些方法。
所以[2,1]["sort"]()
等价于[2,1].sort()
。
让我们继续看看到底发生了什么当试图使用一个数组的方法的时候,可以使用到目前为止拼写的但没有调用过的字母。
1 | []["fill"] |
得到function fill() { [native code] }
,可以使用我们的黄金法则把这个方法头作为一个字符串。
1 | []["fill"]+[] === "function fill() { [native code] }" |
现在我们又得到le其他的字符:c
,o
,v
,(
,)
,{
,[
,]
,}
。
新得到的c
和o
现在可以形成 constructor
这个单词了,到目前为止我们已经处理的对象可以得到它用字符串表示的构造器函数:
1 | true["constructor"] + [] === "function Boolean(){ [native code] }" |
Number
类型的toString
方法有一个称为radix
(“基数”)的秘密的论点。它可以将数值在转换为一个字符串之前先经过基数换算。
1 | (12)["toString"](10) === "12" // base 10 - normal to us |
但是为什么基数只写到16?最大值是36包括所有的字符0
-9
和a
-z
,因此现在我们可以得到任何我们想要的字符。
1 | (10)["toString"](36) === "a" |
棒极了!但是其它字符如标点符号和大写字母如何呢?让我们继续接着往下深入研究。
这一点依赖于你的js正在运行在什么地方,它可能会或可能不会访问特定的预定义的对象或数据。如果你的JS运行在浏览器中,那么你可以访问到一些保留的HTML wrapper methods。
比如,bold
是用一个用b
标签包裹字符串的字符串方法。
1 | "test"["bold"]() === "<b>test</b>" |
这样我们也能够得到字符<
,>
and/
。
你或许已经听说过escape
方法。它主要是将一个字符串转换为一个URI友好的格式,这样可以让浏览器解释编译。这个方法是我们研究的一个重要部分,因此我们要去探究它。我们可以拼写它但是如何真正执行它呢?它不是一个属于我们到目前为止处理过的任何类型的函数,实际上它是一个全局函数。
任意一个函数的构造器是什么样的?
答案是function Function() { [native code] }
,它本身是一个函数对象。
1 | []["fill"]["constructor"] === Function |
用这个方法我们可以给函数传递一个字符串代码。
1 | Function("alert('test')"); |
1 | Function anonymous() { |
现在我们可以像这样使用我们的escape
方法。
1 | []["fill"]["constructor"]("return escape(' ')")() === "%20" |
如果我们想得到我们忘记的其他字符大写C
是很重要的。
1 | []["fill"]["constructor"]("return escape('<')")()[2] === "C" |
用这个方法我们现在可以写一个fromCharCode
方法,它可以将一个给定的十进制表示的数返回成一个 Unicode
字符。
1 | ""["constructor"]["fromCharCode"](65) === "A" |
我们可以使用Unicode lookup去找到一个任意字符用十进制表示的数。
为什么它是有用的呢?
eBay不久前做了一些bad things,他们允许商家使用这些字符执行JS在页面中。但这是一个很少见的攻击量。一些人提到了混淆,但实际上有更好的混淆方式。
Sources: