以太坊Solidity智能合约安全之短地址或参数攻击漏洞的原理及预防

漏洞原理

这个漏洞发生在第三方合约或应用程序调用智能合约时。当将参数传递给智能合约时,参数将根据 ABI 规范进行编码。第三方合约可以发送比预期参数长度短的编码参数(例如,发送一个只有 38 个十六进制字符,即19 个字节长度,而不是标准的 20个字节长度的地址时)。在这种情况下,EVM 将在编码参数的末尾填充 0,以弥补预期的长度。这样,当智能合约不验证输入时,这就会成为一个问题。最明显的例子是,当用户请求提款时,交易所不验证 ERC20 令牌的地址。

安全隐患

转账函数

考虑标准的 ERC20 转账函数接口,注意参数的顺序:

function transfer(address to, uint tokens) public returns (bool success);

EVM 将按照 transfer() 函数指定的顺序对这些参数进行编码,即先地址然后数量。恶意用户可以通过删除以 0 或多个 0 结尾的以太地址的最后一个 0 来构造这种攻击。

短地址

接收用户数据的智能合约允许小于 20 字节的以太坊地址。通常以太坊地址看起来像:

0xc3Bb35818d58FCA0C4943bA98938cb6F46A91700

如果去掉末尾的两个 0 (一个十六进制字节等于0) 会怎样?EVM 会接受它,但在打包的功能参数的末尾添加额外的零。从给出一个短参数的点开始,传递函数参数将移动一个字节,这将移动传递的令牌数量。

攻击过程

假设用户有 100 个代币,但是想要 25600 个,怎么做?

  1. 生成一个结尾为 0 的以太坊地址。以太坊地址几乎是随机生成的,所以平均需要 256 次尝试。虽然看似有难度,实际上还是有这个可能性的,因为生成地址不需要多少时间。
  2. 找到一个有 256,00 个代币的兑换钱包,比如交易所。
  3. 向这个交易所钱包发送 100 个代币,即在我的链外账户内部存入 100 个代币。
  4. 请求使用我生成的地址提取 1,00 个代币(去掉地址中最后一个 “0” 字节)。

然后会发生什么呢?简单地说,如果目标合约不验证地址,它将把所有内容 “打包” 在一起,并将数量 (最后一个参数) 移动到上一个字节上。当需要68个字节时,实际向传输函数传递了 67 个字节的参数。正确的打包数据长度应该是 68 个字节,其中前4个字节是 transfer() 函数签名,第二个是 32 个字节地址,后面的最后32个字节代表的是 uint256 的令牌数量(它有很多前导零)。

所有这些参数都在 msg.data 下传递调用,在这种攻击中所发生的是,前导零的一个字节被从数量中取出,并给缩短的地址。这将给我们留下与开始时相同的地址,所以发送到这里的代币将是可转账的。

当 EVM 检测到在处理 256 位的数据类型时发生下溢 (小于256位),则在地址的末尾追加 0。这意味着你的金额乘以了 1<<8256,最重要的是,交易所在他们的内部账本上检查了你的余额。

预防措施

一个比较简单的方法是在合约的转账函数中检查 msg.data 的长度是正确的大小 (68字节)。

contract NonPayloadAttackableToken {
    modifier onlyPayloadSize(uint size) { 
        assert(msg.data.length >= size + 4);
      _;
	}
	function transfer(address _to, uint256 _value) onlyPayloadSize(2 * 32) {
     	// todo
	}
}

猜你喜欢

转载自blog.csdn.net/zyq55917/article/details/124867222