凡心所向,素履所往

ETH 漏洞复现

字数统计: 2.8k阅读时长: 12 min
2019/03/14 Share

[TOC]

ETH

系统环境 ubuntu 16

搭建私有测试链

  • 下载相关的go-ethereum 协议

git clone https://github.com/ethereum/go-ethereum.git

  • 安装go环境

wget https://dl.google.com/go/go1.12.linux-amd64.tar.gz

tar -C /usr/local -xzf go1.12.linux-amd64.tar.gz

修改配置环境
vim ~/.bashrc

export GOROOT=/usr/local/go
export GOPATH=/home/go_demo
export PATH=$PATH:$GOPATH:/usr/local/go/bin
  • make geth
  • 连接测试网络

因为仅仅是作为测试链来使用,这里就不连接到主网了,毕竟同步数据会消耗长时间和硬盘。

./build/bin/geth –testnet console

  • 开启json-rpc 端口

    geth –rpc –rpcaddr –rpcport

  • RPC常用调用格式
cur addr:port -X POST --data '{"jsonrpc":"2.0","id":id, "method":"${method}","params":"${params}"}'

创建私有链-待续

部署测试合约-待续

合约创建子链-待续


ETH 节点JSON-RPC测试

RPC接口:

    web3_clientVersion


    web3_sha3
    net_version
    net_peerCount
    net_listening
    eth_protocolVersion
    eth_syncing
    eth_coinbase
    eth_mining
    eth_hashrate
    eth_gasPrice
    eth_accounts
    eth_blockNumber
    eth_getBalance
    eth_getStorageAt
    eth_getTransactionCount
    eth_getBlockTransactionCountByHash
    eth_getBlockTransactionCountByNumber
    eth_getUncleCountByBlockHash
    eth_getUncleCountByBlockNumber
    eth_getCode
    eth_sign
    eth_sendTransaction
    eth_sendRawTransaction
    eth_call
    eth_estimateGas
    eth_getBlockByHash
    eth_getBlockByNumber
    eth_getTransactionByHash
    eth_getTransactionByBlockHashAndIndex
    eth_getTransactionByBlockNumberAndIndex
    eth_getTransactionReceipt
    eth_getUncleByBlockHashAndIndex
    eth_getUncleByBlockNumberAndIndex
    eth_getCompilers
    eth_compileLLL
    eth_compileSolidity
    eth_compileSerpent
    eth_newFilter
    eth_newBlockFilter
    eth_newPendingTransactionFilter
    eth_uninstallFilter
    eth_getFilterChanges
    eth_getFilterLogs
    eth_getLogs
    eth_getWork
    eth_submitWork
    eth_submitHashrate
    db_putString
    db_getString
    db_putHex
    db_getHex
    shh_post
    shh_version
    shh_newIdentity
    shh_hasIdentity
    shh_newGroup
    shh_addToGroup
    shh_newFilter
    shh_uninstallFilter
    shh_getFilterChanges
    shh_getMessages

创建钱包

启动节点后,调用节点自身的console,创建账号

> eth.accounts

查询钱包用户

> personal.newAccount()

创建钱包用户
这里注意下,当一个节点绑定多个钱包账户的时候,挖矿的收益会默认在第一个创始账户中,这个时候要改变收益账号

> miner.setEtherbase(eth.accounts[1])
成功改变会返回 true

查询获益钱包账户

> eth.coinbase
>personal.unlockAccount(address,passwd,time)

解锁钱包用户

查询余额

curl -X POST -H "Content-Type":application/json --data '{"jsonrpc":"2.0", "method":"eth_getBalance","params":["0xde1e758511a7c67e7db93d1c23c1060a21db4615","latest"],"id":67}' 172.25.0.10:8545

发起转账请求

curl -X POST -H "Content-Type":application/json --data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params": [{                                                                               
  "from": "0xde1e758511a7c67e7db93d1c23c1060a21db4615",
  "to": "0xd64a66c28a6ae5150af5e7c34696502793b91ae7",
  "value": "0x1"
}],
"id":67}' 172.25.0.10:8545

但是注意一下在此处的 当余额为0的时候,会报错,报错信息如下:

"error":{"code":-32000,"message":"exceeds block gas limit"}

待深究

其中 personal_listWallets 接口可以查看所有账户的解锁情况。

偷渡漏洞验证

以太坊交易流程

用户发起转账请求。

以太坊对转账信息进行签名

