作者:sunsama@知道創宇404區塊鏈安全研究團隊

背景介紹

為了迎合以太坊區塊鏈[1]發展需求,Microsoft Azure[2]早在2016年9月九推出了以太坊節點走自動部署的模塊。部署情況如下:

登陸Microsoft Azure:

部署Ethereum Proof-of-Work Consortium:

訪問建立的“ADMIN-SITE”可以看到一個“Blockchain Admin”界面:

我們注意到這個管理接口提供了一個“轉賬”功能并且整個頁面缺少鑒權機制任何人都可以訪問,這樣就導致惡意攻擊者可以通過該接口提交錢包地址和轉賬數量進行轉賬。

Web3.js 是?個兼容了以太坊核心功能的JavaScript庫[3],很多以太坊客戶端及DApp都是通過調用Web3.js的API接?來實現。 以太坊客戶端開發庫主要是提供了兩種類型的API接口:RPC(Remote Procedure Call)及IPC(Inter-process Communications),在以往的攻擊事件里很多關注點都在RPC接口上,而很少關注IPC接口,在本文的涉及“Blockchain Admin”的問題就發生在IPC接口上,由此下面做了詳細的代碼分析:

代碼分析

在分析之前我們先介紹下PRC及IPC接口區別:

IPC與RPC簡介

IPC(Inter-process Communications)進程間通信,是指在不同進程之間傳播或交換信息,IPC的方式通常有管道、消息隊列、信號量、共享存儲、Socket、Stream等。對于geth來說IPC的方式更為高效,在安裝geth之后 IPC socket不會自動創建,并且他也不是一個永久的資源,只有在啟動geth時才會創建一個IPC Socket。

有以下幾個參數可以在啟動geth時配置IPC相關服務,其他參數可以使用geth —help查看。

--ipcdisable                            Disable the IPC-RPC server
--ipcapi "admin,eth,debug,miner,net,shh,txpool,personal,web3"   API's offered over the IPC-RPC interface
--ipcpath "geth.ipc"                        Filename for IPC socket/pipe within the datadir (explicit paths escape it)

在geth啟動時使用 --ipcpath來指定一個IPC路徑,會有一段信息指明IPC的相關信息。例如

IPC endpoint opened: /Users/username/Library/Ethereum/geth.ipc

Web3.js中提供了使用IPC通信的方法。

// Using the IPC provider in node.js
var net = require('net');
var web3 = new Web3('/Users/myuser/Library/Ethereum/geth.ipc', net); // mac os path
// or
var web3 = new Web3(new Web3.providers.IpcProvider('/Users/myuser/Library/Ethereum/geth.ipc', net)); // mac os path
// on windows the path is: "\\\\.\\pipe\\geth.ipc"
// on linux the path is: "/users/myuser/.ethereum/geth.ipc"

node_modules/web3/lib/web3/ipcprovider.js

var IpcProvider = function (path, net) {
    var _this = this;
    this.responseCallbacks = {};
    this.path = path;

    this.connection = net.connect({path: this.path});
    ...............
};

https://github.com/ethereum/go-ethereum/wiki/Management-APIs中給出了在命令行使用IPC的例子

RPC(Remote Procedure Call)遠程過程調用,指通過網絡從遠程計算機的程序上請求服務。geth為RPC提供了兩種方法,分別是HTTP JSON RPC API(默認8545端口)和WebSocket JSON RPC API(默認8546端口)。

在命令行中可以使用以下參數配置RPC服務。

--rpc                       啟用HTTP-RPC服務器
--rpcaddr value             HTTP-RPC服務器接口地址(默認值:“localhost”)
--rpcport value             HTTP-RPC服務器監聽端口(默認值:8545)
--rpcapi value              基于HTTP-RPC接口提供的API
WebSocket
--ws                        啟用WS-RPC服務器
--wsaddr value              WS-RPC服務器監聽接口地址(默認值:“localhost”)
--wsport value              WS-RPC服務器監聽端口(默認值:8546)
--wsapi  value              基于WS-RPC的接口提供的API
--wsorigins value           websockets請求允許的源

同樣的在Web3.js中也提供了使用RPC的方法。

