はじめに
AIが自分でWebアプリ上を探索して脆弱性を突いてくれる「AIペンテストツール」が増えてきました。攻撃側の動きにも興味がありますが、個人的に知りたかったのは その攻撃が守る側のログにどう残るのか です。
そこで、オープンソースのAIペンテストツール ARTEX を、わざと脆弱に作られたWebアプリに向けて走らせ、ログを眺めてみました。
※基本的に作業のお供はClaudeですが、今回はTerraform等ツールの勉強を兼ねていたので、全部の作業は任せず細かいところは手を動かして検証しました。
この記事の検証は、すべて自分のPC内(127.0.0.1)で完結させています。ペンテストツールは、自分が所有し、かつツールの規約上も許される対象にだけ使ってください。
AWS上で作った環境に向けて使うつもりでスタート
当初の計画は「AWS上にJuice Shopを立てて、ARTEXの攻撃がCloudWatch LogsやVPC Flow Logs、CloudTrailにどう映るかを見る」といったもので、普段から仕事で見ているログだとどんな感じに見えるんだろうという興味から始めました。
消すときのことを考えて、Terraformをローカルから叩いて環境を作りました。構築用のIAMユーザーを切り出してい進めていく中、途中で少しハマりましたがとりあえず作れました。
※Terraformの勉強がてら作っていました。
- 構築用ユーザーの権限に instance profile 関連と
iam:PassRoleが足りず、terraform applyが途中で止まる - terraform stateの仕様を知らないまま進めてたのもあり、AIに壁打ちしながら進めた。
最終的にEC2上でJuice Shopが動くところまでは作れました。
OWASP Juice Shop とは
セキュリティ啓発団体のOWASPが公開している、わざと脆弱に作られた練習用のWebアプリです。ジュースを売るネットショップの体裁で、SQLインジェクションやXSS、アクセス制御の不備など、OWASP Top 10に挙がるような脆弱性が一通り仕込まれています。
脆弱性はそれぞれ「課題」として★1〜★6の難易度が付いていて、攻略するとアプリ内のスコアボードに記録されます。今回使ったバージョンでは課題は116個あり、どの攻撃が成立したかをアプリ自身が教えてくれるので、AIペンテスターの成果を答え合わせするのに最適です、
規約の警告をされ、ローカルに切り替えた
いざARTEXを向けようとしたところで、ClaudeにARTEXの使い方を読ませているときにARTEX側の規約違反に当たるかもしれないと怒られました。
以下がその利用規約です。第2項では自己所有なら例外とも読めるが、1項で『ローカルの隔離環境』での利用が想定されているので、安全側に倒してローカル検証することにしました。
また、別途調べていくとAWS上の資産に対してペネトレーションテストを行うときは申請が必要になるケースもあるようです(中には禁止されていることもあるので注意)
ここは素直に方針を変え、AWSの環境は terraform destroy で片付けることに...
ローカルに「AWSサービスっぽいログが出る環境」を作る
とはいえ、AWSのログっぽいものが見たいのは変わらずです。
そこで、ローカルでAWSのログ形式をまねることにしました。Claudeにどんな感じがいいか相談して出てきたのがこれです。
ARTEX (Docker Compose, 管理UI localhost:8787)
│ 対象URL: http://host.docker.internal:8080/
▼
nginx (127.0.0.1:8080) ── ALB Access Logs っぽいログを出力
│
▼
OWASP Juice Shop (コンテナ内部 :3000) ── 標準出力 = アプリログ
AWSのログとの対応はこう。
| AWSのログ | ローカルでの代わり |
|---|---|
| ALB Access Logs | nginxの log_format でALBと同じ並びのログを出力 |
| CloudWatch Logs(アプリ) | Juice Shopの標準出力 |
| VPC Flow Logs | 今回は見送り |
| CloudTrail | ローカルには相当するものなし(後述) |
nginxの log_format を、ALBのアクセスログのフィールド順に合わせて定義しています。
log_format alb 'http $time_iso8601 app/local-lab/50dc6c495c0c9188 '
'$remote_addr:$remote_port $upstream_addr '
'-1 $upstream_response_time -1 '
'$status $upstream_status $request_length $bytes_sent '
'"$request" "$http_user_agent" - - '
'- "-" "localhost" "-" 0 $time_iso8601 "forward" "-" "-" '
'"$upstream_addr" "$upstream_status" "-" "-"';
access_log /var/log/nginx/alb_access.log alb;
ALBのログのうち、ターゲットグループのARNやTLSの情報など、ローカルでは意味を持たないフィールドはダミー値で埋めているようです。ここは正直Claudeに全任せです。
下記は平常時(動作確認で1回アクセスしたとき)のログです。
http 2026-10-28T14:26:08+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:44674 172.19.0.2:3000 -1 0.004 -1 200 200 159 10450 "GET / HTTP/1.1" "Mozilla/5.0 (Windows NT; Windows NT 10.0; ja-JP) WindowsPowerShell/5.1.26100.9711" - - - "-" "localhost" "-" 0 2026-10-28T14:26:08+00:00 "forward" "-" "-" "172.19.0.2:3000" "200" "-" "-"
ARTEXを走らせる
準備
ARTEXはDocker Composeで起動し、ブラウザの管理UIから操作します。つまずいたのは2点でした。
- APIキーが必要。Claudeのサブスクリプションでは動かないので、Anthropic ConsoleでAPIキーを発行してクレジットを入れる
- 既定のモデルは高価なので、LLMの設定で
claude-haiku-5-5に変更しました(クレジット消費やばいんじゃないかとビクビクしながら弱いAIから試しました)
タスクの作成画面では、対象のURLを Description 欄に書き、Goal に目的(「このWebアプリを探索し、脆弱性を洗い出す」)を記載。Create を押すとそのまま実行が始まりました。

