这种攻击不是专门针对Solidity合约的,而是针对所有可能与合约互动的第三方DApp。
坑点分析
在参数传递给智能合约时,参数将根据ABI规范进行编码。发送短于预期参数长度的编码参数是可能的。
例如,发送一个只有19字节的地址,而不是标准的40个十六进制数20字节。在这种情况下,EVM会把0填充在编码参数的末尾,以补全预期的长度。
当第三方应用程序不验证输入时,这就成为一个问题。最明显的例子是,当用户请求提款时,不会验证ERC20代币的地址。
请想象一下标准的ERC20 transfer函数的接口(注意参数的顺序):
现在交易,一个用户持有大量的代币(如REP),希望提出其中的100个。用户将提交它们的地址:
0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead
以及提取代币数量100。
这时,交易会按照transfer函数指定的顺序编码这些参数,即先是address然后是tokens。编码的结果将是:
a9059cbb000000000000000000000000000000000000000000000000000000000000000000000000000056bc75e2d63100000
其中,前四个字节(a9059cbb)是transfer()函数的签名/选择器,第二个32字节是地址,最后的32个字节代表数据类型为uint256的代币。
请注意,末尾的十六进制
56bc75e2d63100000
相当于100个代币(根据REP代币合约的规定,小数点后有18位)。
好了,现在让我们看看如果发送一个缺少1个字节(2个十六进制数字)的地址会发生什么。具体来说,如果攻击者发送
0xdeaddeaddeaddeaddeaddeaddeaddeaddeadde
作为一个地址(缺少了末尾的两位数字),并同样发送取回100个代币的指令。如果这个兑换没有验证这个输入,它将被编码为:
a9059cbb000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddeadde0000000000000000000000000000000000000000000000056bc75e2d6310000000
请注意,00已经被填充到编码的末尾,补全了所发送的短地址。当它被发送到智能合约时,地址参数将被解读为:
0xdeaddeaddeaddeaddeaddeaddeaddeaddeadde00
同时,该值会被解读为:
56bc75e2d6310000000(注意这两个多出的0)。
这时,代币的价值已经变成了25,600,翻了256倍。也就是说,用户会提取25,600个代币(而交易所却认为用户只能取回100个)到修改后的地址。
避坑技巧
显而易见,在将所有输入发送到区块链之前进行验证,将会有效防止这类攻击。此外,参数排序在这里起着重要的作用。由于填充只发生在最后,智能合约中对参数的仔细排序可以防患于未然。
真实案例:未知
现在还没有发现相关的实际案例。