背景
お触り系タスクでありがちな,「一日一回無料でNFTをMintできる」類のタスクを自動化するために,該当するコントラクトの関数を見ていた時に発見(具体的なコントラクト名は伏せる).
コントラクト
まず,該当するコントラクトの関数部分を見てみる.
mintDailyNFT
function mintDailyNFT(
address to,
uint256 quantity,
uint256 nonce,
bytes memory signature
) public nonReentrant{
_mintWithSignature(to, NFT_TOKEN_ID, quantity, nonce, signature, true);
emit MintDailyOma(to, quantity);
}
これが一日一回NFTをMintできる関数である.動作としては,reentrancyを回避しつつ_mintWithSignatureを実行し,フロントエンドにイベントを送信している.ここでの疑問は,引数にあるsignatureとは何か?だろう.これは_mintWithSignatureを見ることで解決できる.
_mintWithSignature
function _mintWithSignature(
address to,
uint256 tokenId,
uint256 quantity,
uint256 nonce,
bytes memory signature,
bool isDaily
) internal {
// nonceが既に使われていれば例外を投げる
require(!usedNonces[nonce], "nonce already used.");
// 引数からmessageを作成
bytes32 hash = keccak256(
abi.encodePacked(to, tokenId, quantity, nonce, address(this), isDaily)
);
bytes32 message = _prefixed(hash);
// signatureを使用してmessageからsigner addressを復元
address signer = message.recover(signature);
// signer addressがDEFAULT_ADMIN_ROLEを持っていなければ例外を投げる
// つまり,signerがadmin(contract creator)でなければ無効なtxである
require(
hasRole(DEFAULT_ADMIN_ROLE, signer),
"bad signer role signature."
);
// nonceを使用済みにする
usedNonces[nonce] = true;
// NFTをMint
_nftMint(to, tokenId, quantity);
}
ここで,signatureはadminによる署名であることが分かる.つまり,フロントエンドからMintしないと有効なsignatureは作れない ということである.更に,nonceがあるため署名の再利用はできない.よくできているなと感心しつつ,フロントエンドを介さないとMintできない事実に落胆した.
MintのInput Data
気を取り直して,フロントエンドからMintした際のInput Dataを見てみる.
他は予想通りだが,nonceに謎の値が使用されていることが分かる.ここで話は変わるが,MetaMask等のWalletアプリでは,txの送信前にtxの内容を確認できる機能が付いている.実際に,mintDailyNFTをフロントエンドから実行しようとした際のtxの内容を確認してみる.
ここで,青線がnonce,赤枠がsignatureである.次に,このtxを送信せずにバツを押し,再度mintDailyNFTをフロントエンドから実行しようとした際のtxの内容を確認すると,以下の様になっていた.
なんと,nonceの値が全く異なる.要するに,フロントエンド側はtxごとにランダムなnonce値を使ってsignatureを作成しているということである.これの何が脆弱性なのかというと,無限にnonceが異なるsignatureを作り放題=無限にMintし放題 ということである.
実際にやってみた
複数のsignatureを作成し,三連続でmintDailyNFTを実行してみた.
実行できてしまった.(MintしたNFTはburn済)
脆弱な点まとめ
- nonceをtxごとに乱数で作成していた
- Mintが一回目かどうかをフロントエンド側で判断していた(これは仕方ない?)
以上,この記事がよりセキュアなコントラクト作成に活用できれば幸いです.
3/19 追記
脆弱性が修正されていました.おそらく日付毎に固定のnonceが設定されている様子.