実行中の様子
タスク実行画面上ではトークンの消費量が増えていくのと、発見された脆弱性がレベルごとで増えていくのが観察できました。

Plannnerエージェントが考えてWorkerエージェントに指示を出している様子も見えました。最初の命令だけ中国語なのところに味が出ています

PlannnerがWorkerに指示を出しては結果を吸い上げ、そこからさらに指示をだして進行しているフロー図がARTEXの画面から確認できました。

結果
| 項目 | 値 |
|---|---|
| モデル |
claude-haiku-5-5(Worker 2) |
| タスク総時間(ARTEXの表示。計画や後処理を含む) | 6分50秒 |
| 攻撃トラフィックの時間(ログ上の最初と最後のリクエスト) | 6分35秒 |
| リクエスト数 | 634(GET 416 / POST 183 / PUT 34 ほか) |
| ARTEXが報告した Findings | 21件(Critical 3 / High 7 / Medium 8 / Low 3) |
| 実際に攻略できた問題(Juice Shopのスコアボード) | 116問中18問(最高は★4) |
| 費用 | 約 $0.46 |
Finding確認画面です。Criticalがちらほら表示されています。

API請求画面です。1回の使用量は格安です。

1問あたり3セント弱の計算です。Haikuだったのもありかなり格安でいけますね。
ちなみに、管理画面では入力トークンが約330万と表示されていましたが、実行ログを見るとAPIのレート制限(入力 50万トークン/分)による 429 が多発していました。トークン数の大半はこのリトライ分で、速度はモデルの性能よりもレート制限で頭打ちになっていた ようです。
攻撃はログにどう見えたか
めちゃくちゃ攻撃されてる時のALB風ログです。めっちゃ流れるのが楽しかったです。

