SSRFを未然に防ぐ:APIとWebセキュリティの徹底防御ガイド

Security tutorial - IT technology blog
Security tutorial - IT technology blog

SSRFの脅威:自社サーバーが牙を向くとき

Server-Side Request Forgery(SSRF)は、究極の「内部犯行」と言えます。標準的なXSS攻撃がユーザーを標的にするのに対し, SSRF攻撃は自社のバックエンドを騙して攻撃者に仕立て上げます。本来公開されるべきではないRedisキャッシュやクラウドのメタデータサービスなどの内部リソースから、サーバーにデータを強制的に取得させるのです。

午前3時の呼び出し:デフォルトでのセキュリティ

私は「Security by Default(デフォルトでのセキュリティ)」の重要性を、身をもって学びました。午前3時、SSHのブルートフォース攻撃のログで埋め尽くされたターミナルを凝視していたときのことです。その経験から、一つの重要な教訓を得ました。ハッカーは開いている窓を探すだけでなく、システムを騙して内側からドアを開けさせる方法も探しているということです。SSRFはまさにその手口です。プロフィール画像、Webhookの統合、PDF生成機能などで、アプリがユーザー提供のURLを処理しているなら、あなたは標的になっています。

SSRFは実際にどのように起こるのか

新機能の実装において、外部データの取得が必要になることはよくあります。例えば、ユーザーがリモート画像のリンクを提供する場合です。サーバーはそのURLに対してGETリクエストを発行します。

しかし、攻撃者が代わりにhttp://127.0.0.1:6379を入力したとしたらどうでしょう。突然、サーバーが内部のRedisインスタンスを調査し始めます。AWSのようなクラウド環境では、攻撃者はhttp://169.254.169.254/latest/meta-data/を標的にして、IAM認証情報を奪取する可能性があります。ユーザーからの入力に端を発する場合、自社バックエンドから開始されるリクエスト先を決して信用してはいけません。

多層防御:正規表現を超えて

単純な文字列一致に頼ってはいけません。防御には、ネットワークレベルとアプリケーションレベルの両方で隔離レイヤーを構築する必要があります。

ネットワーク隔離の強制

本番環境をセットアップする際、私が最初に行うのはエグレス(外向き)トラフィックの制限です。Webサーバーがすべての内部IPと通信する必要があるケースは稀です。iptablesやクラウドのセキュリティグループを使用することで、Webサーバーが管理用ネットワークに到達するのを物理的に防ぐことができます。最近の監査では、ファイアウォールレベルで10.0.0.0/8の範囲をブロックするだけで、内部スキャン試行の90%を無効化できることがわかりました。

# www-data ユーザーによる AWS メタデータサービスへのアクセスをブロックする
iptables -A OUTPUT -m owner --uid-owner www-data -d 169.254.169.254 -j REJECT

HTTPクライアントの要塞化

標準的なライブラリは、デフォルトでリダイレクトに従います。これは大きな罠です。攻撃者は、一見安全そうなURLを提供し、それを制限された内部IP(192.168.1.1など)にリダイレクトさせることができます。リクエストのライフサイクルにフックできるライブラリを使用してください。DNSリバインディング攻撃を防ぐために、DNS解決の直後, かつリクエストが送信されるにIPアドレスを検証する必要があります。

「デフォルト拒否」の実装

localhost127.0.0.1のブロックリストは、10進数や16進数のエンコーディングで簡単に回避されます。唯一信頼できるアプローチは、許可リスト(アローリスト)です。

PythonによるIP検証

URLの取得を処理する方法を以下に示します。このスクリプトは、データ交換が行われる前にドメインを解決し、予約済みの範囲に対してIPをチェックします。

import socket
import ipaddress
import requests

def is_safe_url(url):
    try:
        # 1. ホスト名を抽出し、DNSを解決する
        host = url.split('/')[2].split(':')[0]
        ip_address = socket.gethostbyname(host)
        ip = ipaddress.ip_address(ip_address)
        
        # 2. プライベートまたはループバック範囲へのリクエストを遮断する
        if ip.is_private or ip.is_loopback or ip.is_link_local:
            return False
            
        return True
    except Exception:
        return False

def fetch_user_content(url):
    if not is_safe_url(url):
        raise ValueError("アクセス拒否:安全でない宛先が検出されました。")
    
    # 302ベースの回避を防ぐため、リダイレクトを無効にする
    return requests.get(url, allow_redirects=False, timeout=5)

AWS IMDSv2 へのアップグレード

EC2を利用している場合は、すぐにIMDSv2へ移行してください。旧バージョンとは異なり、IMDSv2ではPUTリクエストによるセッショントークンが必要です。ほとんどのSSRFの脆弱性はGETリクエストのみを許可するため、この変更だけで攻撃者がインスタンスのメタデータを盗むことはほぼ不可能になります。

# 特定のインスタンスでメタデータアクセスにトークンを必須にする
aws ec2 modify-instance-metadata-options \
    --instance-id i-0abcdef1234567890 \
    --http-tokens required \
    --http-endpoint enabled

継続的な検証:ガードレールのテスト

セキュリティは「一度設定すれば終わり」ではありません。防御が機能していることを証明する必要があります。

攻撃のシミュレーション

Burp SuiteやOWASP ZAPを起動しましょう。http://localhost:80の取得を試みたり、9200番ポートのElasticsearchのような内部サービスを調査したりしてください。アプリが一般的なエラー以外を返した場合、ロジックに漏れがあります。私はまた、これらのテスト中にサーバーが不正な外部コールを行っていないか監視するためにinteract.shを使用しています。

エグレス(外向き)トラフィックの監視

ログはあなたの目と耳です。ホスト名ではなくIPアドレスに対して直接行われたアウトバウンドリクエストを監視してください。内部範囲からの403 Forbiddenレスポンスが大量に発生している場合は、重大な警告信号です。多くの場合、攻撃者が自動ポートスキャンを実行していることを示唆しています。Falcoのようなツールを使えば、Webプロセスがメタデータサービスや機密性の高いデータベースポートに接触しようとした瞬間にアラートを出すことも可能です。

ユーザー入力は「有害なもの」として扱いましょう。ネットワークレベルの壁、厳格なコード検証、そして継続的な監視を組み合わせることで、重大なSSRFリスクを問題のないレベルまで抑えることができます。まずはネットワークから始め、コードを強化し、テストの手を緩めないでください。

Share: