AWS Security Agent 検証 — オンデマンド・ペネトレーションテストの検出品質とコスト
目次
はじめに
2026年3月31日、AWS は AWS Security Agent のオンデマンド・ペネトレーションテストの一般提供(GA)を発表した。AI エージェントが自律的にアプリケーションの脆弱性を発見・検証する「フロンティアエージェント」で、従来の手動ペネトレーションテストを「数週間から数時間に短縮する」と謳っている。
AWS Security Agent は3つの機能を持つ:
- ペネトレーションテスト(Penetration Testing) — オンデマンドの AI 駆動ペネトレーションテスト($50/タスク時間)[GA]
- 設計レビュー(Design Security Review) — 設計ドキュメントのリアルタイムセキュリティレビュー(無料、月200回)[Preview]
- コードレビュー(Code Security Review) — PR に対する自動セキュリティレビュー(無料、月1,000回)[Preview]
今回 GA となったのはペネトレーションテストのみで、設計レビューとコードレビューは Preview のままである。本記事では GA となったペネトレーションテストを検証する。OWASP Top 10 だけでなくビジネスロジックの欠陥も検出できるとされるが、$50/タスク時間の価格に見合う検出品質なのか。意図的に5つの脆弱性を埋め込んだ Flask アプリに対してテストを実行し、検出率・Finding 品質・コストを定量的に評価する。公式ドキュメントは AWS Security Agent User Guide。
前提条件:
- AWS CLI v2(2.34.21 で動作確認済み)
- IAM ロール作成権限、EC2 / VPC の操作権限
- 検証リージョン: ap-northeast-1(東京)
- 料金: $50/タスク時間(秒単位課金)。新規顧客は2ヶ月の無料トライアル(月200タスク時間)
対応リージョンは us-east-1、us-west-2、eu-west-1、eu-central-1、ap-southeast-2、ap-northeast-1 の6つ。
結果だけ見たい場合はまとめに進んでほしい。
検証 1: CLI セットアップ(Agent Space 〜 ペネトレーションテスト作成)
Security Agent のリソースモデルは3層構造である。Application はアカウント全体の Security Agent 設定で、1アカウントにつき1つのみ作成できる(コンソールでの初回セットアップ時に自動作成)。Agent Space はテスト対象をグルーピングする論理コンテナで、VPC・IAM ロール・Target Domain を紐付ける。Target Domain はテスト許可されたドメインの登録で、所有権の証明が必要である。
今回は Agent Space の作成からペネトレーションテスト作成までを CLI のみで実行した。なお、ステップ 3〜5 では EC2 のプライベート DNS や VPC 情報が必要なため、先に検証 2 の「EC2 デプロイ手順」を実行し、$VPC_ID、$SUBNET_ID、$SG_ID、$PRIVATE_DNS を取得してから本セクションに戻ること。セットアップは以下の5ステップで構成される:
- IAM ロール作成(Security Agent サービスプリンシパル用)
- Agent Space 作成
- Target Domain 登録・検証(テスト対象のドメインの所有権証明)
- Agent Space に VPC・IAM ロール・Target Domain を関連付け
- ペネトレーションテスト作成
IAM ロール・Agent Space 作成手順
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=ap-northeast-1
# Security Agent 用サービスロール
cat > securityagent-trust-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "securityagent.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "aws:SourceAccount": "${ACCOUNT_ID}" }
}
}]
}
EOF
aws iam create-role \
--role-name SecurityAgentPentestRole \
--assume-role-policy-document file://securityagent-trust-policy.json
# VPC アクセス用ポリシー(ENI 作成に必要)
aws iam attach-role-policy \
--role-name SecurityAgentPentestRole \
--policy-arn arn:aws:iam::aws:policy/AmazonVPCFullAccess
# CloudWatch Logs ポリシー(ドキュメント未記載だが必須)
cat > logs-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:${REGION}:${ACCOUNT_ID}:log-group:/aws/securityagent/*"
}]
}
EOF
aws iam put-role-policy \
--role-name SecurityAgentPentestRole \
--policy-name SecurityAgentLogsAccess \
--policy-document file://logs-policy.jsonAGENT_SPACE_ID=$(aws securityagent create-agent-space \
--name "pentest-verification" \
--region $REGION \
--query "agentSpaceId" --output text)
echo "Agent Space: $AGENT_SPACE_ID"Target Domain 登録・検証
テスト対象のドメインを登録し、所有権を証明する必要がある。検証方法は DNS_TXT(DNS レコード追加)と HTTP_ROUTE(HTTP エンドポイントにトークン配置)の2種類。今回は HTTP_ROUTE を使用した。
TARGET_DOMAIN_ID=$(aws securityagent create-target-domain \
--target-domain-name "$PRIVATE_DNS" \
--verification-method HTTP_ROUTE \
--region $REGION \
--query "targetDomainId" --output text)
# 検証トークンを取得
aws securityagent batch-get-target-domains \
--target-domain-ids $TARGET_DOMAIN_ID \
--region $REGION \
--query "targetDomains[0].verificationDetails.httpRoute"{
"token": "Vdm6-_8EVhQZR3Xf0hOwug",
"routePath": ".well-known/aws/securityagent-domain-verification.json"
}取得したトークンを http://<domain>/.well-known/aws/securityagent-domain-verification.json で返すよう設定し、verify-target-domain を実行する。
注意: verify-target-domain CLI コマンドの HTTP_ROUTE 検証はパブリックアクセスが必要である。 compute.internal のようなプライベート DNS の場合、CLI の検証ステータスは UNREACHABLE になった。ただし VPC Config を使ったペネトレーションテストでは、PREFLIGHT ステップ中に VPC 内部から別途ドメイン検証が行われ、こちらは正常に通過した(ログに「Verifying ownership of private network domains → Completed」と記録)。つまり CLI のドメイン検証が UNREACHABLE でも、VPC Config 経由のテスト実行には影響しない。
Agent Space 更新・ペネトレーションテスト作成
SERVICE_ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/SecurityAgentPentestRole"
aws securityagent update-agent-space \
--agent-space-id $AGENT_SPACE_ID \
--name "pentest-verification" \
--aws-resources "{
\"vpcs\": [{
\"vpcArn\": \"$VPC_ID\",
\"securityGroupArns\": [\"$SG_ID\"],
\"subnetArns\": [\"$SUBNET_ID\"]
}],
\"iamRoles\": [\"$SERVICE_ROLE_ARN\"]
}" \
--target-domain-ids "$TARGET_DOMAIN_ID" \
--region $REGIONPENTEST_ID=$(aws securityagent create-pentest \
--title "vulnerable-flask-app-pentest" \
--agent-space-id $AGENT_SPACE_ID \
--service-role $SERVICE_ROLE_ARN \
--assets "{
\"endpoints\": [{\"uri\": \"http://${PRIVATE_DNS}\"}]
}" \
--vpc-config "{
\"vpcArn\": \"$VPC_ID\",
\"securityGroupArns\": [\"$SG_ID\"],
\"subnetArns\": [\"$SUBNET_ID\"]
}" \
--region $REGION \
--query "pentestId" --output text)
echo "Pentest: $PENTEST_ID"なお、--code-remediation-strategy AUTOMATIC を指定したところ 「Code remediation is not supported in ap-northeast-1」 というエラーが返った。コード修正自動生成は東京リージョンでは未対応である(2026年4月1日時点)。
セットアップ結果
| ステップ | 所要時間 |
|---|---|
| IAM ロール作成 + ポリシーアタッチ | 約8秒 |
| Agent Space 作成 | 約3秒 |
| Target Domain 登録 | 約2秒 |
| Agent Space 更新(VPC・IAM・Target Domain) | 約3秒 |
| ペネトレーションテスト作成 | 約2秒 |
| 合計 | 約18秒 |
CLI のみで完結し、18秒でテスト設定が完了する。ただし初回セットアップでは以下の制約に注意:
- 1アカウント1 Application 制限(コンソールで初回セットアップ済みなら CLI からの作成は不要)
- サービスロールは Agent Space の
aws-resources.iamRolesに事前登録が必須 - サービスロールに CloudWatch Logs 権限(
logs:CreateLogGroup等)が必須(ドキュメント未記載) --code-remediation-strategy AUTOMATICは東京リージョン未対応
検証 2: ペネトレーションテスト実行と Finding 品質分析
テスト対象アプリ
意図的に5つの脆弱性を埋め込んだ Flask アプリを EC2(t3.small)にデプロイした。本番環境ではアプリケーションがプライベートサブネットに配置されるケースが多いため、パブリックエンドポイントではなく VPC Config を使ったプライベートエンドポイントへのテストを選択した。Security Agent はプライベート DNS 経由で VPC 内からアクセスする。
脆弱性は「従来の DAST ツールでも検出できる定型パターン2つ」と「エンドポイントの発見や認証コンテキストの理解が必要なもの3つ」を意図的に混在させた。Security Agent の「コンテキスト認識型」アプローチが定型パターン以外でどこまで有効かを測るためである。
| # | 脆弱性 | カテゴリ | 検出難易度(予想) |
|---|---|---|---|
| 1 | SQL Injection(ログインフォーム) | インジェクション | 低(定型パターン) |
| 2 | Stored XSS(コメント機能) | XSS | 低(定型パターン) |
| 3 | IDOR(他ユーザーのプロフィール閲覧) | アクセス制御 | 中(認証コンテキスト必要) |
| 4 | JWT 検証不備(署名未検証) | 認証 | 中(ビジネスロジック) |
| 5 | Path Traversal(ファイルダウンロード) | インジェクション | 中(定型パターンだが HTML 上にリンクなし) |
Flask アプリのソースコード(app.py 全文)
"""
Deliberately vulnerable Flask application for Security Agent penetration testing.
WARNING: Do NOT deploy in production environments.
"""
import os, sqlite3, hashlib, json, base64
from flask import Flask, request, jsonify, g, send_file
app = Flask(__name__)
app.config["SECRET_KEY"] = "hardcoded-secret-key-123"
DATABASE = "/tmp/vuln_app.db"
UPLOAD_DIR = "/tmp/uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
def get_db():
if "db" not in g:
g.db = sqlite3.connect(DATABASE)
g.db.row_factory = sqlite3.Row
return g.db
@app.teardown_appcontext
def close_db(exception):
db = g.pop("db", None)
if db is not None:
db.close()
def init_db():
db = sqlite3.connect(DATABASE)
db.executescript("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
email TEXT, role TEXT DEFAULT 'user');
CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER, content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
""")
db.execute("INSERT OR IGNORE INTO users (username, password, email, role) VALUES (?, ?, ?, ?)",
("admin", hashlib.md5(b"admin123").hexdigest(), "admin@example.com", "admin"))
db.execute("INSERT OR IGNORE INTO users (username, password, email, role) VALUES (?, ?, ?, ?)",
("alice", hashlib.md5(b"password1").hexdigest(), "alice@example.com", "user"))
db.execute("INSERT OR IGNORE INTO users (username, password, email, role) VALUES (?, ?, ?, ?)",
("bob", hashlib.md5(b"password2").hexdigest(), "bob@example.com", "user"))
db.commit()
db.close()
# --- Vulnerability 1: SQL Injection ---
@app.route("/api/login", methods=["POST"])
def login():
data = request.get_json()
username, password = data.get("username", ""), data.get("password", "")
password_hash = hashlib.md5(password.encode()).hexdigest()
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password_hash}'"
try:
user = get_db().execute(query).fetchone()
except Exception as e:
return jsonify({"error": str(e)}), 500
if user:
payload = base64.b64encode(json.dumps(
{"user_id": user["id"], "username": user["username"], "role": user["role"]}
).encode()).decode()
return jsonify({"token": f"header.{payload}.nosignature", "user_id": user["id"]})
return jsonify({"error": "Invalid credentials"}), 401
# --- Vulnerability 2: Stored XSS ---
@app.route("/api/comments", methods=["GET"])
def get_comments():
comments = get_db().execute(
"SELECT c.id, c.content, c.created_at, u.username "
"FROM comments c JOIN users u ON c.user_id = u.id ORDER BY c.created_at DESC"
).fetchall()
return jsonify([dict(c) for c in comments])
@app.route("/api/comments", methods=["POST"])
def post_comment():
token = request.headers.get("Authorization", "")
user = decode_token(token)
if not user:
return jsonify({"error": "Unauthorized"}), 401
content = request.get_json().get("content", "")
get_db().execute("INSERT INTO comments (user_id, content) VALUES (?, ?)", (user["user_id"], content))
get_db().commit()
return jsonify({"message": "Comment posted"}), 201
# --- Vulnerability 3: IDOR ---
@app.route("/api/users/<int:user_id>", methods=["GET"])
def get_user_profile(user_id):
token = request.headers.get("Authorization", "")
user = decode_token(token)
if not user:
return jsonify({"error": "Unauthorized"}), 401
profile = get_db().execute("SELECT id, username, email, role FROM users WHERE id = ?",
(user_id,)).fetchone()
return jsonify(dict(profile)) if profile else (jsonify({"error": "Not found"}), 404)
# --- Vulnerability 4: JWT signature not verified ---
def decode_token(auth_header):
try:
token = auth_header.split("Bearer ")[1]
payload = json.loads(base64.b64decode(token.split(".")[1] + "=="))
return payload
except Exception:
return None
# --- Vulnerability 5: Path Traversal ---
@app.route("/api/files/download", methods=["GET"])
def download_file():
filename = request.args.get("filename", "")
filepath = os.path.join(UPLOAD_DIR, filename)
return send_file(filepath) if os.path.exists(filepath) else (jsonify({"error": "Not found"}), 404)
@app.route("/health")
def health():
return jsonify({"status": "ok"})
@app.route("/")
def index():
comments = get_db().execute(
"SELECT c.content, u.username FROM comments c JOIN users u ON c.user_id = u.id"
).fetchall()
comments_html = "".join(f"<div class='comment'><b>{c['username']}</b>: {c['content']}</div>" for c in comments)
return f"<!DOCTYPE html><html><head><title>Vulnerable App</title></head><body>" \
f"<h1>Comment Board</h1>{comments_html}</body></html>"
if __name__ == "__main__":
init_db()
app.run(host="0.0.0.0", port=80)EC2 デプロイ手順
デフォルト VPC を使用する。Security Agent は並列に13カテゴリの攻撃を実行するため、Flask 開発サーバー(シングルスレッド)ではクラッシュする。gunicorn(マルチワーカー)が必要である。
REGION=ap-northeast-1
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" \
--query "Vpcs[0].VpcId" --output text --region $REGION)
VPC_CIDR=$(aws ec2 describe-vpcs --vpc-ids $VPC_ID \
--query "Vpcs[0].CidrBlock" --output text --region $REGION)
SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" \
--query "Subnets[0].SubnetId" --output text --region $REGION)
AMI_ID=$(aws ssm get-parameters \
--names /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
--query "Parameters[0].Value" --output text --region $REGION)# SSM 接続用ロール
cat > ec2-trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
EOF
aws iam create-role --role-name SecurityAgentPentestEC2Role \
--assume-role-policy-document file://ec2-trust-policy.json
aws iam attach-role-policy --role-name SecurityAgentPentestEC2Role \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam create-instance-profile \
--instance-profile-name SecurityAgentPentestEC2Profile
aws iam add-role-to-instance-profile \
--instance-profile-name SecurityAgentPentestEC2Profile \
--role-name SecurityAgentPentestEC2Role
sleep 10 # IAM 伝播待ち# SG: VPC 内からの HTTP 80 のみ許可
SG_ID=$(aws ec2 create-security-group \
--group-name security-agent-pentest-target \
--description "Pentest target - HTTP from VPC only" \
--vpc-id $VPC_ID --region $REGION --query "GroupId" --output text)
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID --protocol tcp --port 80 \
--cidr $VPC_CIDR --region $REGION
# EC2 起動
INSTANCE_ID=$(aws ec2 run-instances \
--image-id $AMI_ID --instance-type t3.small \
--subnet-id $SUBNET_ID --security-group-ids $SG_ID \
--iam-instance-profile Name=SecurityAgentPentestEC2Profile \
--associate-public-ip-address \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=security-agent-pentest-target}]" \
--region $REGION --query "Instances[0].InstanceId" --output text)
aws ec2 wait instance-running --instance-ids $INSTANCE_ID --region $REGION
PRIVATE_DNS=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID \
--query "Reservations[0].Instances[0].PrivateDnsName" --output text --region $REGION)
echo "Private DNS: $PRIVATE_DNS"# SSM Agent オンライン待ち
while true; do
STATUS=$(aws ssm describe-instance-information \
--filters "Key=InstanceIds,Values=${INSTANCE_ID}" \
--query "InstanceInformationList[0].PingStatus" --output text --region $REGION 2>/dev/null)
[ "$STATUS" = "Online" ] && break
echo "Waiting for SSM Agent..." && sleep 10
done
# app.py を EC2 に転送(base64 エンコード経由)
APP_B64=$(base64 -w0 app.py)
aws ssm send-command --instance-ids $INSTANCE_ID \
--document-name "AWS-RunShellScript" \
--parameters "{\"commands\":[
\"sudo mkdir -p /opt/vulnerable-app\",
\"echo '$APP_B64' | base64 -d | sudo tee /opt/vulnerable-app/app.py > /dev/null\"
]}" --region $REGION
# gunicorn インストール + DB 初期化 + 起動
aws ssm send-command --instance-ids $INSTANCE_ID \
--document-name "AWS-RunShellScript" \
--timeout-seconds 120 \
--parameters '{"commands":[
"sudo python3 -m ensurepip --upgrade",
"sudo python3 -m pip install flask==3.1.0 gunicorn",
"cd /opt/vulnerable-app && sudo python3 -c \"exec(open('app.py').read().split('if __name__')[0]); init_db()\"",
"sudo systemd-run --unit=vuln-app --working-directory=/opt/vulnerable-app gunicorn --bind 0.0.0.0:80 --workers 4 --timeout 120 app:app"
]}' --region $REGION
sleep 5
# 動作確認
aws ssm send-command --instance-ids $INSTANCE_ID \
--document-name "AWS-RunShellScript" \
--parameters '{"commands":["curl -s http://localhost/health"]}' \
--region $REGIONテスト実行
aws securityagent start-pentest-job \
--pentest-id $PENTEST_ID \
--agent-space-id $AGENT_SPACE_ID \
--region $REGION{
"status": "IN_PROGRESS",
"pentestJobId": "pj-d63d1394-8caa-455c-a966-e384eb266718"
}進捗は batch-get-pentest-jobs で確認できる。テスト完了まで約2時間43分かかった。
aws securityagent batch-get-pentest-jobs \
--agent-space-id $AGENT_SPACE_ID \
--pentest-job-ids <pentestJobId> \
--region $REGION \
--query "pentestJobs[0].{status:status,steps:steps[*].{name:name,status:status}}"テストは4つのステップで進行する:
- PREFLIGHT — VPC 内にコンテナ環境を構築し、エンドポイントの到達性を確認
- STATIC_ANALYSIS — エンドポイントのクロール、ネットワークスキャン、TLS スキャン
- PENTEST — 実際のペネトレーションテスト(多段階攻撃シナリオの実行)
- FINALIZING — 結果の集約、CVSS スコアリング、レポート生成
CloudWatch Logs(/aws/securityagent/<agent-space-name>/<pentest-id>)に詳細な実行ログが出力される。実際に観測された内部タスクは以下の通り:
| タスク | 所要時間 | 内容 |
|---|---|---|
| Setup Testing Environment | 約26分 | VPC 内にコンピュート環境・ネットワーキング・コンテナを構築 |
| Endpoint Accessibility Validation | 数秒 | エンドポイントへの HTTP 接続確認 |
| SCANNER | 約7秒 | ネットワークスキャン(ポート・サービス検出) |
| TLS SCANNER | 約8秒 | TLS 設定のスキャン |
| CRAWLER | 約29分 | 複数の AI エージェントがブラウザと Python スクリプトで並列にエンドポイントを探索 |
| PENTEST(攻撃タスク群) | 約1時間37分 | 13カテゴリの攻撃を並列実行 + VALIDATOR TASK で検証 |
特筆すべきは CRAWLER の動作である。CloudWatch Logs から確認できたのは、複数の AI エージェントがブラウザセッション(browse ツール)と Python の requests ライブラリを並列に使い、アプリのエンドポイントを自律的に発見・分析していることだ(1回目の実行ログでは少なくとも4つの異なる agent_id を確認)。フォーム構造の解析、未知パス(/admin、/api/v1、/graphql 等)のプローブも行っており、従来の DAST ツールのクローラーとは異なるアプローチである。
PENTEST フェーズでは OWASP Top 10 を中心とした13カテゴリの攻撃タスク(XSS、SQLi、IDOR、Path Traversal、SSRF、SSTI、Code Injection、Command Injection、LFI、XXE、JWT、Privilege Escalation、Arbitrary File Upload)が並列実行された。各攻撃タスクの完了後、VALIDATOR TASK が Finding の再現検証を行い、confidence を確定する。
なお、1回目の実行では Flask の開発サーバー(シングルスレッド)が並列攻撃リクエストに耐えられずクラッシュし、テストが FAILED になった。2回目は gunicorn(4ワーカー)に切り替えて正常完了した。ペンテスト対象のアプリケーションサーバーにはマルチワーカー構成が必要である。
結果分析
テスト完了後、list-findings と batch-get-findings で検出結果を取得する。
# Finding 一覧
aws securityagent list-findings \
--agent-space-id $AGENT_SPACE_ID \
--pentest-job-id <pentestJobId> \
--region $REGION
# Finding 詳細(再現手順・CVSS・影響分析を含む)
aws securityagent batch-get-findings \
--agent-space-id $AGENT_SPACE_ID \
--finding-ids <findingId1> <findingId2> ... \
--region $REGIONテスト全体の所要時間は約2時間43分(PREFLIGHT 約32分 + STATIC_ANALYSIS 約29分 + PENTEST 約1時間37分 + FINALIZING 約6分)。
| 脆弱性 | 検出 | CVSS | confidence | 備考 |
|---|---|---|---|---|
| SQL Injection | ✅ | 9.8 CRITICAL | HIGH | ' OR '1'='1 で認証バイパスを再現。認証情報も admin:admin123 を発見 |
| Stored XSS | ✅ | 6.4 MEDIUM | LOW | <img onerror> ペイロードで JS 実行を確認。CSP ヘッダー未設定も指摘 |
| IDOR | ✅ | 6.5 MEDIUM | HIGH | alice のトークンで GET /api/users/1(admin)にアクセスし情報取得を再現 |
| JWT 検証不備 | ✅ | 10.0 CRITICAL | HIGH | 署名なしトークンで任意の user_id/role を設定し特権昇格を再現 |
| Path Traversal | ❌ | - | - | 未検出 |
| 想定外: Integer Overflow | ✅ | 4.3 MEDIUM | HIGH | GET /api/users/99999999999999999 で 500 エラー。埋め込んでいない脆弱性 |
検出率: 4/5(80%)、偽陽性: 0件
Path Traversal が未検出だった理由は確定できないが、/api/files/download?filename= は HTML ページ上にリンクがなく、API ドキュメントやソースコードも提供していなかったため、CRAWLER がエンドポイント自体を発見できなかった可能性が高い。ソースコードをアーティファクトとして登録すれば CRAWLER の探索範囲が広がるが、それで検出されるかは未検証である。
一方で、埋め込んでいない Integer Overflow の脆弱性を追加で発見した点は評価できる。user_id パラメータに極端な値を入力することで 500 Internal Server Error が発生するという、入力バリデーション不備の問題である。
Finding の品質は高い。各 Finding には以下が含まれる:
- 詳細な再現手順 — curl コマンド形式で、コピー&ペーストで再現可能
- CVSS スコア — 9.8(SQL Injection)、10.0(JWT)など、妥当なスコアリング
- 影響分析 — セッションハイジャック、データ漏洩等の具体的な影響を記述
- 修正ガイダンス — パラメータ化クエリ、出力エスケープ等の具体的な修正方法(Finding の description 内)
コスト
上記の所要時間(約2時間43分)を $50/タスク時間で換算すると 約$136(秒単位課金)。新規顧客の2ヶ月無料トライアル(月200タスク時間)を利用すれば、この規模のテストは無料枠内で十分実行可能である。
まとめ — どのようなチームに向いているか
今回の検証では、小規模アプリ(6エンドポイント)に対して5つの既知脆弱性のうち4つを検出し、偽陽性は0件だった。定期テストの間を埋める継続的セキュリティテストとして実用的と判断できる。特にビジネスロジック脆弱性(IDOR、JWT 検証不備)を HIGH confidence で検出できた点は、従来の DAST ツールとの明確な差別化ポイントだ。
| 観点 | 結果 | 評価 |
|---|---|---|
| CLI セットアップ | 18秒(IAM ロール〜ペンテスト作成) | ◎ |
| PREFLIGHT(VPC 環境構築) | 約32分 | ○ |
| テスト全体の所要時間 | 約2時間43分(6エンドポイントの小規模アプリ) | ○ |
| 検出率 | 4/5(80%)+ 想定外の脆弱性1件 | ◎ |
| 偽陽性 | 0件 | ◎ |
| Finding 品質 | 再現手順・CVSS・影響分析・修正ガイダンス付き | ◎ |
| ビジネスロジック脆弱性 | IDOR・JWT 検証不備を HIGH confidence で検出 | ◎ |
| VPC 内テスト | VPC Config で対応 | ◎ |
| コード修正提案 | 東京リージョン未対応 | △ |
| コスト | 約$136(2時間43分 × $50/タスク時間) | ○ |
- 定期的な手動ペンテストの間を埋める用途に最適 — 手動ペンテスト(数十万〜数百万円/回)は年1-2回が一般的。Security Agent なら約$136/回で開発サイクルに合わせた頻度でテストでき、2ヶ月の無料トライアルで評価もできる
- ソースコード / API ドキュメントのアーティファクト登録で検出率向上の余地あり — 今回は URL のみ提供で 4/5 の検出率だった。未検出の Path Traversal は HTML 上にリンクのないエンドポイントだったため、ソースコードを登録すれば CRAWLER の探索範囲が広がる可能性がある(未検証)
- CI/CD に組み込む場合は PREFLIGHT の約32分を考慮 — VPC 内テストでは環境構築に時間がかかるため、デプロイ前ゲートではなく夜間バッチや週次スケジュールでの実行が現実的
- テスト対象サーバーはマルチワーカー構成が必須 — 13カテゴリの攻撃が並列実行されるため、シングルスレッドサーバーでは負荷に耐えられない。本番環境では問題にならないが、検証環境では gunicorn 等の導入を忘れずに
実運用に向けた注意点
- VPC Config 利用時のドメイン検証 — CLI の
verify-target-domainはパブリックからのアクセスのためプライベート DNS ではUNREACHABLEになるが、VPC Config 経由のペンテストでは PREFLIGHT 中に VPC 内部検証が別途行われるため、テスト実行には影響しない。ただし Target Domain の登録自体は必要 - CloudWatch Logs 権限の追加を忘れずに — サービスロールに
logs:CreateLogGroup権限がないと PREFLIGHT で即失敗する。公式ドキュメントに記載がないため、IAM ポリシー作成時に忘れやすい - Code Remediation は東京リージョン未対応 —
--code-remediation-strategy AUTOMATICは 東京リージョンでは未対応(対応リージョンは未確認)。修正提案が必要な場合はリージョンの選定が必要
クリーンアップ
リソース削除手順
# ペネトレーションテスト削除
aws securityagent batch-delete-pentests \
--agent-space-id $AGENT_SPACE_ID \
--pentest-ids $PENTEST_ID \
--region $REGION
# Target Domain 削除
aws securityagent delete-target-domain \
--target-domain-id $TARGET_DOMAIN_ID \
--region $REGION
# Agent Space 削除
aws securityagent delete-agent-space \
--agent-space-id $AGENT_SPACE_ID \
--region $REGION
# EC2 インスタンス終了
aws ec2 terminate-instances --instance-ids $INSTANCE_ID --region $REGION
aws ec2 wait instance-terminated --instance-ids $INSTANCE_ID --region $REGION
aws ec2 delete-security-group --group-id $SG_ID --region $REGION
# IAM ロール削除
aws iam detach-role-policy --role-name SecurityAgentPentestRole \
--policy-arn arn:aws:iam::aws:policy/AmazonVPCFullAccess
aws iam delete-role-policy --role-name SecurityAgentPentestRole \
--policy-name SecurityAgentLogsAccess 2>/dev/null
aws iam delete-role --role-name SecurityAgentPentestRole
aws iam detach-role-policy --role-name SecurityAgentPentestEC2Role \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam delete-instance-profile --instance-profile-name SecurityAgentPentestEC2Profile
aws iam delete-role --role-name SecurityAgentPentestEC2Role