【実務・中級編】 Web3アプリケーションにおけるCSP(Content Security Policy)の適用 – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

Web3のフロントエンドは「信頼の最後の砦」だ:CSPでウォレットドレインを物理的に封殺せよ

いいか、よく聞け。Web3界隈で「スマートコントラクトの監査は完璧だ」と胸を張るプロジェクトは山ほどあるが、そのフロントエンドがスカスカで、ユーザーの資産がごっそり抜かれる事例を俺はいくつも見てきた。

コントラクトがどれほど堅牢でも、フロントエンド(DApp)がXSS(クロスサイトスクリプティング)に無防備なら、攻撃者はユーザーのブラウザ上で「偽の署名要求」を捏造する。ウォレットの接続ボタンを押した瞬間、裏で eth_sendTransaction が改ざんされたら終わりだ。

今日は、そんな悪夢を未然に防ぐための「CSP(Content Security Policy)」の実装術を叩き込む。教科書的な説明は省略する。現場で「効く」設定だけを教えるぞ。

—

攻撃者の狙い:なぜDAppのフロントエンドが標的になるのか

攻撃者は、JavaScriptのライブラリ(ethers.js や web3.js)をフックして、provider.request({ method: 'eth_sendTransaction', ... }) の引数を書き換える。

もし君のサイトに悪意あるスクリプトが混入した場合、CSPがないと、攻撃者は外部の悪意あるドメインへデータを送信したり、勝手なスクリプトをロードし放題だ。これを防ぐ唯一にして最強の防御線が Content-Security-Policy ヘッダーによる「ブラウザへの強制命令」だ。

—

実践:最強のCSPポリシーを定義する

Web3アプリにおいて、許可すべきは「自分のドメイン」と「信頼できるRPCノード」、そして「ウォレット接続に必要な特定のスクリプト」だけだ。それ以外はすべて遮断する。

以下は、Nginxで設定すべき、実務レベルで推奨するCSPヘッダーだ。

# Nginxの設定ファイル内(server または location ブロック)
# 安全のために厳格なポリシーを適用する
add_header Content-Security-Policy "
  default-src 'self'; 
  script-src 'self' https://trusted.cdn.com; 
  connect-src 'self' https://mainnet.infura.io https://api.walletconnect.com wss://relay.walletconnect.org; 
  frame-src 'self' https://verify.walletconnect.com; 
  style-src 'self' 'unsafe-inline'; 
  object-src 'none'; 
  base-uri 'self';
" always;

この設定のポイント:

  • default-src 'self':デフォルトで外部からの読み込みを全て拒否。
  • connect-src:これがWeb3の生命線だ。API通信やウォレット接続先(InfuraやWalletConnect)のドメインをホワイトリスト化する。これ以外への通信はブラウザが強制ブロックする。
  • object-src 'none':レガシーな攻撃(プラグイン等)を無効化。
  • frame-src:WalletConnectのモーダル等がiframeを使う場合、許可リストを適切に管理する。

—

バックエンドでの動的生成(PHP/Node.jsでの実装例)

Nginxで一括設定できない場合や、nonce(ナンス)を使ってインラインスクリプトを制御したい場合は、アプリケーション側でヘッダーを制御する。

PHPでの実装例:

<?php
// 一意なナンスを生成(スクリプトごとに使い回さないのが鉄則)
$nonce = base64_encode(random_bytes(16));

// CSPヘッダーを送信
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-$nonce'; connect-src 'self' https://mainnet.infura.io;");

// HTML出力時
echo "<script nonce='$nonce'>console.log('これは実行される');</script>";
echo "<script>alert('これはブロックされる');</script>";
?>

—

現場でハマる「落とし穴」とデバッグのコツ

「CSPを導入したらサイトの表示が崩れた」「ウォレットが繋がらなくなった」と泣きついてくる後輩が多い。だが、それはブラウザのデベロッパーツール(コンソール)を見れば一発で解決する。

CSPによってリソースがブロックされた場合、ブラウザは親切にもエラーログを吐く。
> Refused to connect to 'https://malicious-site.com' because it violates the following Content Security Policy directive...

このログが出たら、「本当にそのドメインが必要か?」を自問自答しろ。必要なら connect-src に追加し、不要なら攻撃の兆候だ。

セキュリティチーフからのアドバイス:

1. unsafe-inline は絶対に使わない:どうしても必要な場合以外は排除しろ。XSSの温床だ。
2. report-uri を活用する:ポリシー違反が発生した際に、自分の監視サーバーへログを飛ばす設定をしておけ。インシデントの予兆検知に役立つ。
3. 常にテストモードを挟む:Content-Security-Policy-Report-Only ヘッダーを使えば、ブロックせずに違反だけを通知してくれる。まずはこれで本番環境のログを1週間監視してから、正式に適用しろ。

—

結論:セキュリティは「多層防御」の積み重ね

CSPは魔法ではない。しかし、攻撃者がフロントエンドを掌握した瞬間に「送金先を書き換える」という最後の凶行を物理的に遮断するための、最もコストパフォーマンスの良い防壁だ。

「面倒だから」といって設定を疎かにするな。君が書いたコードの先に、誰かの大切な資産があることを忘れるなよ。次は、React/Next.js環境におけるより詳細な実装と、WalletConnectのセッションハイジャック対策について掘り下げる。準備しておけ。

コメント

タイトルとURLをコピーしました