JavaScript 中的渗透与安全
前提
你确定你的代码能按预期使用吗?你能防止它被恶意利用吗?
如果你打算在函数中添加守卫,那么这篇文章会为你打开一个新世界,就像这篇文章为我打开了一个新世界一样。仅仅使用检查是不够的。
指数
你既是狼,又是羊。我创建了下面的函数,以便它包含你学习攻击和相关防御技术所需的一切:
- 探测和双吸气器
- 原型贿赂
- 原始幻觉
该函数为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}]`
)
}
}
不要关注
_err
功能。这在这里不重要。
幸福之路如下:
const c = Connector({ address: 'investments' })
// Connection to address [investments] enstablished
c.transfer(300)
//Transfered an amount of [300] to the address [investments]
探测和双吸气器
攻击
address
假设您是该脚本的恶意用户。您想向未包含在 中的账户发送一笔钱validAddresses
。
正面进攻明显被挡了下来。
Connector({ address: 'malicious' })
// The address malicious is not valid. Valid ones are: partner-account, investments, mutual
请记住,在冒充黑客时您并不知道代码的实现!
可以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
很清楚。只需更改第五次读数即可;其有效性已在前四次读数中验证。使用双吸气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]
由于这项技术,您已经有效地绕过了初始化阶段的保护。
防御
问题是address
会被反复访问。即使两次也太多了。
但如果只有一个,双吸器就无法骗过守卫。
要访问address
一次,只需将其复制到变量即可。由于它是string
原始的——新变量是单独的副本,没有 getter。
在ES6中,你可以使用解构:
function Connector({ address }) { ... }
运行探测器,发现它实际上只发出一声哔哔声。双重吸气者的威胁已被消除。
原型贿赂
攻击
你得想办法潜入代码。但他们筑起了高墙——我们需要一个潜入者,一个来自内部的人,暂时假装没看见。
函数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]
只有在初始化阶段才会无差别地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
}
我们之前使用的技巧这次将不再起作用,并且_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]
你又一次成功躲过了守卫。
原始幻觉
实例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}]`
)
}
原始幻觉技术实现了与双吸气器类似的效果,但方式有所不同。DG 技术的一个限制实际上是它仅适用于通过引用传递的变量。例如,尝试将其实现为原始类型。Number
我发现修改 更实用Number.prototype.valueOf
。你可能永远不需要直接调用这个方法。JavaScript本身会在需要检索对象(在本例中为 )的原始值时调用它。以下示例更直观:Number
Number.prototype.valueOf = () => {
console.count('probe')
return this
}
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
正如你猜测的那样,该valueOf
方法会在预期的时间被调用primitive value
——就像数学运算一样。此时,剩下的就是将探针插入到方法中transfer
。
c.transfer(number)
// probe: 1
// probe: 2
// Transfered an amount of [42] to the address [hacker-address]
探测器的两个日志在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]
再次,你成功得到了你想要的东西。
如果你想聊聊书呆子的事情或者只是打个招呼,你可以在这里找到我:
文章来源:https://dev.to/didof/penetration-and-security-in-javascript-probing-double-getter-p47