境界線は幻想だ:SSRFで内部ネットワークを「食い尽くす」手口と防御の鉄則
「ファイアウォールがあるから安全」「VPC内だから外部からは見えない」――そう信じているなら、今すぐ考えを改めてほしい。外部から直接触れない内部APIや管理画面が、Webサーバーという「一番外側の門番」の裏切りによって、いとも簡単に引きずり出されるのがSSRF(Server-Side Request Forgery)の恐ろしさだ。
今日は、我々レッドチームがペネトレーションテストで「最初に狙う穴」の一つ、SSRFについて、現場の知見を交えて徹底的に解説する。
—
1. SSRFの正体:なぜ「サーバーの代筆」が脅威になるのか
SSRFの核心は、「サーバーに、本来アクセスすべきでない場所へリクエストを代行させること」にある。
攻撃者はWebサーバーの脆弱性を突き、自分たちの代わりに内部ネットワークの探索を行わせる。例えば、AWSのメタデータサービス(169.254.169.254)を叩かせてIAMロールの認証情報を奪ったり、内部のRedisやElasticsearchにコマンドを送り込んでデータを破壊・流出させるのが定石だ。
攻撃のシグナル
もしあなたのサーバーのアクセスログに、以下のような奇妙なリクエストが散見されるなら、既に探索が始まっているかもしれない。
?url=http://localhost:8080/admin?image_url=http://169.254.169.254/latest/meta-data/?callback=http://internal-db.local:6379/
—
2. 現場で使える防御の鉄則:ホワイトリスト以外は「悪」と見なせ
SSRFを防ぐために「ブラックリスト方式(特定のIPを拒否する)」を採用するのは、水漏れをセロハンテープで塞ぐようなものだ。IPの難読化やDNSリバインディングを使えば、いとも簡単にすり抜けてくる。
唯一の正解は、「ホワイトリストによる厳格な許可」だ。
Pythonでの実装例:安全なリクエストの設計
外部からURLを受け取ってコンテンツを取得する際、ライブラリのデフォルトに任せてはいけない。ホスト名、ポート、プロトコルを厳格にバリデーションする実装が必要だ。
import requests
from urllib.parse import urlparse
# 許可するホストのホワイトリスト
ALLOWED_HOSTS = ['api.trusted-service.com', 'images.cdn.com']
def fetch_url(target_url):
parsed_url = urlparse(target_url)
# 1. プロトコルの制限 (http/httpsのみ許可)
if parsed_url.scheme not in ['http', 'https']:
raise ValueError("無効なプロトコルです")
# 2. ホスト名のバリデーション
if parsed_url.netloc not in ALLOWED_HOSTS:
raise ValueError("許可されていないホストへのアクセスです")
# 3. リクエスト実行(リダイレクトを追跡しない設定が重要)
try:
response = requests.get(target_url, timeout=2, allow_redirects=False)
return response.content
except requests.exceptions.RequestException as e:
return f"エラーが発生しました: {e}"
—
3. インフラレベルでの「トドメ」を刺す設定
コード側の修正はもちろん必要だが、二重の防御としてインフラ設定も重要だ。
AWS環境:メタデータサービス(IMDSv2)の強制
AWSを使っているなら、メタデータサービスへのアクセスを強化する IMDSv2 を必須に設定しよう。これだけで、単純なSSRFによる認証情報の奪取を大幅に防げる。
# AWS CLIでメタデータサービスをIMDSv2専用にするコマンド
aws ec2 modify-instance-metadata-options \
--instance-id i-xxxxxxxxxxxxxxxxx \
--http-tokens required \
--http-put-response-hop-limit 1
Nginx:内部ネットワークへのルーティング制限
Webサーバーが不用意に内部ネットワークへパケットを投げないよう、iptables や nftables でEGRESS(外向き)トラフィックを制限するのがプロのやり方だ。
# 内部ネットワーク(10.0.0.0/8)への通信をサーバー自体から遮断する例
iptables -A OUTPUT -d 10.0.0.0/8 -j DROP
—
4. エンジニアへのアドバイス:脆弱性は「設計」の段階で潰す
現場でインシデントを見てきた経験から言わせてもらうと、SSRFを許してしまうシステムは、大抵の場合「何でもURLを渡せば処理してくれる柔軟すぎる機能」が原因だ。
- URLを直接ユーザーから受け取らない: 可能ならIDで管理し、サーバー側でURLを組み立てる設計に変える。
- リダイレクトを無効にする:
curlやrequestsのデフォルト設定は要注意だ。攻撃者はリダイレクトを悪用して内部IPに誘導する。 - DNS解決を制御する: 外部から指定されたホスト名が内部IPを指している場合、解決の段階で弾くバリデーションを組み込む。
セキュリティは「どこか一つの設定を変えればOK」という魔法ではない。アプリケーションのコード、インフラの境界、そして運用監視。これらが噛み合って初めて、強固な城壁となる。
今日からあなたの書くコードが、次のインシデントを防ぐ防波堤になることを期待している。不明点があれば、またいつでも聞いてくれ。現場からは以上だ。
コメント