主页 > imtoken地址是什么 > 以太坊“后走私时代”“清道夫攻击”被盗币
以太坊“后走私时代”“清道夫攻击”被盗币
0x00 前言
2018 年 8 月 1 日以太坊节点收益,已知创宇 404 区块链安全研究团队发布了《钱难眠,盗贼独行——通过以太坊的 JSON-RPC 接口盗币的多种方式》《秘密》,介绍了走私漏洞和后走私时代的盗币方式,揭示了后走私时代的三种盗币方式:离线攻击、重放攻击和爆破攻击。
在进一步的研究中,我们发现了对这些攻击的补充:清除攻击。攻击者要么求助于矿工,要么拥有一定的算力,获得将交易打包成区块的权利。在走私漏洞中,攻击者在被攻击节点上构造一个gasPrice为0的交易,等待用户解锁账户进行广播签名。攻击者还设置了一个恶意节点来接收交易。攻击者可以将符合条件的交易打包,然后0手续费完成转账。通过这种攻击,攻击者可以获得足够的余额来支付转账费用或勉强支付费用节点上的所有以太币,在一定程度上可以阻止其他攻击者的竞争。
此外,在收集到足够多的 ETH 剩余物后,攻击者将目标锁定在被盗但仍留在账户中的代币上。直到现在,许多智能合约发行的代币,部分被攻击账户中的代币,仍然被攻击者通过扫雷攻击少量盗取。
本文将从零费用交易开始,模拟复制被盗币的实际过程,分析scavenger攻击成功的关键点。
0x01 以零费用交易开始
在区块链系统中,每笔交易都应附带一部分gas和对应的gasPrice作为交易费用。交易打包成区块,这笔费用将用于奖励完成打包的矿工。
在《钱难入眠,小偷独行——以太坊JSON-RPC接口盗币的各种方法的秘密》中,我们提到了一个使用以太坊JSON-RPC接口0x957cD4Ff9b3894FC78b5134A8DC72b032fFbC464的攻击者账户。攻击者扫描公共网络中开放的 RPC 端口,并构造高费用的交易请求。一旦用户解锁账户,用户余额将转入攻击者账户或攻击者创建的合约账户。
在分析该账户的交易信息时,我们发现了一笔不符合常理的交易。让我们从这个交易开始。
交易地址:0xb1050b324f02e9a0112e0ec052b57013c16156301fa7c894ebf2f80ac351ac22
Function: transfer(address _to, uint256 _value)
MethodID: 0xa9059cbb
[0]: 000000000000000000000000957cd4ff9b3894fc78b5134a8dc72b032ffbc464
[1]: 000000000000000000000000000000000000000000000000000000000abe7d00
从0x00a329c0648769a73afac7f9381e08fb43dbea72向合约MinereumToken(攻击者的合约)的交易,余额很少,但本次交易使用账户的全部余额作为价值与合约进行交互。此交易使用正常数量的 gas,但其 gasPrice 设置为 0。
如上所述,攻击者会使用更高的交易费用来保证他的交易成功。矿工会根据节点的 txpool 中每笔交易的 gasPrice 倒序排列交易,gasPrice 高的交易会先打包。堵塞。这个世界上无时无刻不在发生着无数的交易。在过去的 7 天里,一笔交易的最低 gasPrice 是 3Gwei。这个零费用交易是如何发生的,又是如何打包进区块的。
0x02 思想分析
在区块链系统中,任何人都可以加入区块链网络,成为节点之一,参与记账、挖矿等操作。保证区块链可信和去中心化的核心是共识机制。
共识机制
在以太坊中,矿工打包上一个区块的hash值、txpool中的高手续费交易、时间戳等数据,不断计算nonce进行挖矿,最先获得合格nonce值的矿工将有权记账,将获得手续费和挖矿奖励。矿工将广播获得的区块,其他节点将验证该区块。如果没有错误,则认为生成了一个新的块,并且区块链高度增加了。这是每个节点生成新区块以维持共识的过程。
完成 0 gasPrice 交易需要确认两个问题
我们来测试一下 0 gasPrice 交易相关的操作。了解零费用交易是如何产生的,txpool是如何接受的,是否可以识别包含零费用交易的区块,并确认以上问题的答案。
0x03 零费用交易测试 a.单节点测试
首先,我们来确认一下这个交易是否可以进入节点的交易池并启用一条测试链。默认 rpc 端口为 8545,使用 python 的 web3 包启动 0 gasPrice 传输。
geth --networkid 233 --nodiscover --verbosity 6 --ipcdisable --datadir data0 --rpc --rpcaddr 0.0.0.0 console
节点发起转账的脚本,转账前必须解锁账户
from web3 import Web3, HTTPProvider
web3 = Web3(HTTPProvider("http://localhost:8545/"))
print(web3.eth.accounts)
# 转帐前要解锁账户
web3.eth.sendTransaction({
"from":web3.eth.accounts[0],
"to":web3.eth.accounts[1],
"value": 10,
"gas":21000,
"gasPrice":0,
})
互动结果
> txpool.content
{
pending: {},
queued: {}
}
> eth.getBalance(eth.accounts[0])
800000000
> personal.unlockAccount(eth.accounts[0],'sissel')
true
> INFO [08-14|11:20:14.972] Submitted transaction fullhash=0x72e81751d2517807cabad24102d3cc2f0f4f2e8b92f1f106f1ee0bf6be734fe4 recipient=0x92636b228148e2824cB8d472Ef2F4e76f2F5059C
> txpool.content
{
pending: {
0x092fda221a114FA702e2f59C217C92cfEB63f5AC: {
3: {
blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
blockNumber: null,
from: "0x092fda221a114fa702e2f59c217c92cfeb63f5ac",
gas: "0x5208",
gasPrice: "0x0",
hash: "0x72e81751d2517807cabad24102d3cc2f0f4f2e8b92f1f106f1ee0bf6be734fe4",
input: "0x",
nonce: "0x3",
r: "0x1eca20e3f371ed387b35ca7d3220789399a3f64c449a825e0fa7423b96ce235c",
s: "0x35a58e5cb5027c7903c1f1cc061ae846fb5150186ebbabb2b0766e4cbfc4aee6",
to: "0x92636b228148e2824cb8d472ef2f4e76f2f5059c",
transactionIndex: "0x0",
v: "0x42",
value: "0xa"
}
}
},
queued: {}
}
> miner.start(1)
INFO [08-14|11:20:35.715] Updated mining threads threads=1
INFO [08-14|11:20:35.716] Transaction pool price threshold updated price=18000000000
null
INFO [08-14|11:20:35.717] Starting mining operation
> INFO [08-14|11:20:35.719] Commit new mining work number=115 txs=1 uncles=0 elapsed=223µs
> mINFO [08-14|11:20:36.883] Successfully sealed new block number=115 hash=ce2f34…210039
INFO [08-14|11:20:36.885] ? block reached canonical chain number=110 hash=2b9417…850c25
INFO [08-14|11:20:36.886] ? mined potential block number=115 hash=ce2f34…210039
INFO [08-14|11:20:36.885] Commit new mining work number=116 txs=0 uncles=0 elapsed=202µs
> miner.stop()
true
> eth.getBalance(eth.accounts[0])
799999990
节点1发起的零手续费交易成功,交易挖矿成功打包入块。
b.多节点共识测试
现在加入另一个节点
geth --datadir "./" --networkid 233 --rpc --rpcaddr "localhost" --port 30304 --rpcport "8546" --rpcapi "db,eth,net,web3" --verbosity 6 --nodiscover console
使用这些方法添加节点
> admin.nodeInfo
> admin.addPeer()
> admin.peers
节点一仍然使用之前的脚本发起零费用交易,成功加入节点一的txpool,但交易因gasPrice被节点二非法拒绝。
TRACE[08-15|10:09:24.682] Discarding invalid transaction hash=3902af…49da03 err="transaction underpriced"
> txpool.content
[]
在geth的配置中发现了一个与此相关的参数
--txpool.pricelimit value 强制执行的最小gas价格限制以接受到池中(默认值:1)
启动时改为0,但交易还没有出现在节点2的txpool中。
看源码,这个参数确实是控制交易池添加的交易的最小gasPrice,但不能小于1。
if conf.PriceLimit < 1 {
log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultTxPoolConfig.PriceLimit)
conf.PriceLimit = DefaultTxPoolConfig.PriceLimit
}
让节点1(txpool中的gasPrice为0)开始挖矿,将交易打包成区块后,发现节点2已经批准了该区块,达成共识以太坊节点收益,两个节点的高度都增加了。
结论:
我们将进行简短的源代码分析以支持我们的结论。
0x04源码分析
(以下代码分析基于当前最新提交:commit 6d1e292eefa70b5cb76cd03ff61fc6c4550d7c36)
目前以太坊中最流行的节点程序(Geth/Parity)提供了 RPC API 用于连接其他第三方程序,例如矿池和钱包。首先确认一下节点打包txs时的代码实现。
我。交易池
代码路径:./go-ethereum/core/tx_pool.go
// TxPool contains all currently known transactions. Transactions
// enter the pool when they are received from the network or submitted
// locally. They exit the pool when they are included in the blockchain.
type TxPool struct {
config TxPoolConfig
chainconfig *params.ChainConfig
chain blockChain
gasPrice *big.Int //最低的GasPrice限制
/*
其他参数
*/
}
在生成tx实例时,发现gasPrice有最低要求,在这个函数中会拒绝交易。
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// 在这里是gasPrice的校验
if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
return ErrUnderpriced
}
/*
...
*/
return nil
}
二。删除低于阈值的交易
代码路径:./go-ethereum/core/tx_list.go,在处理txs时,低于阈值的交易将被删除,但本地交易不会被删除。
// Cap finds all the transactions below the given price threshold, drops them
// from the priced list and returs them for further removal from the entire pool.
func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transactions {
drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop
save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep
for len(*l.items) > 0 {
// Discard stale transactions if found during cleanup
tx := heap.Pop(l.items).(*types.Transaction)
if _, ok := (*l.all)[tx.Hash()]; !ok {
// 如果发现一个已经删除的,那么更新states计数器
l.stales--
continue
}
// Stop the discards if we've reached the threshold
if tx.GasPrice().Cmp(threshold) >= 0 {
// 如果价格不小于阈值, 那么退出
save = append(save, tx)
break
}
// Non stale transaction found, discard unless local
if local.containsTx(tx) { //本地的交易不会删除
save = append(save, tx)
} else {
drop = append(drop, tx)
}
}
for _, tx := range save {
heap.Push(l.items, tx)
}
return drop
}
以上部分是区块链网络中的一个节点。当试图接收或加入一个 gasPrice 为 0 的交易时,会受到一些过滤或规则限制。但是,通过修改源代码,我们仍然可以合法地将 0 gasPrice 交易添加到区块中,并进行后续的 nonce 计算。我们继续源码分析,看看这样得到的block。是否能被其他节点接受并达成共识。
三。共识验证
代码路径:./go-ethereum/consensus/consensus.go 这是geth中提供的共识算法引擎接口
type Engine interface {
// 签名
Author(header *types.Header) (common.Address, error)
/*
验证了header、seal,处理难度等函数
...
*/
// 预处理区块头信息,修改难度等
Prepare(chain ChainReader, header *types.Header) error
// 区块奖励等,挖掘出区块后的事情
Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error)
// 计算nonce,若收到更高的链,则退出
Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error)
// 计算难度值
CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int
// APIs returns the RPC APIs this consensus engine provides.
APIs(chain ChainReader) []rpc.API
// Close terminates any background threads maintained by the consensus engine.
Close() error
}
查看VerifySeal(),发现验证了以下内容:
可以看到,其他节点已经对共识的签名、nonce等内容进行了校验,但是对于零手续费的交易,没有进行验证。换句话说,零费用交易不会激励矿工,但它仍然是合法的。
0x05 开发过程
攻击者首先利用走私漏洞构造一个零费用、正常的转账交易。用户解锁账户后,广播交易。具体流程如下图所示:
0x06 总结
由此我们可以得出0gasPrice等特殊交易如下结论:
通常情况下,0 gasPrice 可以通过节点本身添加到 txpool 中。
以geth为例,修改geth的部分源码重新编译运行,节点可以接受其他节点发送的特殊交易(目标账户发起的0gasPrice交易)。这是攻击者需要做的。
0gasPrice 的交易可以打包成区块,满足共识要求。
由于json-rpc接口的攻击方式,攻击者可以通过走私漏洞对0gasPrice交易进行签名并广播。通过收集这样的 0gasPrice 交易并将其添加到一些矿工的 txpool 中,当矿工挖出一个新区块时,这些交易也会被打包。即攻击者可能与部分矿工联手,或者攻击者自身具有一定的算力,使矿工不再遵循诚实挖矿的原则来维护区块链系统,
0x07 使用价值及防御计划
因为零费用交易的出现,很多低收益的攻击也就有了意义。
增加收入
这样,攻击者就可以利用这种方式,结合其他攻击方式,将被攻击账户中的余额全部转出,实现收益最大化。
羊毛用完了
根据《钱难眠,盗贼独行——揭开以太坊JSON-RPC接口各种盗币方法的秘密》中提到的攻击方式,针对账户余额较小的情况,甚至不足以支付转账费用,账户中的碎片可以通过上述抓绒攻击方案收取。由于该交易的gasPrice为0,因此该类型的多笔交易可以同时打包在一个区块中,例如该合约下的多组交易:0x1a95b271b0535d15fa49932daba31ba612b52946,该区块中的多笔交易:4788940
被盗代币
在被盗账户没有以太币的情况下,攻击者发现这些账户中还有一些智能合约发行的代币。没有以太币,你无法支付gas来转账,而零费用交易可以完美解决这个问题。直到现在,还有很多没有以太币的被攻击账户,还在用这种方式转移代币。
防御计划
由于0gasPrice交易只是扩展其他攻击方案的一种方式,所以防御上也要重点使用之前的json-rpc接口。
0x08 影响量表
我们从上面提到的 0 gasPrice 交易开始。调查发现,近期仍有不少交易为0gasPrice。大部分零费用交易来自矿池:0xb75d1e62b10e4ba91315c4aa3facc536f8a922f5 和 0x52e44f279f4203dcf680395379e5f9990a69f13c,如区块6161214、6160889等
我们注意到,这些 0 gasPrice 交易,只是少量的早期交易,携带的 ETH 会更少,这符合我们对其毛茸茸的特性的预期。据统计,自2017年6月以来,已零手续费转账748个账户,共计24.2eth。
在里面我还发现了《钱难入眠,大盗独行——以太坊JSON-RPC接口多重盗币技术之秘》中提到的replay攻击,导致账号丢失:0x682bd7426ab7c7b4b5beed331d5f82e1cf2cecc83c317ccee6b4c4f1ae34d909
被盗0.05eth
在这0 gasPrice 下,更多的是对合约发出的TOKEN的转账请求,将用户账户中的token转入合约所有者账户,例如:
账户的交易记录。
攻击者拥有多个矿池的算力,将多个被攻击账户所拥有的各种代币转移到对应账户中,虽然单笔交易量很小,但是有很多账户和合约可以进行这种攻击方式,无需手续费。几乎没有加起来,直到现在,攻击者仍在对这些令牌进行清除攻击。
0x09 结论
基于去中心化的区块链系统共识可以达成交易,前提是绝大多数矿工将通过诚实的挖矿系统维护整个比特币。当矿工不再诚实时,区块链的可信度和去中心化将大大降低。当黑客与矿工联合,或者当黑客成为有算力的矿工时,他们会在现有攻击手段的基础上提供更多扩展的攻击方案。 0 gasPrice 交易的出现违背了区块链设计的初衷,即矿工应该支付费用作为激励。区块链技术和虚拟货币的普及,赋予了链上货币巨大的经济价值,每个人都想在区块链浪潮中分一杯羹。对于黑客来说尤其如此。作为窃贼,他们绞尽脑汁从各个角度攻击区块链和合约。黑客住在矿机里,不仅可以挖区块,还可以找漏洞。
欢迎关注我和专栏,我会定期携带技术文章~
也欢迎来访:知乎创宇云安全:
或拨打热线:400-833-1123
如果你想和我做朋友,欢迎加微信kcsc818~