js黑客思想(2)

不使用括号调用函数

  你希望一个特定的对象返回一个原始值时,可以使用 valueOf 方法。通常,你会将它与对象字面量一起使用,然后将你的对象与其他原始值进行加法或减法操作。

let obj = {
  valueOf() {
    return 1
  }
}
console.log(obj + 1) // 2

let obj = {
  valueOf: alert
}
obj + 1 // Uncaught TypeError: Illegal invocation

  我们将 alert 定义为与 valueOf 一起调用,js 抛出 illegal invocation.这是因为 alert 函数要求 this 是一个 window 对象。当我们的 alert 函数被调用时,this 对象时我们自定义对象 obj,这就是为什么会抛出异常。大多数 js 中的对象都继承自 object 原型,并且 valueOf 实际上时在 Object 原型中定义的,这意味着我们不需要使用自定义对象,我可以使用现有对象,因为几乎每个对象都具有 valueOf 功能!

window.valueOf = alert
window + 1

// 因为默认使用的是windows对象,所以可以删除window
valueOf = alert
window + 1

// 你也可以使用toString以与valueOf相同的方式
valueOf = alert
window + 1

  我们扩展一下目标,不仅仅调用函数,还可以尝试调用函数并传递参数,而且都不需要任何括号。window 对象有一个全局处理程序叫做 onerror,当你提供一个处理程序时,你的函数将接收到页面上的所有异常。该处理程序将在第一个参数中以消息的形式被调用,url 在第二个参数中,行号在第三个参数中,列号在第四个参数中,最后一个参数中是错误对象。

onerror = alert
throw 'foo' //Uncaught foo

onerror = eval
throw '=alert\x281\x29'

alert(throw 'test') //这无效

  throw 语句是一个语句,这意味着它不能用作表达式。在使用块语句时,你不需要在块之后包含分号,这可以绕过分号的限制。

{
  onerror = eval
}
throw '=alert\x281337\x29'

onerror = alert
throw 1337

  我们给"foo"使用逗号操作符赋值。let foo=('bar','baz')。foo的值是"baz".这是因为逗号操作符返回表达式的最后一个部分。在使用throw语句时,它接受一个js表达式,因此逗号操作符在这里完全适用。因此,可以滥用这个功能来减少你使用的字符数量并创建一些令人惊讶的js代码。

throw onerror = alert,1337

  上述代码使用throw语句并赋值给onerror处理程序,然后使用逗号操作符,将表达式1337的结果传递给异常处理程序,导致调用alert函数并显示Uncaught 1337.你可以使用任意数量的操作数,只要它们属于同一个表达式,并且最后一个操作数将始终传递给异常处理程序。

throw onerror =alert,1,2,3,4,5,6,7 ;// Uncaught 10

  js中的另一个特性是catch子句中的可选异常变量。这使我们可以在不使用括号的情况下使用try catch块,并简单的抛出异常以调用异常处理程序:try{throw onerror =alert} catch{ throw 1337 }

  标记模版字符串提供了许多在不使用括号的情况下调用函数的方法。你可以使用模版字符串来调用函数。模版字符串还支持占位符,可以嵌入js表达式。占位符可以使用${}定义,甚至支持嵌套的模版字符串。

alert `1337` 

`${alert(1337)}`
`${`${alert(1337)}`}`

  使用标记字符串时,一个字符串数组作为第一个参数传递给函数,如果没有占位符,这将是一个字符串,但如果有占位符,则字符串将被分隔。

alert `foobar` //foobar
alert `foo${1}bar` // foo,bar

  我们可以利用这个功能来评估代码,但是当使用eval时,代码不会被执行。

eval`alert\281337\x29`// alert不会被调用

  这是因为eval函数只返回一个数组,而不会将传递给它的参数转换为字符串。如果你使用另一个函数(比如setTimeout),它会将参数转换为字符串,那么这将正常工作:

setTimeout`alert\x281377\x29` //调用alert(1337)

  标记模版还有更多功能,如果你使用一个评估为字符串的占位符,它不会被添加到第一个参数的字符串数组中,而是会被用作第二个参数。

function x() {
console.log(arguments); // Arguments(4) [Array(4), 'foo', 'bar', 'baz',...
}
x${'foo'}${'bar'}${'baz'}

  可以使用这个机制来调用Function构造函数。Function构造函数接受多个参数,但如果你只提供一个参数,它将被用作函数的主题;如果你提供多个参数,最后一个参数将被用作函数的主体。这意味着我们可以使用第一个参数中的字符串数组,它将被转换为字符串并指定正在构造的函数的参数,而最后一个参数将使用占位符表达式的结果。

// 生成一个函数
Function`x${'alert\x281337\x29'}`
// 生成并调用该函数
Function`x${'alert\x281337\x29'}```

  让我们尝试滥用这个功能,首先是使用setTimeout。你可以使用3个参数来调用setTimeout,第一个参数是要调用的字符串或函数,第二个参数是在多少毫秒后调用该函数,第三个参数是发送给该函数的参数,前提是第一个参数是一个函数而不是字符串。

setTimeout`${alert}${0}${1337}` //不起作用

  这失败的原因是一个空的字符串数组作为第一个参数被发送,而不是我们的alert函数!我们需要找到一种不同的方式来执行任意的js代码。我们可以在这里使用call将字符串数组分配给函数的this值,这意味着第一个占位符将被用作setTimeout函数的第一个参数。

setTimeout.call`${alert}${0}${1337}` //不起作用,非法调用错误

  它不起作用,因为this值不是windows对象。如果我们尝试使用字符串而不是占位符,那么它将正常工作,因为只有字符串作为setTimeout函数的第一个参数传递,第二个和第三个参数将被省略,而this值将是windows对象。

setTimeout`alert\x281337\x29`
posted @ 2023-07-17 15:47  艾路  阅读(9)  评论(0编辑  收藏  举报