Http Api
var Web3 = require('web3');
var web3 = new Web3('http://localhost:8545');
// or
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));

WebSocket Api
// change provider
web3.setProvider('ws://localhost:8546');
// or
web3.setProvider(new Web3.providers.WebsocketProvider('ws://localhost:8546'));
/**
 * HttpProvider should be used to send rpc calls over http
 */
var HttpProvider = function (host, timeout) {
    this.host = host || 'http://localhost:8545';
    this.timeout = timeout || 0;
};

以太坊黑色情人節事件中,攻擊者就是利用了RPC接口進行惡意轉賬。

流程分析

我們在Blockchain Admin頁面的兩個輸入框中輸入轉賬地址和轉賬數量并提交。

/home/ethtest/etheradmin/app.js定義了提交后服務器處理的方法。

命令行中的參數
var listenPort = process.argv[2]
var gethIPCPath = process.argv[3];
var coinbase = process.argv[4];
var coinbasePw = process.argv[5];
var consortiumId = process.argv[6];
var registrarHostEndpoint = process.argv[7];
var registrarConnectionString = process.argv[8];
var registrarDatatbaseId = process.argv[9];
var registrarCollectionId = process.argv[10];

定義了使用IPC服務
var web3IPC = new Web3(new Web3.providers.IpcProvider(gethIPCPath, require('net')));

            ··············

app.post('/', function(req, res) {
  var address = req.body.etherAddress;//轉賬地址
  var amount = req.body.amount;//轉賬數量

  if(web3IPC.isAddress(address)) {
      //如果提交的地址是以太坊地址則解鎖賬號
    web3IPC.personal.unlockAccount(coinbase, coinbasePw, function(err, res) {
      console.log(res);
        //通過ipc方法發送一筆交易
      web3IPC.eth.sendTransaction({from: coinbase, to: address, value: web3IPC.toWei(amount, 'ether')}, function(err, res){ console.log(address)});
    });

    req.session.isSent = true;
  } else {
    req.session.error = "Not a valid Ethereum address";
  }

  res.redirect('/');
});

使用POST方法提交后,會判斷我們輸入的地址是否是合法的以太坊地址。默認情況下我們的賬號是處于鎖定狀態的,這里判斷地址正確后使用personl.unlockAccount()方法解鎖賬號。該方法需要的參數coinbase和coinbasePw在啟動服務時已經在命令行中作為參數傳遞過來了,使用ps命令查看該服務的進程。

其中f9cdc590071d9993b198b08694e5edf376979ce6是我們的錢包地址,123qweasdZXC是解鎖錢包需要的密碼,/home/ethtest/.ethereum/geth.ipcgetIPCPath參數的內容。

personal.js中的unlockAccount方法。

    var unlockAccount = new Method({
        name: 'unlockAccount',
        call: 'personal_unlockAccount',
        params: 3,
        inputFormatter: [formatters.inputAddressFormatter, null, null]
    });

IpcProvider.js中對發送方法的定義。

IpcProvider.prototype.send = function (payload) {

    if(this.connection.writeSync) {
        var result;

        // try reconnect, when connection is gone
        if(!this.connection.writable)
            this.connection.connect({path: this.path});

        var data = this.connection.writeSync(JSON.stringify(payload));

        try {
            result = JSON.parse(data);
        } catch(e) {
            throw errors.InvalidResponse(data);                
        }

        return result;

    } else {
        throw new Error('You tried to send "'+ payload.method +'" synchronously. Synchronous requests are not supported by the IPC provider.');
    }
};

ipcprovider會調用JSONRPC.js將unlockAccount方法中的參數格式化為JSON格式。

在node_modules/web3/lib/web3/ipcprovider.js中下斷點跟蹤一下數據流。

然后將數據通過socket寫入。

接下來geth通過IPC接收到了請求的方法和參數,然后使用UnlockAccount函數進行賬戶解鎖,解鎖賬戶后使?eth.sendTransaction?法發送交易。

sendTransaction方法會使用已經解鎖后的本地賬戶的私鑰進行簽名,并使用SignedTransaction方法進行發送簽名后的交易。

