サーバーサイドリクエストフォージェリ
サーバーサイドリクエストフォージェリ(SSRF)の脆弱性は、ユーザーがアプリケーションを操作し、攻撃者が指定したドメインに対してHTTPリクエストを送信させることが可能になることで発生します。アプリケーションがプライベートネットワークや内部ネットワークにアクセスできる場合、攻撃者はアプリケーションを介して内部サーバーへリクエストを送信させることも可能です。
ここでは、いくつかの例を通して、この脆弱性が実際にどのようなものか、より深く理解していきましょう。
例えば、ユーザーからURLを受け取り、そのページのプレビューやエクスポート用としてスクリーンショットを生成するAPIを考えてみます。
ts
let url = request.params.url;
let response = http.get(url);
let render = response.render();
return render.export();
URLパラメータはユーザーが制御できるため、攻撃者は任意のURLにアクセスできてしまいます。使用するライブラリによっては、「file://」スキームを使用してローカルファイルにアクセスされる可能性さえあります。
AWS環境でホストされている場合、SSRFの一般的な悪用方法として、AWSメタデータAPIへのアクセスが挙げられます。ここにはAWS APIの認証情報が含まれている可能性があり、アカウント内の他のAWSリソースへの不正アクセスにつながる恐れがあります。言うまでもなく、これは非常に危険な状態です。
緩和策
SSRFの脆弱性を緩和するのは非常に難しい場合があり、コードが何を目的としているかに大きく依存します。要件に応じて、適切な緩和策を講じる必要があります。
ユーザー指定のURLを避ける
ユーザーが任意のURLを入力する仕組みに依存しない機能を実装できる場合があります。もし可能であれば、これがSSRFのリスクを軽減する最も効果的な方法です。
機能を最小限に抑える
PDFエクスポート機能を実装する際、ヘッドレスブラウザを使用してページのスクリーンショットを撮るという手法がよく取られます。しかし、ブラウザは非常に複雑であり、多くの機能や攻撃対象領域(アタックサーフェス)を抱えているため、常に推奨されるわけではありません。
目的に合った適切なツールを選択することが重要です。多くの場合、シンプルなHTTPクライアントで十分です。機能を最小限に抑え、攻撃対象領域や攻撃ベクトルを減らすための対策は常に存在します。例えば、HTTPクライアントを使用する場合、以下のような対策が考えられます。
- リダイレクトの追跡を無効にする
- HTTPS以外のスキームをすべて無効にする
注意すべき落とし穴
リダイレクトとiframe
プライベートリソース(IPアドレスや内部ホスト名)へのSSRFを防ぐ一般的な方法として、ユーザーが提供したURLを解析し、「拒否リスト(deny-list)」を使用して機密リソースへのアクセスをブロックするという手法があります。
しかし、この方法はほとんどの場合において効果的ではありません。クライアントがHTTPリダイレクトやHTML/JavaScriptによるリダイレクトに従う場合や、iframeのような複雑な要素をレンダリングできる場合には、簡単に回避されてしまうためです。
攻撃者は、HTTP 301/302リダイレクト、HTMLメタタグによるリダイレクト、読み込み時のJavaScriptによるURL設定、あるいは内部リソースを表示するiframeの埋め込みなどを利用して、機密リソースへ転送するページをホストするURLを脆弱なアプリケーションに提供できます。これにより、元のURLに対する検証を事実上すべて回避することが可能になります。
DNSリバインディング
もう一つの「リダイレクト」手法として、DNSを利用する方法があります。SSRF攻撃を防ぐための一般的な対策として、以下のような手順が取られることがあります。
- 提供されたURLを解析する
- ホスト名を取得し、DNSルックアップを実行する
- 解決されたIPアドレスが内部/プライベートIPであればURLを拒否し、パブリックIPであれば許可する
この手法は「DNSリバインディング」に対して脆弱であるため、実際にはあまり効果がありません。DNSリバインディングは、リモートホストによってTCP接続が切断された際の、ほとんどのネットワークスタック(LinuxやWindowsなど)の標準的な動作を悪用します。
リモートホストが強制的に接続を切断すると、システムは再度DNSクエリを実行してIPアドレスを再解決し、再接続を試みます。
これにより、攻撃者は以下のような攻撃が可能になります。
- HTTPポートが開いているパブリックIPアドレスを指し、TTL(Time-to-live)が非常に短い`rebinding.attacker.com`というDNSエントリを作成する
- 脆弱なアプリケーションにURL(例: https://rebinding.attacker.com/)を送信する。アプリケーションは解決されたIPがプライベートではないことを確認し、安全であると判断して処理を続行する
- HTTP接続が確立された後、`rebinding.attacker.com`のDNSエントリを内部IPアドレスに変更する
- HTTP接続を強制的に切断する
- これにより、アプリケーションは`rebinding.attacker.com`のIPアドレスを再解決し、今度は内部IPアドレスが返される
- IP解決時の保護チェックは既に完了しており、DNSエントリの再解決と再接続はカーネルレベルで行われるため、アプリケーションはIPが変更されたことに気づかない
これにより、プライベートIPアドレスに解決されるURLの提供に対する保護が事実上回避されます。
IPv4とIPv6
「拒否リスト(deny-list)」を回避するもう一つの一般的な方法は、IPv6を使用することです。すべてのIPv4アドレスはIPv6アドレスとして表現できるため、アクセスしたいIPv4アドレスにマッピングされたIPv6アドレスを使用することで、拒否リストを回避できることがよくあります。
実践的なセキュアコーディング
OWASP Top 10のようなソフトウェアの脆弱性を理解し軽減するためのセキュアコーディングガイドラインを確認しましょう。また、実践的なアプリシミュレーションを通じて学べるトレーニングミッションにもぜひ挑戦してください。