【実務・中級編】サーバーサイドリクエストフォージェリ (SSRF) の防御 – アプリケーションセキュリティ & 安全な開発防御ガイド

SSRFは「サーバーの肩を借りた内部侵入」だ。その盲点を撃ち抜け。

現場でインシデント対応をしていると、いまだに「SSRF(Server-Side Request Forgery)」を単なる「外部サイトへのリクエスト機能」と誤解しているエンジニアに出会う。だが、甘い。SSRFは、お前たちが大事に守っているはずのプライベートネットワークのゲートを、サーバー自身に開けさせる「自爆攻撃」なんだ。

クラウド環境では、これが致命傷になる。特にAWSのインスタンスメタデータサービス(IMDSv2以前)や、内部管理用の管理画面へのアクセスに使われると、数分で環境全体が乗っ取られる。今日は、教科書的な説明ではなく、現場で通用する「防御の極意」を叩き込む。

—

1. 攻撃者が狙う「盲点」:URLバリデーションの罠

多くのエンジニアが犯す最大の過ちは、「URLのホワイトリストを正規表現で頑張って書く」ことだ。

  • ^https?://example\.com/.$

これを見て「安全だ」と思ったなら、君のセキュリティ感覚はアップデートが必要だ。攻撃者は、以下のような「抜け穴」を常に探している。

  • DNS Rebinding: 最初は許可されたドメインで解決させ、直後に内部IP(例: 169.254.169.254)へ解決先を切り替える。
  • リダイレクト: 許可されたドメインの先に、内部リソースへのリダイレクトを仕込む。
  • IPv6/エンコーディング: http://[::]:80/ や、URLエンコードを駆使したバイパス。

鉄則: バリデーションで制御しようとするな。「物理的に通信経路を遮断する」のが唯一の正解だ。

—

2. 実践的防御:セキュアな実装コード

外部リクエストを行う際は、「信頼できないURLを直接ライブラリに渡さない」こと。そして、必ず「許可されたIPアドレス」のみに通信を制限する。

Pythonでの実装例(requests + socket)

import requests
import socket
from urllib.parse import urlparse

def secure_request(url):
# 1. ホスト名を解決してIPを確認する
parsed_url = urlparse(url)
hostname = parsed_url.hostname

# 2. 解決されたIPがプライベートIP空間でないかチェック
try:
ip = socket.gethostbyname(hostname)
# 簡易的なプライベートIPチェック(実際はipaddressモジュールで詳細に定義すること)
if ip.startswith((‘127.’, ‘169.254.’, ‘192.168.’, ’10.’)):
raise ValueError(“内部ネットワークへのリクエストは禁止されています”)

# 3. 許可されたドメインリストとの照合
allowed_domains = [‘api.trusted-service.com’]
if hostname not in allowed_domains:
raise ValueError(“許可されていないドメインです”)

return requests.get(url, timeout=3)
except Exception as e:
# エラーログは詳細に出しすぎない(情報の漏洩防止)
print(f”Request blocked: {e}”)
return None

—

3. インフラ側で「絶対に」やるべき設定

アプリのコードだけで守ろうとするな。インフラ側のガードレールを固めるのが、俺たちプロの仕事だ。

Cloud IAMによる防御(AWSの場合)

EC2インスタンスからメタデータサービスへのアクセスが必要ないなら、IMDSv2を強制し、さらにIAMロールの権限を最小化しろ。

IMDSv2を強制するコマンド
aws ec2 modify-instance-metadata-options \
–instance-id i-1234567890abcdef0 \
–http-tokens required \
–http-put-response-hop-limit 1 \
–http-endpoint enabled

ネットワークセグメンテーション(Egress制御)

サーバーからインターネットへの通信は、「デフォルト拒否(Deny All)」が基本だ。

  • Security Group / NACL: 特定のドメインへの通信を除き、外部へのアウトバウンドを全遮断する。
  • プロキシサーバーの導入: Squidなどのフォワードプロキシを挟み、そこでドメインフィルタリングを一元管理する。アプリサーバーには直接インターネットへのルートを与えない。

—

4. チーフからの提言:泥臭い検証を怠るな

最後に、一番重要なことを言う。
「君たちのアプリは、自分のサーバーが自分自身の管理ポート(localhost:8080など)を叩けることに気づいているか?」

脆弱性スキャンツールに頼るのもいいが、一度自分の手で、以下のPoCをステージング環境で試してほしい。

1. WebアプリのURL入力欄に http://127.0.0.1:80 や http://169.254.169.254/latest/meta-data/ を入力してみる。
2. これで「Internal Server Error」ではなく、何らかのレスポンスが返ってくるなら、君のアプリは既に穴だらけだ。

セキュリティとは、完璧なコードを書くことではない。「何が起きても致命傷にならないように、壁を何重にも作ること」だ。今日から、君たちの開発チームの設計にこの「多層防御」の考え方を組み込んでくれ。それが、インシデントを未然に防ぐ唯一の道だ。

コメント

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