我們通過geth日志獲取交易hash,在console中查看詳細信息。

  • 下面是從提交交易請求到生成交易并發送的流程圖。

值得一提的是:在我們分析過程發現通過Microsoft Azure提供的以太坊節點自動化部署方案仍然使用的1.7.3版本的geth ?這個版本里UnlockAccount函數:

func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration *uint64) (bool, error) {

    const max = uint64(time.Duration(math.MaxInt64) / time.Second)

    var d time.Duration

    if duration == nil {

        d = 300 * time.Second

    } else if *duration > max {

        return false, errors.New("unlock duration too large")

    } else {

        d = time.Duration(*duration) * time.Second

    }

    err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d)

    return err == nil, err

}

wiki中對personal_unlockAccount方法的定義:

從keystore中解鎖賬戶并獲得私鑰,并把已經解鎖的私鑰放到內存中。解鎖賬戶的api允許傳入超時時間,默認超時為300秒,如果傳?入的超時時間為0,則是永久不不會超時,賬戶?直處于解鎖狀態,直到節點進程退出。這也是“以太坊【偷渡】漏洞事件[5]”發生的主要原因。

風險評估

在以往的關于以太坊攻擊案例里更多的是發生在暴露在互聯網的RPC接口上,?基于本地進程通訊的IPC接口 被認為是相對安全可靠的,但是如果類似于Microsoft Azure提供的以太坊節點?動化部署?案里 的“Blockchain Admin”基于IPC調?程序,本身沒有任何認證直接暴露在互聯網上無疑是巨大的安全風險。(注:通過ZoomEye?路空間搜索引擎[7]可以看到曾經暴露在互聯網上的目標。)

在實際測試分析過程發現使用Microsoft Azure提供的以太坊節點自動化部署方案更多的是聯盟鏈或私有鏈,部署共有鏈的情況較少,所以這個安全事件實際可能給共有鏈的帶來的影響相對不大。對于聯盟鏈或私有鏈的影響需要根據其本身的情況去衡量量評估。

報告流程

針對以上問題我們第一時間聯系了微軟:

  • 2018年5月21日 相關問題描敘報告給MSRC郵件 secure@microsoft.com
  • 2018年5月22日 收到MSRC郵件反饋并按要求補充了相關技術細節
  • 2018年5月24日 收到MSRC Case分配確認郵件
  • 2018年5月31日 收到MSRC關于ZoomEye搜索引擎相關細節詢問并反饋
  • 2018年7月6日 郵件MSRC追問相關問題修復進展
  • 2018年7月10日 收到MSRC反饋郵件稱:他們認為這個是設計考慮的問題,用戶可以選擇對管理頁面進行限制,另外升級了Geth版本

總結

區塊鏈虛擬貨幣安全事件頻發,安全刻不不容。通過這次的案例可以得幾點建議:

  • 盡量避免使用這種自動化部署區塊鏈應用的方案,如果必須使用的話,請仔細查看該方案使用的程序是否存在安全缺陷與漏洞。

  • 修改默認端口,關閉對外的高權限接口,如果必須暴露在互聯網,請對接口進行鑒權。

  • 關注官方發布的更新日志,及時更新代碼。


針對目前主流的以太坊應用,知道創宇提供專業權威的智能合約審計服務,規避因合約安全問題導致的財產損失,為各類以太坊應用安全保駕護航。

知道創宇404智能合約安全審計團隊: https://www.scanv.com/lca/index.html
聯系電話:(086) 136 8133 5016(沈經理,工作日:10:00-18:00)

歡迎掃碼咨詢:

參考

[1] https://baike.baidu.com/item/%E4%BB%A5%E5%A4%AA%E5%9D%8A/20865117?fr=aladdin
[2] https://azure.microsoft.com/en-us/
[3] https://github.com/ethereum/web3.js/
[4] https://github.com/ethereum/go-ethereum/wiki/Management-APIs
[5] http://www.bjnorthway.com/547/
[6] https://mp.weixin.qq.com/s/Kk2lsoQ1679Gda56Ec-zJg
[7] https://www.zoomeye.org/searchResult?q=%22Blockchain%20Admin%22


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/638/