这种攻击并不是专门针对 Solidity 合约执行的,而是针对可能与之交互的第三方应用程序执行的。
一、漏洞
将参数传递给智能合约时,参数将根据 ABI 规范进行编码。可以发送比预期参数长度短的编码参数(例如,发送只有 38 个十六进制字符(19 个字节)的地址而不是标准的 40 个十六进制字符(20 个字节))。在这种情况下,EVM 会将 0 填到编码参数的末尾以补成预期的长度。
当第三方应用程序不验证输入时,这会成为问题。最明显的例子是当用户请求提款时,交易所不验证 ERC20 Token 的地址。Peter Venesses 的文章 “ERC20 短地址攻击解释”中详细介绍了这个例子。
考虑一下标准的 ERC20 传输函数接口,注意参数的顺序,
function transfer(address to, uint tokens) public returns (bool success);
现在考虑一下,一个交易所持有大量代(比方说 REP
),并且,某用户希望取回他们存储的100个代币。用户将提交他们的地址, 0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead
以及代币的数量 100
。交易所将根据 transfer()
函数指定的顺序对这些参数进行编码,即先是 address
然后是 tokens
。编码结果将是
a9059cbb000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddeaddead0000000000000000000000000000000000000000000000056bc75e2d63100000
提取出函数签名,剩余位数要以32字节分隔开:
a9059cbb
000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddeaddead
0000000000000000000000000000000000000000000000056bc75e2d63100000
前四个字节(a9059cbb)是 transfer()
函数签名/选择器;
第二个 32 字节是地址;
最后 32 个字节是表示代币数量的 uint256
。请注意,最后的十六进制数 56bc75e2d63100000
对应于 100 个代币(包含 18 个小数位,这是由 REP 代币合约指定的)。
好的,现在让我们看看如果我们发送一个丢失2个十六进制数的地址会发生什么。具体而言,假设攻击者以 0xdeaddeaddeaddeaddeaddeaddeaddeaddeadde
作为地址发送(缺少最后两位数字),并取回相同的 100
个代币。如果交易所没有验证这个输入,它将被编码为
a9059cbb000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddeadde0000000000000000000000000000000000000000000000056bc75e2d6310000000
提取出函数签名,剩余位数要以32字节分隔开:
a9059cbb
000000000000000000000000deaddeaddeaddeaddeaddeaddeaddeaddeadde00
00000000000000000000000000000000000000000000056bc75e2d6310000000
差别是微妙的。请注意, 00
已被填充到编码的末尾,以补完发送的短地址。当它被发送到智能合约时, address
参数将被读为 0xdeaddeaddeaddeaddeaddeaddeaddeaddeadde00
并且值将被读为 56bc75e2d6310000000
(注意两个额外的 0)。此值现在是 25600
个代币(值已被乘以 256
)。在这个例子中,如果交易所持有这么多的代币,用户会取出 25600 个代币(而交换所认为用户只是取出 100)到修改后的地址。很显然,在这个例子中攻击者不会拥有修改后的地址,但是如果攻击者产生了以 0 结尾的地址(很容易强制产生)并且使用了这个生成的地址,他们很容易从毫无防备的交易所中窃取令牌。
二、预防手段
我想很明显,在将所有输入发送到区块链之前对其进行验证可以防止这些类型的攻击。还应该指出的是参数排序在这里起着重要的作用。由于填充只发生在字符串末尾,智能合约中参数的缜密排序可能会缓解此攻击的某些形式。
网友评论