校验签名后的信息并将信息加入交易缓存池 (txpool)

从交易缓存池中提取交易信息进行广播

此处的核心便是对personal_unlockAccount的爆破攻击,在爆破成功后或者用户自身交易解锁的空余时间内,对该接口进行解锁,在解锁后,攻击者便调用转账函数,对该接口以较高的gas进行转账处理。

防范方法:

 personal.sendTransaction 进行转账

关于 personal.sendTransaction与personal_unlockAccount对区别
这也就意味着如果执行了 unlockAccount() 函数、没有超时的话,从 ipc、rpc 调用 SendTransaction() 都会成功签名相关交易

偷渡漏洞接口爆破

爆破的目标
personal_unlockAccount 接口变成了 personal_sendTransaction 接口

开启挖矿

curl -X POST -H "Content-Type":application/json --data '{"jsonrpc":"2.0", "method":"miner_start","params":[],"id":67}' 172.25.0.10:8545

或者 用本地console

miner.start(1)

漏洞复现

这里用docker 环境下的python3.6
docker run -it python:3.6 /bin/bash

探测节点端口是否开放、获取当前区块高度

直接调用RPC 8545端口

>>> from web3 import Web3,HTTPProvider
>>> web3=Web3(HTTPProvider("http://xx.xx.xx.xx:8545/"))
>>> web3.eth.blockNumber
探测节点上是否绑定有相关账户
>>> web3.eth.accounts
查询下账户相关余额
>>> web3.eth.getBalance(web3.eth.accounts[])
调用RPC爆破账户
轮询监控节点解锁账户
解锁期间进行转账

没什么可写的,就是用个循环,调用web3.eth.sendTransaction 不停的进行转账请求

离线状态下偷渡漏洞

跟偷渡漏洞实质没有区别,只是区别在于,不是在解锁期间进行转账,是在解锁期间调用转账签名eth_signTransaction,在账户解锁期间按照 nonce 递增的顺序构造多笔转账的签名,然后再在攻击者自己的节点上根据签名返回的raw 对签名进行广播,eth.sendRawTransaction(raw),达到转账的目的


轻节点的拒绝服务漏洞-待分析

  • 知识简介:
    同步区块数据中,P2P模式,也分为客户端和服务端,在这其中,获取数据的是客户端,推送数据的属于服务端。

漏洞分析

漏洞成因

漏洞主要利用的是轻节点协议LES协议,复现前先缕一下这个函数及其功能。

LES协议

全称:Light Ethereum Subprotocol
以太坊三层协议中的上层协议

  • 层1 以太坊应用层协议: eth协议、les协议
  • 层2 p2p 通信链路
  • 层3 go语言的网络IO层

漏洞复现

les/handler.go

            case query.Origin.Hash != (common.Hash{}) && !query.Reverse:
                // Hash based traversal towards the leaf block
                if header := pm.blockchain.GetHeaderByNumber(origin.Number.Uint64() + query.Skip + 1); header != nil {
                    if pm.blockchain.GetBlockHashesFromHash(header.Hash(), query.Skip+1)[query.Skip] == query.Origin.Hash {
                        query.Origin.Hash = header.Hash()
                    } else {
                        unknown = true
                    }
                } else {
                    unknown = true
                }
            case query.Reverse:
                // Number based traversal towards the genesis block
                if query.Origin.Number >= query.Skip+1 {
                    query.Origin.Number -= (query.Skip + 1)
                } else {
                    unknown = true
                }

            case !query.Reverse:
                // Number based traversal towards the leaf block
                query.Origin.Number += (query.Skip + 1)
            }
        }

        bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + query.Amount*costs.reqCost)
        pm.server.fcCostStats.update(msg.Code, query.Amount, rcost)
        return p.SendBlockHeaders(req.ReqID, bv, headers)

eth/handler.go 365-385 此处已修复

        case query.Origin.Hash != (common.Hash{}) && !query.Reverse:
                // Hash based traversal towards the leaf block
                var (
                    current = origin.Number.Uint64()
                    next    = current + query.Skip + 1
                )
                if next <= current {
                    infos, _ := json.MarshalIndent(p.Peer.Info(), "", "  ")
                    p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos)
                    unknown = true
                } else {
                    if header := pm.blockchain.GetHeaderByNumber(next); header != nil {
                        if pm.blockchain.GetBlockHashesFromHash(header.Hash(), query.Skip+1)[query.Skip] == query.Origin.Hash {
                            query.Origin.Hash = header.Hash()
                        } else {
                            unknown = true
                        }
                    } else {
                        unknown = true
                    }
                }

