JavaScript 中的渗透与安全

2025-05-25

JavaScript 中的渗透与安全

前提

你确定你的代码能按预期使用吗?你能防止它被恶意利用吗?

如果你打算在函数中添加守卫,那么这篇文章会为你打开一个新世界,就像这篇文章为我打开了一个新世界一样。仅仅使用检查是不够的

指数

你既是,又是。我创建了下面的函数,以便它包含你学习攻击和相关防御技术所需的一切:

  1. 探测和双吸气器
  2. 原型贿赂
  3. 原始幻觉

该函数为Connector,它接收一个options配置对象。该对象必须包含一个名为 的属性,address该属性必须与 中列出的属性之一相同validAddresses,否则会抛出异常。

addresses一旦与有效的其中一个建立了连接,实例就会提供transfer方法来移动作为输入传递的某个amount值,该值不得超过该值500

function Connector(options) {
  const validAddresses = ['partner-account', 'investments', 'mutual']

  if (!options.address || typeof options.address !== 'string') _err1()

  if (!validAddresses.includes(options.address)) _err2(options, validAddresses)

  console.info(`Connection to address [${options.address}] enstablished`)

  return {
    transfer,
  }

  function transfer(amount) {
    if (!amount || amount <= 0) _err3()

    if (amount > 500) _err4()

    console.info(
      `Transfered an amount of [${amount}] to the address [${options.address}]`
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

不要关注_err功能。这在这里不重要。

幸福之路如下

const c = Connector({ address: 'investments' })
// Connection to address [investments] enstablished

c.transfer(300)
//Transfered an amount of [300] to the address [investments]
Enter fullscreen mode Exit fullscreen mode

探测和双吸气器

攻击

address假设您是该脚本的恶意用户。您想向未包含在 中的账户发送一笔钱validAddresses

正面进攻明显被挡了下来。

Connector({ address: 'malicious' })
// The address malicious is not valid. Valid ones are: partner-account, investments, mutual
Enter fullscreen mode Exit fullscreen mode

请记住,在冒充黑客时您并不知道代码的实现!

可以address提前发送一个有效值,并计算其被访问的次数。这样你就能知道什么时候该把它转换成地址了——ZACmalicious

建立一个探测器

let i = 0
const probe = {
  get address() {
    console.count('probe')
    return 'investments'
  },
}

const c = Connector(probe)
// probe: 1
// probe: 2
// probe: 3
// probe: 4
// Connection to address [investments] enstablished

c.transfer(300)
// probe: 5
Enter fullscreen mode Exit fullscreen mode

很清楚。只需更改第五次读数即可;其有效性已在前四次读数中验证。使用双吸气address技术即可

let i = 0
const doubleGetter = {
  get address() {
    if (++i === 5) return 'malicious'
    return 'investments'
  },
}

const c = Connector(doubleGetter)
// Connection to address [investments] enstablished

c.transfer(300)
// Transfered an amount of [300] to the address [malicious]
Enter fullscreen mode Exit fullscreen mode

由于这项技术,您已经有效地绕过了初始化阶段的保护。

防御

问题是address会被反复访问。即使两次也太多了。
但如果只有一个,双吸器就无法骗过守卫

要访问address一次,只需将其复制到变量即可。由于它是string原始——新变量是单独的副本,没有 getter。

ES6中,你可以使用解构

function Connector({ address }) { ... }
Enter fullscreen mode Exit fullscreen mode

运行探测器,发现它实际上只发出一声哔哔声双重吸气者的威胁已被消除。


原型贿赂

攻击

你得想办法潜入代码。但他们筑起了高墙——我们需要一个潜入者,一个来自内部的人,暂时假装没看见。

函数includes就是你的人。贿赂它很简单:

const includesBackup = Array.prototype.includes

// bribe it...
Array.prototype.includes = () => true

const c = Connector({ address: 'malicious' })
// Connection to address [malicious] enstablished

// ...and immediately everything in the norm
Array.prototype.includes = includesBackup

c.transfer(300)
// Transfered an amount of [300] to the address [malicious]
Enter fullscreen mode Exit fullscreen mode

只有在初始化阶段才会无差别地includes返回true。判别式守卫validAddresses.include(address)实际上被蒙蔽了,然后就malicious address可以傲慢地从前门进入。

防御

在 周围拉了一堵墙Connector,这就是一个块作用域。在这个作用域内,你需要拥有一个Array.prototype.includes不被外部破坏的 副本,并且只使用这一个。

{
  const safeIncludes = Array.prototype.includes

  function Connector({ address }) {
    const validAddresses = ['partner-account', 'investments', 'mutual']

    ...

    const isValidAddress = safeIncludes.bind(validAddresses)
    if (!isValidAddress(address)) _err2(address, validAddresses)

    ...
  }

  global.Connector = Connector // window if browser
}
Enter fullscreen mode Exit fullscreen mode

我们之前使用的技巧这次将不再起作用,并且_err2将被抛出。

攻击

只要稍微耍点小聪明,就能腐蚀includes主管。这bind……
我建议保留一份腐蚀函数的副本,以便在违规行为发生后立即纠正。

const includesBackup = Array.prototype.includes
const bindBackup = Function.prototype.bind

Array.prototype.includes = () => true
Function.prototype.bind = () => () => true

const c = Connector({ address: 'malicious' })
// Connection to address [malicious] enstablished

Array.prototype.includes = includesBackup
Function.prototype.bind = bindBackup

c.transfer(300)
// Transfered an amount of [300] to the address [malicious]
Enter fullscreen mode Exit fullscreen mode

你又一次成功躲过了守卫


原始幻觉

实例Connector提供了transfer方法。该方法需要amount一个数字作为参数,并且为了确保转账成功,该参数的值不得超过允许值500。假设我已经成功与我选择的某人建立了联系address。此时,我想转账的金额要高于允许金额。

// Connector#transfer
function transfer(amount) {
  if (!amount || amount <= 0) _err3()

  if (amount > 500) _err4()

  console.info(
    `Transfered an amount of [${amount}] to the address [${options.address}]`
  )
}
Enter fullscreen mode Exit fullscreen mode

原始幻觉技术实现了与双吸气器类似的效果,但方式有所不同。DG 技术的一个限制实际上是它仅适用于通过引用传递的变量。例如,尝试将其实现为原始类型Number

我发现修改 更实用Number.prototype.valueOf。你可能永远不需要直接调用这个方法。JavaScript本身会在需要检索对象(在本例中为 )的原始值时调用它。以下示例更直观:Number

Number.prototype.valueOf = () => {
  console.count('probe')
  return this
}
Enter fullscreen mode Exit fullscreen mode

this在这种情况下,Number表示与构造函数中传递的数字相同的数字。

你可能已经认出来了,它是一个探针。你可以在一个实例上测试不同的操作Number

const number = new Number(42)

console.log(number)
// [Number: 42]

console.log(+number)
// probe: 1
// 42

console.log(number > 0)
// probe: 2
// true
Enter fullscreen mode Exit fullscreen mode

正如你猜测的那样,该valueOf方法会在预期的时间被调用primitive value——就像数学运算一样。此时,剩下的就是将探针插入到方法中transfer

c.transfer(number)
// probe: 1
// probe: 2
// Transfered an amount of [42] to the address [hacker-address]
Enter fullscreen mode Exit fullscreen mode

探测器的两个日志在amount <= 0和 中精确对应amount> 500。此时,您意识到您不需要在某个时刻将 值交换为另一个值 - 您只需要在valueOf调用 时返回一个满足上述条件的值即可。

Number.prototype.valueOf = () => 1
const number = new Number(100000)

c.transfer(number)
// Transfered an amount of [100000] to the address [hacker-address]
Enter fullscreen mode Exit fullscreen mode

再次,你成功得到了你想要的东西。


如果你想聊聊书呆子的事情或者只是打个招呼,你可以在这里找到我:

文章来源:https://dev.to/didof/penetration-and-security-in-javascript-probing-double-getter-p47
PREV
我如何学习机器学习(无需数学天才)
NEXT
干净代码架构的特点 | Node.js