今回は、Reentrancy問題のあるスマートコントラクトに攻撃をしてみます。
アカウントごとの役割
アカウントの役割は次の通りです。
- MAIN ACCOUNT (eth.accounts[0])
攻撃される側のコンストラクト(Victim Balance)生成者。 - ACCOUNT1 (eth.accounts[1])
攻撃される側のコントラクトに送金を行う通常のユーザ。 - ACCOUNT2 (eth.accounts[2])
攻撃する側のコンストラクト(Evil Receiverコ)生成者。 - ACCOUNT3 (eth.accounts[3])
マイナー。
攻撃される側のコントラクトをデプロイ
まずはデプロイを行います。デプロイアカウントはMAIN ACCOUNTです。
デプロイするのは、Victim Balanceコントラクトになります。
[Victim Balanceコントラクトのデプロイ]
デプロイ後のコントラクトの状態を確認します。
[攻撃される側のコントラクト状態]
デプロイ直後なので、コントラクトの残高が0 ehterであることが確認できます。
攻撃する側のコントラクトをデプロイ
次は攻撃する側のコントラクトのデプロイを行います。デプロイアカウントはACCOUNT2です。
デプロイするのは、Evil Receiverコントラクトになります。
targetには、攻撃される側のコントラクトのアドレスを設定します。
[Evil Receiverコントラクトのデプロイ]
デプロイ後のコントラクトの状態を確認します。
[攻撃する側のコントラクト状態]
ターゲットのコントラクトがVictim Balanceとなっていることが確認できます。
通常のユーザから送金
ACCOUNT1から、攻撃される側のコントラクト(Victim Balance)に20 ether送金します。
Add To Balance関数を使用します。
[送金]
送金後のコントラクトの状態を確認します。
[攻撃される側のコントラクト状態]
残高が20 etherになっていることが確認できます。
攻撃する側のコントラクトに送金
ACCOUNT2から、攻撃する側のコントラクト(Evil Receiver)に10 etherを送金します。
Add Balance関数を使用します。
[送金]
送金後のコントラクトの状態を確認します。
[攻撃する側のコントラクト状態]
残高が10 etherになっていることが確認できます。
攻撃する側のコントラクトから送金
攻撃する側のコントラクト(Evil Receiver)から攻撃される側のコントラクト(Victim Balance)に10 etherを送金します。
Send Eth To Target関数を使用します。
[送金]
攻撃される側のコントラクトの状態を確認します。
[攻撃される側のコントラクト状態]
10 ether追加されて、残高が30 etherになりました。
攻撃する側のコントラクトの状態を確認します。
[攻撃する側のコントラクト状態]
10 ether送金したので、残高が0 etherになりました
攻撃する側のコントラクトから返金要求
攻撃する側のコントラクト(Evil Receiver)から攻撃される側のコントラクト(Victim Balance)に返金要求を行います。Withdraw関数を使用します。
10 ether送金したので、返金額は10 etherとなるはずです。
[返金]
攻撃された側のコントラクト状態を確認します。
[攻撃される側のコントラクト状態]
問題が発生しました。
10 ether返金されたはずなので、20 etherの残高のはずが10 etherしかありません。
攻撃した側のコントラクト状態も確認します。
[攻撃する側のコントラクト状態]
本来は攻撃する側のコントラクトからは10 ehterしか引き出せないはずですが、20 etherを引き出せています。
これは、攻撃される側のコントラクト(Victim Balance)で返金処理後に残高更新していることに問題があります。
Fallback関数から再度攻撃される側の返金用関数(withdraw関数)が呼び出さると残高があるという判定になり、再返金処理が実行されてしまっているのです。
(イベントログにて、処理フローを確認したかったのですが、ログが時系列に出力されずにとても確認しづらかったので省略します😥😥)
次回はこのReentrancy問題を解決するためのソース修正を行います。