アクセスログ(ALB風):何を試したか
まずステータスコードの分布です。
| ステータス | 件数 |
|---|---|
| 200 | 350 |
| 500 | 86 |
| 401 | 81 |
| 302 | 41 |
| 201 | 22 |
| 403 | 21 |
| 400 | 15 |
※ここら辺の分析はClaudeに任せたら以下でした。
普通の利用では、500や401、403がここまで多くなることはありません。
**エラーの比率の高さ** そのものが、探索されているサインです。
加えて、送信元は1つ、6分半で600件超という高いレートで、
User-Agent は `curl/7.88.1`(一部 `Python-urllib/3.12`)でした。
よく叩かれていたのは `/rest/products/search`、`/rest/user/login`、`/api/Users`、`/rest/user/whoami`、`/rest/admin/application-configuration` などです。
**SQLインジェクションの手順がそのまま残っていた** ことも特徴に上げられます。
1. 商品検索の `q` パラメータに `UNION SELECT NULL--`、`UNION SELECT NULL,NULL--` … と **NULLを1つずつ増やして** 送り、列の数を探る
2. 列数がわかったら `UNION SELECT id,email,password,... FROM Users--` で **ユーザーの資格情報を抜き出す**
3. `UNION SELECT sql,... FROM sqlite_master--` で **テーブル構造を一覧する**
Calude曰く参考書に載っている手順を、そのまま忠実に行っているとのこと。
一応説明の該当箇所のログは以下の通り。ログと突き合わせて、説明見るとまあまあグロいですね。
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47078 172.19.0.2:3000 -1 0.006 -1 500 500 174 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47082 172.19.0.2:3000 -1 0.006 -1 500 500 181 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47098 172.19.0.2:3000 -1 0.008 -1 500 500 188 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47112 172.19.0.2:3000 -1 0.006 -1 500 500 195 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47118 172.19.0.2:3000 -1 0.006 -1 500 500 202 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL%2cNULL-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47122 172.19.0.2:3000 -1 0.008 -1 500 500 209 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47126 172.19.0.2:3000 -1 0.006 -1 500 500 216 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47134 172.19.0.2:3000 -1 0.007 -1 500 500 223 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47144 172.19.0.2:3000 -1 0.006 -1 500 500 230 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47152 172.19.0.2:3000 -1 0.007 -1 500 500 237 2318 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:00+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47158 172.19.0.2:3000 -1 0.007 -1 500 500 244 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+id%2cemail%2cpassword%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL+FROM+Users-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:00+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:06+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47172 172.19.0.2:3000 -1 0.006 -1 500 500 242 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL+FROM+Users+-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:06+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:06+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47186 172.19.0.2:3000 -1 0.007 -1 500 500 245 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL+FROM+Users+--%2b HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:06+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:06+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47194 172.19.0.2:3000 -1 0.007 -1 500 500 243 1349 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL+FROM+Users+%23 HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:06+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:06+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47196 172.19.0.2:3000 -1 0.006 -1 500 500 246 1451 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+NULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL+FROM+Users+%2f%2a HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:06+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:06+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47200 172.19.0.2:3000 -1 0.007 -1 200 200 167 413 "GET /rest/products/search?q=xyz%27%29%29+ORDER+BY+8-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:06+00:00 "forward" "-" "-" "172.19.0.2:3000" "200" "-" "-"
http 2026-10-28T14:36:06+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47204 172.19.0.2:3000 -1 0.006 -1 200 200 167 413 "GET /rest/products/search?q=xyz%27%29%29+ORDER+BY+9-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:06+00:00 "forward" "-" "-" "172.19.0.2:3000" "200" "-" "-"
http 2026-10-28T14:36:06+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:47216 172.19.0.2:3000 -1 0.010 -1 200 200 168 413 "GET /rest/products/search?q=xyz%27%29%29+ORDER+BY+10-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:06+00:00 "forward" "-" "-" "172.19.0.2:3000" "200" "-" "-"
http 2026-10-28T14:36:06+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:45800 172.19.0.2:3000 -1 0.007 -1 500 500 168 1401 "GET /rest/products/search?q=xyz%27%29%29+ORDER+BY+11-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:06+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:06+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:45806 172.19.0.2:3000 -1 0.007 -1 500 500 168 1401 "GET /rest/products/search?q=xyz%27%29%29+ORDER+BY+12-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:06+00:00 "forward" "-" "-" "172.19.0.2:3000" "500" "-" "-"
http 2026-10-28T14:36:10+00:00 app/local-lab/50dc6c495c0c9188 172.19.0.1:45822 172.19.0.2:3000 -1 0.056 -1 200 200 251 5750 "GET /rest/products/search?q=xyz%27%29%29+UNION+SELECT+id%2cemail%2cpassword%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL%2cNULL+FROM+Users-- HTTP/1.1" "curl/7.88.1" - - - "-" "localhost" "-" 0 2026-10-28T14:36:10+00:00 "forward" "-" "-" "172.19.0.2:3000" "200" "-" "-"
アプリログ(CloudWatch Logs相当):何が成立したか
アクセスログからわかるのは「何を試したか」までです。それが成功したかどうかは、アプリ側のログを見る必要があります。
Claude曰くJuice Shopは、問題が解かれると次のようなログを出すようです。
info: Solved 4-star nullByteChallenge (Poison Null Byte)
info: Solved 4-star noSqlReviewsChallenge (NoSQL Manipulation)
さらに、Juice Shop自身の 不正検知 も働いていました。
Cheat score ... solved in 0min (expected ~8min) ... 0.99
「本来8分ほどかかる問題を0分で解いた」ので、自動化ツールの可能性が高い(スコア0.99)と判定しています。人間には無理な速さ は、防御側にとってわかりやすいシグナルです。
info: Solved 2-star deprecatedInterfaceChallenge (Deprecated Interface)
info: Cheat score for deprecatedInterfaceChallenge solved in 0min (expected ~4min) with hints allowed: 0.9503583333333333
info: Solved 3-star forgedReviewChallenge (Forged Review)
info: Cheat score for forgedReviewChallenge solved in 0min (expected ~6min) with hints allowed: 0.9960472222222222
UnauthorizedError: No Authorization header was found
Error: Unexpected path: /rest/products/reviews?id=%7b%22%24gt%22%3a%22%22%7d
at /juice-shop/build/routes/angular.js:18:18
at /juice-shop/build/lib/utils.js:239:26
at Layer.handle [as handle_request] (/juice-shop/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/juice-shop/node_modules/express/lib/router/index.js:328:13)
at /juice-shop/node_modules/express/lib/router/index.js:286:9
at router.process_params (/juice-shop/node_modules/express/lib/router/index.js:346:12)
at next (/juice-shop/node_modules/express/lib/router/index.js:280:10)
at /juice-shop/build/routes/verify.js:235:5
at Layer.handle [as handle_request] (/juice-shop/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/juice-shop/node_modules/express/lib/router/index.js:328:13)
at /juice-shop/node_modules/express/lib/router/index.js:286:9
at router.process_params (/juice-shop/node_modules/express/lib/router/index.js:346:12)
at next (/juice-shop/node_modules/express/lib/router/index.js:280:10)
at /juice-shop/build/lib/insecurity.js:218:5
at Layer.handle [as handle_request] (/juice-shop/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/juice-shop/node_modules/express/lib/router/index.js:328:13)
Error: WHERE parameter "captchaId" has invalid "undefined" value
スコアボードとの突合:実際にはどこまで刺さったか
Juice Shopはスコアボード(/api/Challenges)で、どの問題が解かれたかを持っています。これを正解データとして突き合わせました。
| 難易度 | 攻略数 |
|---|---|
| ★1 | 4 |
| ★2 | 4 |
| ★3 | 5 |
| ★4 | 5 |
| ★5・★6 | 0 |
管理者ログインのSQLi回避、NoSQLインジェクション、資格情報の抜き出し、他人のカートの閲覧などは攻略できていました。一方、XSSは9問中0、XXEやデシリアライズといった専門知識が必要な高難易度のものはとれていませんでした。とはいえ安いモデルのhaikuで6分半なら、★4あたりが限界というのが今回の結果でした。
[★1] Improper Input Validation :: Repetitive Registration
[★1] Security Misconfiguration :: Error Handling
[★1] Observability Failures :: Exposed Metrics
[★1] Sensitive Data Exposure :: Confidential Document
[★2] Broken Authentication :: Password Strength
[★2] Injection :: Login Admin
[★2] Security Misconfiguration :: Deprecated Interface
[★2] Broken Access Control :: View Basket
[★3] Improper Input Validation :: Upload Type
[★3] Broken Access Control :: Forged Review
[★3] Broken Access Control :: Forged Feedback
[★3] Improper Input Validation :: Deluxe Fraud
[★3] Improper Input Validation :: Admin Registration
[★4] Improper Input Validation :: Poison Null Byte
[★4] Unvalidated Redirects :: Allowlist Bypass
[★4] Sensitive Data Exposure :: Forgotten Developer Backup
[★4] Injection :: NoSQL Manipulation
[★4] Injection :: User Credentials
また、ARTEXが報告したFindingsは21件で、実際に成立した18件とは一致しません。ツールが「見つけた」と言っているものと、Juiceが本当に刺さったものに違いが出たのも面白いです。Findingsと課題は1対1ではないので、どれがどれに当たるかは別途深掘りしたいです。
ログから見えたAIペンテスターの特徴
| 特徴 | ログでの見え方 | どのログか |
|---|---|---|
| 対象を調べてから狙う | Juice Shop固有のAPI(/rest/products/search など)や、機密ファイルの置き場所(/ftp、/encryptionkeys)を開始直後から探る |
アクセスログ |
| 応答を見て次の手を変える |
UNION SELECT NULL を1つずつ増やす → 書き方を変える → ORDER BY で列数を確定 → 抜き出し |
アクセスログ |
| 速い | 列数の探りから資格情報の抜き出しまで約10秒 | アクセスログ |
| 外しながら近づく | URLの形を間違えたNoSQLインジェクションで Unexpected path → その後、正しいAPIで攻略 |
アプリログ |
| 書き込み系が多い | GET 416件に対し POST 183件、PUT 34件 | アクセスログ |
| 人間には無理な速さ | 本来数分かかる課題を0分で攻略し、cheat score 0.95〜0.99 | アプリログ |
| User-Agentはありふれている |
curl/7.88.1(一部 Python-urllib/3.12)。UAだけでは目立たない |
アクセスログ |
前段のnginxが攻撃を止めていた可能性もあるかも
nginxのエラーログには、ARTEXが /file-upload に5MBと2MBのファイルを送ろうとして、nginxのアップロード上限(既定1MB)で弾かれた記録が残っていました。これらのリクエストはJuice Shopまで届いていません。
Juice Shopには「サイズ制限を突破してアップロードする」課題(Upload Size)がありますが、今回は攻略できていませんでした。nginxで止められたことが原因の一つかもしれません。
おまけ:sonnetは動かなかった
haikuより上位の claude-sonnet-5-5 でも試そうとしましたが、今回のARTEX(v0.3.15)では実行が始まりませんでした。ツールが推論モデルに対応していなかったようで色々設定を変えたんですが動かなかったです。
- thinkingの設定なし:APIは200を返すが、本文が空のまま止まる
- thinkingを有効化:
thinking.enabled.budget_tokens: Field requiredで400エラー。ARTEXの設定画面には budget を入れる欄がない
追記(2026/10/30)
Anthropicの「Cyber Verification Program」について教えていただきました。
Anthropicのサポート記事によると、このプログラムはセキュリティの専門家向けに、高度なサイバー関連の機能を使えるようにするための申請制度です。対象はOpus 5.5やSonnet 5.5などで、申請していない場合、マルウェアの解析や、脆弱性を実際に突けるかどうかを確かめる作業などは「セーフティ分類器によって中断されることがある」とされています。Haikuは対象モデルに挙がっていませんでした。
今回のsonnetで起きた「APIは200を返すのに本文が空のまま止まる」現象は、この分類器による中断だった可能性があります。
上位モデルでペンテストツールを動かしたい場合は、ツール側の対応に加えて、こうした申請が必要になるかもしれません。
参考: https://support.claude.com/en/articles/14604842-cyber-verification-program
まとめ
-
ペンテストツールを使う前に、対象への権限 と ツールの規約 の両方を確認しましょう。
-
AWSに出せなくても、nginxでALB形式のログを出せば、ログの観察ができた
-
AIペンテスターは安くて速い(18問を約$0.46、6分半)。一方で、XSSや高難度の問題はまだ取りこぼしていた(モデルの性能による部分もありそうなので、上位モデルなら届くかも)
-
守る側としては、エラー率の急増、応答に合わせて段階的に変わるリクエスト、人間には無理な速さという点が手がかりになりそうです。
次はハニーポッドを作ってログ集計したいと思います。今回見れたようなAIペンテストツールのようなログが出てくるのかが気になります。
参考
- OWASP Juice Shop: https://owasp.org/www-project-juice-shop/
- ALBのアクセスログ(AWSドキュメント): https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html




