api-noauth-hunt について
必要な認証や認可がない可能性のある API 操作を見つけるセキュリティテスト用スキルです。読み取り専用の範囲を限定した調査を重視し、状態を変更するテストには合成レコードと事前の明示的な許可を求めます。
主な機能
- 01一般的な API パスの調査
- 02OpenAPI と Swagger スキーマの確認
- 03未認証読み取りの範囲限定検証
- 04制限付きの合成データ CRUD テスト
- 05証跡記録とクリーンアップの手順
活用シーン
- 01API サブドメインの公開操作の確認
- 02JavaScript バンドルで見つけたエンドポイントの調査
- 03認可済み評価でのアクセス制御不備の検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill api-noauth-huntインストール後、Claude Code で/api-noauth-huntを入力して読み込まれたか確認します。
詳細
API の認証なし検証
データや業務機能に必要な認証または認可がなくても到達できる可能性のある API 操作を特定します。通常、調査は読み取り専用です。書き込み検証では合成レコードを使用し、実行直前に明示的な許可が必要です。
使用するタイミング
- ポートスキャンで、標準以外のポート(3000、5000、8080-8085、9000)上に HTTP サービスが見つかった場合。
- 対象に API サブドメイン(api.target.com、backend.target.com)がある場合。
- JavaScript バンドルで内部 API エンドポイントが参照されている場合。
port-service-discoveryで予期しないポート上に HTTP が見つかった後。firebase-supabase-attackでバックエンド API が特定された後。
前提条件
- curl、python3、jq がインストールされていること。
- API の疑いがある対象の URL または IP:ポート。
- ファジング用の一般的な API パスの一覧。
実行方法
# Quick API test — try common paths without auth
TARGET="https://api.target.com"
for path in "/" "/api" "/api/v1" "/api/users" "/api/health" "/docs" "/swagger.json"; do
code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 --connect-timeout 5 "$TARGET$path")
echo "HTTP $code: $TARGET$path"
done
クイックリファレンス
| シグナル | 意味 | 対応 |
|---|---|---|
/api/users または /api/clients で HTTP 200 | 認証なしでデータにアクセスできる可能性があります | 範囲を限定したサンプルを 1 件検証します |
| 認証なしの POST で HTTP 2xx | 認証なしで書き込める可能性があります | 停止し、書き込みの許可を得ます |
/docs、/swagger.json に OpenAPI/Swagger がある | API 全体の構成が公開されています | すべてのエンドポイントを列挙します |
| エラー時にスタックトレースが返る | 内部パスやフレームワークのバージョンがわかります | インフラストラクチャをマッピングします |
| 予期しないメソッドによる状態変更 | メソッド単位の認可に不備がある可能性があります | 合成レコードを使って再現します |
| パスワードの検証なしでログインできる | 認証を回避できる可能性があります | 承認済みのテストアカウントで検証します |
手順
フェーズ 1 — API の調査
TARGET="$1" # URL or IP:port
OUTDIR="$OUTDIR/api_recon"
mkdir -p "$OUTDIR"
echo "[*] API discovery on $TARGET"
# Common API paths
API_PATHS=(
"/" "/api" "/api/v1" "/api/v2" "/v1" "/v2"
"/api/users" "/api/clients" "/api/admin" "/api/health"
"/api/auth" "/api/login" "/api/register"
"/api/products" "/api/orders" "/api/contracts"
"/docs" "/swagger.json" "/swagger.yaml" "/openapi.json"
"/api-docs" "/swagger-ui.html" "/graphql"
"/health" "/status" "/version" "/info" "/ping"
"/actuator" "/actuator/health" "/actuator/info" "/actuator/env"
)
for path in "${API_PATHS[@]}"; do
code=$(curl -sk -o /tmp/api_probe_$$.tmp -w "%{http_code}" --max-time 5 --connect-timeout 5 "$TARGET$path" 2>/dev/null)
if [[ "$code" == "200" ]]; then
body=$(cat /tmp/api_probe_$$.tmp)
content_type=$(file -b --mime-type /tmp/api_probe_$$.tmp 2>/dev/null)
# Check if it's JSON (likely API)
if echo "$body" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
record_count=$(echo "$body" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d) if isinstance(d,list) else 'object')" 2>/dev/null)
echo " [API] $path → HTTP 200 (JSON, ${record_count} records)"
elif echo "$body" | grep -qi "swagger\|openapi"; then
echo " [SWAGGER] $path → HTTP 200 (API documentation)"
elif echo "$body" | grep -qi "graphql"; then
echo " [GRAPHQL] $path → HTTP 200"
else
echo " [HTTP] $path → HTTP 200 (${#body} bytes, $content_type)"
fi
elif [[ "$code" == "401" || "$code" == "403" ]]; then
echo " [AUTH] $path → HTTP $code (protected)"
elif [[ "$code" == "500" ]]; then
echo " [ERROR] $path → HTTP 500 (potential injection point)"
cat /tmp/api_probe_$$.tmp | head -5
elif [[ "$code" != "404" && "$code" != "000" ]]; then
echo " [$code] $path"
fi
done
rm -f /tmp/api_probe_$$.tmp
フェーズ 2 — OpenAPI/Swagger の悪用
TARGET="$1"
echo "[*] Extracting API schema..."
# Try multiple Swagger paths
for sw_path in "/swagger.json" "/swagger.yaml" "/openapi.json" "/api/swagger.json" \
"/api-docs" "/v2/api-docs" "/v3/api-docs"; do
schema=$(curl -sk --max-time 10 --connect-timeout 10 "$TARGET$sw_path" 2>/dev/null)
if echo "$schema" | grep -q '"paths"'; then
echo "[+] Found OpenAPI spec at $sw_path"
# Extract all endpoints
echo "$schema" | python3 -c "
import sys, json
spec = json.load(sys.stdin)
paths = spec.get('paths', {})
for path, methods in paths.items():
for method in methods.keys():
if method not in ('parameters',):
print(f' {method.upper():7s} {path}')
" 2>/dev/null
# Save for later use
echo "$schema" > /tmp/openapi_$$.json
echo "[+] Schema saved to /tmp/openapi_$$.json"
break
fi
done
フェーズ 3 — 許可を得た合成レコードの CRUD 検証
このフェーズではサーバーの状態が変化します。スコープで書き込みテストが明示的に許可されており、エンドポイントが破棄可能な合成レコードを保存する場合にのみ実行してください。以下のガードは意図的なものです。削除したり、既存オブジェクトの ID に置き換えたりしないでください。
TARGET="$1"
ENDPOINT="$2" # approved test collection
OUTPUT_DIR="${OUTPUT_DIR:-./output}"
PROBE_ID="noauth-test-$(date +%s)"
if [[ "${I_HAVE_EXPLICIT_WRITE_AUTHORIZATION:-no}" != "yes" ]]; then
echo "Refusing state-changing validation without explicit authorization." >&2
exit 1
fi
mkdir -p "$OUTPUT_DIR/api-validation"
create_body=$(curl -sk --max-time 10 --connect-timeout 5 \
-X POST "$TARGET$ENDPOINT" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$PROBE_ID\",\"test_record\":true}")
printf '%s\n' "$create_body" \
> "$OUTPUT_DIR/api-validation/create-response.json"
created_id=$(printf '%s' "$create_body" | jq -r '.id // .uuid // empty')
if [[ -z "$created_id" ]]; then
echo "Create response did not expose a disposable object ID; stop here." >&2
exit 1
fi
curl -sk --max-time 10 --connect-timeout 5 \
"$TARGET$ENDPOINT/$created_id" \
-o "$OUTPUT_DIR/api-validation/read-response.json"
curl -sk --max-time 10 --connect-timeout 5 \
-X PATCH "$TARGET$ENDPOINT/$created_id" \
-H "Content-Type: application/json" \
-d '{"validation_state":"updated"}' \
-o "$OUTPUT_DIR/api-validation/update-response.json"
curl -sk --max-time 10 --connect-timeout 5 \
-X DELETE "$TARGET$ENDPOINT/$created_id" \
-o "$OUTPUT_DIR/api-validation/delete-response.txt"
フェーズ 4 — 範囲を限定した読み取り検証
TARGET="$1"
ENDPOINT="$2" # confirmed no-auth endpoint
OUTPUT_DIR="${OUTPUT_DIR:-./output}"
mkdir -p "$OUTPUT_DIR/api-validation"
curl --max-time 15 --connect-timeout 5 -sk \
"$TARGET$ENDPOINT?page=1&limit=2" \
-o "$OUTPUT_DIR/api-validation/bounded-sample.json"
jq 'if type == "array" then .[:2] else . end' \
"$OUTPUT_DIR/api-validation/bounded-sample.json"
注意点
- HTTP 200 ≠ API です。 一部のサービスは、予期しないパスに対して HTML を返します。JSON の Content-Type を確認してください。
- ページネーションによって、検証がデータ収集になる可能性があります。 アクセス制御の不備を示すのに必要な最小のページをリクエストしてください。データセットを列挙しないでください。
- POST、PATCH、PUT、DELETE は状態を変更します。 明示的な許可を得て、テスト用に作成した合成オブジェクトのみを操作してください。
- 認証テストによってアカウントがロックされたり、アラートが発生したりする可能性があります。 承認済みのテスト用 ID と、合意したリクエスト頻度を使用してください。
検証
- 同じ操作が認証なしで成功し、想定されるネガティブコントロールでは拒否されることを確認します。
- 読み取りの場合は、保護対象のデータ種別を示すのに必要な、最小限のサニタイズ済みサンプルのみを保持します。
- 書き込みの場合は、同じ合成オブジェクトの作成、取得、更新、削除、およびクリーンアップを記録します。
- URL、メソッド、想定されるポリシー、観測された動作、ID、タイムスタンプ、コントロールの結果、テストの制限を記録します。
コメント
Markdown 対応