SetString导致的OOM拒绝服务

漏洞分析

根据分析,go语言中在使用math.big.Rat.SetString的时候,如果对传入的需要转换的函数没有相应的检测的时候,在传入其值为一个很大的浮点数的字符串的时候,就可能导致CPU耗尽的拒绝服务。
big.NewInt(0).SetString同样存在

测试样例

这里简单调用下该模块,检查其对传入的很大的浮点型字符串时的处理

package main
import (
    "fmt"
    "math/big"
    "os"
)
func  Test(test string) () {
    num, success := new(big.Rat).SetString(test);
    if success {
        fmt.Print(num);
    }
}
func main(){
    fmt.Println(os.Args)
    for i := 0; i < len(os.Args); i++ {
        fmt.Println(os.Args[i])
    }
    Test(os.Args[1]);

}

测试结果图:
-w1256
此处可以明显看到通过OS传入的浮点型参数1e111111111,此时CPU升到100%

不过当浮点数

func main(){
    type txdata struct {
        AccountNonce string `json:"nonce"`
        Price        string `json:"gasPrice"`
        GasLimit     string `json:"gas"`
        Recipient    string `json:"to"`
        Amount       string `json:"value"`
        Payload      string `json:"input"`
    }
    var dec txdata

    data := []byte("{\"nonce\":\"2\",\"gasPrice\":1000000000000000000,\"gas\":\"50000\",\"to\":\"0x3fac0b8a3d21f8565b7446c6cc9e932badfb186c\",\"value\":\"20000\",\"input\":\"0x\"}")
    if err := json.Unmarshal(data, &dec); err != nil {
        fmt.Println(err);
    }
    price := big.NewInt(0)
    price.SetString(dec.Price, 0);
}
漏洞分析

看过测试用例后,这里我们实际分析一个最近看到的公链上漏洞。

  • 定位漏洞
    这里根据代码审计中最简单的漏洞定位方法,因为知道了math.big.SetString存在问题,这里就去寻找,然后察看是否有可疑参数传入
    这里我们定位到了
    go-ethernum/cmd/geth/accountcmd.go 文件中
func signTx(ctx *cli.Context) error {
    prikey := ctx.Args().First()
    if len(prikey) == 0 {
        fmt.Println("{error1}")
        return errors.New("no prikey")
    }
    key, err := crypto.LoadECDSA(prikey)
    if err != nil {
        fmt.Println("{error2}")
        return err
    }
    jsonstr := ctx.GlobalString(utils.PasswordFileFlag.Name)
    log.Info(jsonstr)
    type txdata struct {
        AccountNonce string `json:"nonce"`
        Price        string `json:"gasPrice"`
        GasLimit     string `json:"gas"`
        Recipient    string `json:"to"`
        Amount       string `json:"value"`
        Payload      string `json:"input"`
    }
    var dec txdata
    if err := json.Unmarshal([]byte(jsonstr), &dec); err != nil {//go的json解析
        fmt.Println("{error3}")
        return err
    }
    nonce, err := strconv.ParseUint(dec.AccountNonce, 0, 64)
    if err != nil {
        fmt.Println("{error4}")
        return err
    }
    amount := big.NewInt(0)
    amount.SetString(dec.Amount, 0)
    price := big.NewInt(0)
    price.SetString(dec.Price, 0)
    gas := big.NewInt(0)
    gas.SetString(dec.GasLimit, 0)
    tx := types.NewTransaction(nonce, common.HexToAddress(dec.Recipient), amount, gas, price, []byte(dec.Payload))
    s := types.NewEIP155Signer(big.NewInt(15))
    sign, err := types.GetRowTransaction(s, tx, key)
    if err != nil {
        fmt.Println("{error5}")
        return err
    }
    fmt.Println("{" + sign + "}")

简单的看了下,此处是对以太坊交易的功能,稍微理下流程:
转账请求:

进行请求的时候,可以有三种方式:
调用JSON-RPC

curl -X POST -H "Content-Type":application/json --data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params": [{                                                                               
  "from": "0xde1e758511a7c67e7db93d1c23c1060a21db4615",
  "to": "0xd64a66c28a6ae5150af5e7c34696502793b91ae7",
  "value": "0x1"
}],
"id":67}' 172.25.0.10:8545

调用js console

eth.sendTransaction({"from":eth.accounts[0],to:eth.accounts[1],value:web3.toWei(10,'ether')})

调用web3.js ,实质还是调用了js console API

web3.eth.sendTransaction("from":eth.accounts[0],to:eth.accounts[1],value:web3.toWei(10,'ether')})

流程如下:

  • 用户输入转账的地址和转入的地址和转出的金额
  • 系统通过转出的地址的私钥对转账信息进行签名(用于证明这 笔交易确实有本人进行)
  • 系统对交易信息进行验证
  • 把这笔交易入到本地的txpool中(就是缓存交易池)
  • 把交易信息广播给其它节点
    借用下seebug的图:
    -w830

在用户输入调用web3.js后,传入参数进行交易,在此处调用ethapi接口:
internal/ethapi/api.go 1071

unc (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {

    // Look up the wallet containing the requested signer
    account := accounts.Account{Address: args.From}

    wallet, err := s.b.AccountManager().Find(account)
    ......

通过from来调取传入的账户地址

account := accounts.Account{Address: args.From}//接收from参数
wallet, err := s.b.AccountManager().Find(account)//判断account是否存在

func (am *Manager) Find(account Account) (Wallet, error) {

通过交易信息生成type包

func (args *SendTxArgs) toTransaction() *types.Transaction {
    if args.To == nil {
        return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
    }
    return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
}

对交易进行签名

signed, err := wallet.SignTx(account, tx, chainID)

最后提交交易

    return submitTransaction(ctx, s.b, signed)

在这里推测是使用了big.NewInt.SetString函数,导致了此处的OOM造成了拒绝服务的风险。但是单独测试的后期并没复现成功,存在点问题,可能根本原因不在这,只在节点上测试成功。误打误撞的找到了这个节点漏洞。

漏洞复现

在客户端console中,使用对应的转账函数,此时使用其中的转账函数,将其中的gasPrice、gasvalue、value的值使用为对应的16进制长数据
将导致CPU占用。

eth.sendTransaction()

-w956
-w934

坑点

内存

在内存资源不足的情况下,程序会自动退出。

goroutine 1340 [chan receive]:
github.com/ethereum/go-ethereum/p2p/discover.(*udp).

在调用math.big.Rat.SetString的时候,浮点数过大会不解析,同样不会造成影响。
-w710

参考

https://blog.csdn.net/fpcc/article/details/81050976
https://tianyun6655.github.io/2017/09/24/以太坊源码交易/
https://www.colabug.com/3485978.html
https://cloud.tencent.com/developer/section/1143071
https://www.jianshu.com/p/1b0ffb58f565
https://github.com/zsfelfoldi/go-ethereum/wiki/Light-Ethereum-Subprotocol-%28LES%29
https://paper.seebug.org/656/
CATALOG
  1. 1. ETH
    1. 1.1. 搭建私有测试链
    2. 1.2. 创建私有链-待续
      1. 1.2.1. 部署测试合约-待续
      2. 1.2.2. 合约创建子链-待续
      3. 1.2.3. ETH 节点JSON-RPC测试
    3. 1.3. 创建钱包
      1. 1.3.1. 偷渡漏洞验证
        1. 1.3.1.1. 偷渡漏洞接口爆破
        2. 1.3.1.2. 漏洞复现
          1. 1.3.1.2.1. 探测节点端口是否开放、获取当前区块高度
          2. 1.3.1.2.2. 探测节点上是否绑定有相关账户
          3. 1.3.1.2.3. 查询下账户相关余额
          4. 1.3.1.2.4. 调用RPC爆破账户
          5. 1.3.1.2.5. 轮询监控节点解锁账户
          6. 1.3.1.2.6. 解锁期间进行转账
        3. 1.3.1.3. 离线状态下偷渡漏洞
      2. 1.3.2. 轻节点的拒绝服务漏洞-待分析
        1. 1.3.2.1. 漏洞分析
          1. 1.3.2.1.1. 漏洞成因
            1. 1.3.2.1.1.1. LES协议
        2. 1.3.2.2. 漏洞复现
      3. 1.3.3. SetString导致的OOM拒绝服务
        1. 1.3.3.1. 漏洞分析
          1. 1.3.3.1.1. 测试样例
          2. 1.3.3.1.2. 漏洞分析
        2. 1.3.3.2. 漏洞复现
    4. 1.4. 坑点
      1. 1.4.1. 内存
  2. 2. 参考