はじめに
Frontier AI の急速な進化に伴い、先日2026年10月22日のPHPの脆弱性のニュースのように、重大な脆弱性が新たに発見され、それを悪用される事案が頻出していることは、皆様にとっても非常に身近な話題かと思います。
一方で、最先端のAIを、自社で管理するコードの点検のために使うことを考えたことはありますでしょうか。
今回は、CiscoのFoundation AI が開発した、コード内の脆弱性がある箇所を特定することに特化した小規模言語モデル 「Antares」 を小規模な環境で動かし、ソフトウェアのセキュリティ向上を目的とするオープンコミュニティの OWASP が公開する、脆弱性を内包する練習用のECサイトとして有名な「Juice Shop」を解析し、AIが脆弱性を発見する実際の様子を確かめてみました。
この記事では次の内容をまとめます。
- Antares とは何か、どう動くのか、何が強みなのか
- CPU のみの環境での動作速度
- Juice Shop での検証結果
- 検証から見えた「得意なこと・苦手なこと」と、実務での使いどころ
解析ログについて
本記事で引用する Antares の解析ログは、実際の実行結果から抜粋したものです。ターン番号・コマンド・think(Antares の推論)・出力は実際のもので、長い出力は ... で省略している箇所があります。ログ中の「◆」で始まる行は、その行についての解説です。各章の末尾の折りたたみに、あるタスクの全ターンのログを掲載しています。
Antaresとは
| 項目 | 内容 |
|---|---|
| 種類 | 10億パラメータ(1B)の小規模言語モデル(SLM) |
| 目的 | コード内の脆弱性を持つ箇所を特定することに特化 |
| ベース | IBM Granite 4.0 |
| 学習 | 強化学習(GRPO)で学習 |
| 動作 | 外部システムに頼らず、自律的にターミナルを操作してリポジトリ内を探索する |
| 環境 | 大規模なGPU環境不要。軽量なオンプレミス環境で動く |
Cisco の Foundation AI は、脆弱性特定のベンチマークで、自身のサイズを大きく超える最先端の大規模モデルを上回る性能を持つとしています。モデルは複数サイズが用意されています。
| モデル | File F1 |
|---|---|
| Antares-350M-GRPO | 0.135 |
| Antares-1B-GRPO | 0.209 |
| Antares-3B-GRPO | 0.223 |
File F1 は、Antares が提出したファイルと、正解のファイルを比べるスコアです。提出したファイルのうち正解だった割合(適合率)と、正解のファイルのうち見つけられた割合(再現率)の調和平均で、0〜1の値をとります。1に近いほど、正解のファイルを過不足なく提出できていることを表します。上の値は、ベンチマーク(VLoc Bench: 実在する290のリポジトリ、500タスク、147種類の CWE)の全タスクの平均です。
今回は、速度と精度のバランスから 1Bモデルを使って検証を実施しました。350Mモデルは事前に実施した簡易的な検証において、速度は数倍速かったものの、判定の精度が明確に落ちたため使用を控えました。
Antaresの強み
(1) 汎用モデルに勝る精度を、小さなモデルと軽い環境で実現できる
公式ベンチマーク(VLoc Bench)の File F1 では、1B の Antares(0.209)が、Gemini-3-Pro(0.152)や、パラメータ数が約750倍の GLM-5.2(753B、0.186)など、多くの汎用モデルを上回っています。比較された20以上のモデルのうち、1B の Antares を上回ったのは、OpenAI の GPT-5.5 系(2モデル)だけです。3B の Antares(0.223)は、GPT-5.5(0.221)と同等の水準です。
モデルのサイズが小さいため、巨大なモデルを動かすためのGPUが無い環境でも気軽に使用できることや、クラウドでモデルをデプロイする際に注意が必要な推論コストについて、心配しなくて良いのが特徴です。
(2) 機密性の高いソースコードを、外部に一切送らずにスキャンできる
Antaresを使った解析は、Antaresが出力し読み取り専用のコマンドをサーバ側で実行することで進み、その過程でインターネットにアクセスする等の工程はありません。そのため、自社で内製した EC サイトのような機密性の高いソースコードを、OpenAIやGoogleといったLLMベンダがホスティングする外部のクラウドに一切送信すること無く、自社の物理サーバー内だけでスキャンを完結できます。
※Antaresを動作させる上での留意事項
Antares は、「このCWEに該当する脆弱性はあるか?」 という仮説を渡されて初めて動く特化型エージェントです。
言い換えると、ソースを丸ごと渡されて「このソースを解析し、該当する脆弱性を教えて」というざっくりとした問いに答えてくれるわけではない、ということです。
Antaresはls / cat / grep / find などの読み取り専用コマンド を駆使しながら、コードの書き方そのものから危険なパターンを読み取ります。
動作環境と速度
事前に、同じ Antares を3つの環境で動かして動作速度を比較しました。
| 環境 | 構成 | 1件あたり |
|---|---|---|
| ローカルPC | CPU(12コア/32GB)+ transformers | 1B: 約17.7分 / 350M: 約2.9分 |
| AWS | NVIDIA GPU(A10G)+ vLLM | 数秒〜数十秒 |
| 物理サーバー | Linux VM(8vCPU/15GB)+ Ollama | 数十秒 |
- AWS の GPU 環境は最速ですが、推論コストの面で見送りました。
- CPU のみの物理サーバーでも、GPU環境と比べて遜色ない速度で推論させることができました。
- そこで本検証では、物理サーバーに 1B モデルを入れ、解析サービスの環境を構築することにしました。
※ Juice Shop(計963ファイル)での実測では、本記事で扱う9タスク(CWE-601 / 327 / 89 を各3回)の所要時間は、1タスク(最大15回のコマンド実行を含む)あたり約110〜204秒(平均約144秒)程度。
Antaresの動作の流れ
Antares が提示するコマンドを、サーバー側で1つずつ実行して結果を返すことを繰り返し、怪しいファイルを絞り込んでいきます。
- プロンプト: 対象ディレクトリと、CWE 番号(仮説)を渡す。
- 推論: ファイル特定のために実行すべきコマンドと検索キーワードを Antares が回答する。
- 再度推論: ②のコマンドを実行した出力結果を Antares に再度送り、次のコマンドを推論させる。最大15回まで繰り返す。
- 提出: CWE に該当しそうなファイルを順位付きで提出する。
制約と注意点は次のとおりです。
-
ls/find/grep/catなどで読み取ることが出来ないファイル(例: コンパイル済みのバイナリファイル)は、検知の対象外です。 - 適切な出力を得るためのプロンプトの書式には、Antares 特有のお作法があります。今回は、公式リポジトリの記載を参照し、LLM と相談しながら書式を整えました(参照した場所は、後述の「検証対象」の節の折りたたみ部分に記載)。
解析対象
本検証では、OWASP Juice Shop というファイルを解析対象として採用しました。
OWASP Juice Shop は、OWASPが公式に提供する、意図的に脆弱に作られた疑似ECサイトです。セキュリティ学習の定番教材として認知されています。
今回は、次の条件で検証しました。
- Juice Shopの各ファイルから、正解の手がかりになるファイル・マーカー類を除去したものを使用(963ファイル)。
- EC サイトにありがちな代表的な CWE として、5種類(89 / 94 / 918 / 327 / 601)を選びました。Antares を実行する前に、正解ファイルを事前に記録しています。
- 各 CWE を3回ずつ、合計15タスクを実行。
- 使用モデル: Antares 1B(Ollama 上で動かす量子化版
q8_0) - 1タスクの探索は、最大15回のコマンド実行まで可能。
本記事では、このうち CWE-601 / 327 / 89 の3種を詳しく取り上げます。残りの CWE-94(コードインジェクション)と CWE-918(SSRF)も同じ条件で解析しましたが、うまくいかなかった(正解のファイルを特定できなかった)ため、本記事では詳細を割愛します。
| CWE | 内容(MITRE の正式名称) | 事前登録した正解 |
|---|---|---|
| CWE-601 | オープンリダイレクトURL Redirection to Untrusted Site ('Open Redirect')
|
lib/insecurity.ts, routes/redirect.ts
|
| CWE-327 | リスクのある暗号アルゴリズムUse of a Broken or Risky Cryptographic Algorithm
|
lib/insecurity.ts |
| CWE-89 | SQLインジェクションImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
|
routes/login.ts, routes/search.ts
|
Antares に渡した指示(システムプロンプトとユーザープロンプト)
Antares へのプロンプトには、書き方のお作法があります。公式リポジトリの次の2か所を参照して作成しました。
-
README.md(モデルカード)の「How to Get Started with the Model」の節: エージェントとしての動かし方(システムプロンプトで CWE を渡すこと、最大15回のターミナル呼び出し制限、<think>/<tool_call>/<tool_response>のタグの付加など) -
chat_template.jinja: ツール定義(<tools>)やツール呼び出し(<tool_call>)をプロンプトに整形するルール
ユーザープロンプトは、CWE ごとに次の形です(CWE-601 の例)。CWE 番号と一緒に入れている CWE の説明は、MITRE 社の CWE の定義に基づく正式名称をそのまま使用しています。
Analyze this codebase for the following vulnerability class:
CWE-601: URL Redirection to Untrusted Site ('Open Redirect')
The repository is at /workspace/repo/. Use the terminal tool to explore and determine if this vulnerability exists. Then either submit the vulnerable file(s) or declare no vulnerability found.
システムプロンプトは、公式ドキュメントの記載に倣い、以下のように作成しています(冒頭の「You are a security vulnerability localization agent.」も、モデルカードの例に沿ったものです)。
You are a security vulnerability localization agent. You have read-only terminal access to a codebase.
Given a CWE (Common Weakness Enumeration) description, your task is to determine whether this codebase contains a vulnerability matching that CWE class, and if so, identify which source file(s) are vulnerable.
You can explore the codebase using the `terminal` tool — it runs read-only commands (awk, basename, cat, cut, diff, dirname, du, echo, file, find, grep, head, ls, nl, pwd, realpath, rg, sed, sort, stat, tail, tree, uniq, wc, xargs) inside the repository. Pipes are OK (up to 4 stages). You have up to 15 terminal calls.
When you're done exploring:
- If you found vulnerable file(s): call `submit_vulnerable_files` with a ranked list of file paths (most likely vulnerable first).
- If you believe this codebase does NOT contain the described vulnerability: call `submit_no_vulnerability_found`.
You may be looking at code that has already been patched — in that case, the correct answer is to submit nothing. Do not guess or hallucinate files. Only submit files you have evidence for.
NOTE: Submitted paths must be exact file paths (e.g. src/utils.py), never globs or wildcards. Use at least 7-10 terminal calls to thoroughly explore the codebase before submitting.
このシステムプロンプトには、3つのツールが登場します。
-
terminal: Antares が対象のコードを調べるために、読み取り専用のコマンド(ls/cat/grepなど)を実行する場所です。 -
submit_vulnerable_files: 該当すると判断したファイルを、可能性の高い順に提出します。 -
submit_no_vulnerability_found: 該当する脆弱性はない、と申告する際に使用します。
各ツールの引数などを定義した JSON は、このシステムプロンプトとは別に、チャットテンプレートによって自動で付け足されます。
解析例1: CWE-601(オープンリダイレクト)
脆弱性の概要
CWE-601 は、偽装リンクで外部の悪意あるサイトへ誘導できてしまう脆弱性です。
多くのサイトには、リンクを押すと別のページへ自動で移動する「リダイレクト」機能があります。
- 本来は、決まった先(自社の別ページや提携先)にだけ案内します。
- 脆弱な場合は、案内先を外部から自由に指定でき、悪意あるサイトにも飛ばせます。
Juice Shop のどこが問題か
Juice Shop には /redirect?to=○○ という機能があります。関係するコードは2つのファイルに分かれています。以下のコードブロックは、実際のコードを改変せずに引用したものです(解説はコードの外に記載)。
引用元は、Juice Shop の 2026-10-01 時点のソースです(routes/redirect.ts は公開リポジトリのままです)。lib/insecurity.ts は、Antares に見せたコードであり、公開リポジトリのものから、正解の手がかりになる vuln-code-snippet というコメントを除去したものです。
① リダイレクトを実行する側(routes/redirect.ts 12〜24行目)
export function performRedirect () {
return ({ query }: Request, res: Response, next: NextFunction) => {
const toUrl: string = query.to as string
if (security.isRedirectAllowed(toUrl)) {
challengeUtils.solveIf(challenges.redirectCryptoCurrencyChallenge, () => { return toUrl === 'https://explorer.dash.org/address/Xr556RzuwX6hg5EGpkybbv5RanJoZN17kW' || toUrl === 'https://blockchain.info/address/1AbKfgvw9psQ41NbLi8kufDQTezwG8DRZm' || toUrl === 'https://etherscan.io/address/0x0f933ab9fcaaa782d0279c300d73750e1311eae6' })
challengeUtils.solveIf(challenges.redirectChallenge, () => { return isUnintendedRedirect(toUrl) })
res.redirect(toUrl)
} else {
res.status(406)
next(new Error('Unrecognized target URL for redirect: ' + toUrl))
}
}
}
- 15行目:
security.isRedirectAllowed(toUrl)が許可チェックです。このチェックを通った場合に限り、18行目のres.redirect(toUrl)でリダイレクトを実行します。 - ※17行目の
isUnintendedRedirectは、すり抜けたURLかどうかを調べて、課題の達成を記録するために OWASP 側が使っている関数であり、脆弱性のある箇所ではありません(リダイレクトの動作には影響しません)。
② 許可チェック部分の本体(lib/insecurity.ts 122〜139行目)
export const redirectAllowlist = new Set([
'https://github.com/juice-shop/juice-shop',
'https://blockchain.info/address/1AbKfgvw9psQ41NbLi8kufDQTezwG8DRZm',
'https://explorer.dash.org/address/Xr556RzuwX6hg5EGpkybbv5RanJoZN17kW',
'https://etherscan.io/address/0x0f933ab9fcaaa782d0279c300d73750e1311eae6',
'http://shop.spreadshirt.com/juiceshop',
'http://shop.spreadshirt.de/juiceshop',
'https://www.stickeryou.com/products/owasp-juice-shop/794',
'http://leanpub.com/juice-shop'
])
export const isRedirectAllowed = (url: string) => {
let allowed = false
for (const allowedUrl of redirectAllowlist) {
allowed = allowed || url.includes(allowedUrl)
}
return allowed
}
「許可リストにあるURLにだけ飛ばす」というチェック自体は問題無く入っています。ところが、136行目の url.includes(allowedUrl) は、URL のどこかに許可リストの文字列が含まれていれば OK という判定です。意図しないURLまでリダイレクトされてしまうのを防ぎたい場合、本来は飛び先が許可リストと完全に一致することを確認すべきです。
すり抜けてしまうURLの例
許可リストには http://leanpub.com/juice-shop が入っています。このとき、/redirect?to=○○ の ○○ に次のURLを指定すると、どうなるでしょうか。
to に指定するURL |
許可チェック(isRedirectAllowed) |
結果 |
|---|---|---|
http://leanpub.com/juice-shop |
通る(許可リストと一致) | 提携先へ移動(意図どおり) |
https://evil.example/ |
通らない | 406 エラーで拒否される |
https://evil.example/?http://leanpub.com/juice-shop |
通ってしまう | 偽サイト evil.example へ移動 |
3行目のように、偽サイトのURLの末尾に、許可リストの文字列を付け足すだけで、許可チェックをすり抜けることが可能となっているのです。
攻撃の流れ(時系列)
登場人物は、攻撃者、被害者、本物のショップ、攻撃者が用意した偽サイトです。
- 攻撃者が、許可リストの文字列を末尾に付けたURLを作る。
https://(ショップ)/redirect?to=https://evil.example/?http://leanpub.com/juice-shop - このリンクをメールやSNSで被害者に送る。ドメインは本物のショップなので、怪しまれにくい。
- 被害者がリンクを押し、本物のショップにアクセスする。
- ショップ側が「許可リストの文字列が含まれているので OK」と判断し、
evil.exampleへ自動で転送する。 - 偽サイトが本物そっくりのログイン画面を表示し、被害者が ID やパスワードを入力してしまう(フィッシング)。
一見、正規サイトのリンクに見えてしまうため、「怪しいリンクは踏まない」という通常のフィッシング対策をすり抜けやすい点が厄介です。
Antares はどう見つけたか
あるタスクでの探索ログから、実際のコマンドと出力を引用します(think が Antares の推論、$ がコマンド、◆ がそのコマンドの解説です)。
[ターン4] think: Locate redirect logic and URL construction code.
$ rg -n "redirect\.|redirect\(" .
./routes/redirect.ts:18: res.redirect(toUrl)
./routes/updateUserProfile.ts:44: res.redirect(process.env.BASE_PATH + '/profile')
./routes/profileImageFileUpload.ts:56: res.redirect(process.env.BASE_PATH + '/profile')
./routes/profileImageUrlUpload.ts:49: res.redirect(process.env.BASE_PATH + '/profile')
◆ リダイレクトを実行している箇所を洗い出している。`routes/redirect.ts:18` で、利用者が指定できるURL(`toUrl`)をそのまま渡している。
[ターン7] think: Locate implementation of redirect target validation or allowlist.
$ rg -n "isUnintendedRedirect|redirectAllowlist|redirectAllowlist\.|allowlist" .
./routes/redirect.ts:17: challengeUtils.solveIf(challenges.redirectChallenge, () => { return isUnintendedRedirect(toUrl) })
./routes/redirect.ts:26:function isUnintendedRedirect (toUrl: string) {
./routes/redirect.ts:28: for (const allowedUrl of security.redirectAllowlist) {
./data/static/i18n/fi_FI.json:511: "You have to find a way to beat the allowlist of allowed re...
◆ 「移動先を検証する仕組み(許可リスト)がどこにあるか」を探している。`redirect.ts` が `security.redirectAllowlist` という別ファイルの変数を参照していると分かる。
[ターン9] think: Locate redirect allowlist configuration.
$ rg -n "redirectAllowlist" .
./routes/redirect.ts:28: for (const allowedUrl of security.redirectAllowlist) {
./lib/insecurity.ts:122:export const redirectAllowlist = new Set([
./lib/insecurity.ts:135: for (const allowedUrl of redirectAllowlist) {
◆ 参照先の `redirectAllowlist` の定義を探し、`lib/insecurity.ts:122` に到達。
[ターン11] think: Locate redirect validation implementation details.
$ cat routes/redirect.ts
[ターン12] think: Locate redirect allowlist implementation and whether it validates schemes/hosts.
$ cat lib/insecurity.ts
◆ 移動を実行する側(`redirect.ts`)と、許可リストを持つ側(`insecurity.ts`)の両方の中身を確認。
[ターン13] think: Locate the redirect allowlist source and verify whether it contains only safe URLs.
submit_vulnerable_files(["routes/redirect.ts", "lib/insecurity.ts"])
◆ 2つのファイルをまとめて提出。
(ターン5・6・8・10 は同様の検索の繰り返しのため省略しました。ターン1・2 は、cd . && ls などがシェル演算子として拒否されたものです。)
この探索のポイントは、処理の流れを自律的にたどっていることです。ターン4でリダイレクトの実行箇所(routes/redirect.ts:18)を見つけ、ターン7で、リダイレクト先の検証や許可リストを探しています。そして、redirect.ts の中の security.redirectAllowlist という参照をたどって、ターン9で許可リストの定義(lib/insecurity.ts:122)に到達しています。キーワードにヒットしたファイルを並べただけではありません。
3回の実行結果は次のとおりです。
- 3回とも正解のファイルを含む。
- うち2回は、正解の2ファイルが完全に一致。
- 残る1回は、
routes/redirect.tsのみ提出(片方だけ)。
CWE-601 の提出結果(3回分)
| 回 | 提出されたファイル(順位順) |
|---|---|
| 1回目 | routes/redirect.ts |
| 2回目 |
routes/redirect.ts, lib/insecurity.ts
|
| 3回目 |
routes/redirect.ts, lib/insecurity.ts
|
CWE-601: 全ターンのログ(計3タスクのうち1タスク分を抜粋)
CWE-601 / 最終提出: routes/redirect.ts, lib/insecurity.ts
Antares に渡したユーザープロンプト
Analyze this codebase for the following vulnerability class:
CWE-601: URL Redirection to Untrusted Site ('Open Redirect')
The repository is at /workspace/repo/. Use the terminal tool to explore and determine if this vulnerability exists. Then either submit the vulnerable file(s) or declare no vulnerability found.
ターン1
- Antares の推論(think):
Start by identifying the project structure and key files.
- 実行コマンド:
cd /workspace/repo && ls
- コマンドの出力(Antares に返した内容):
[denied] Shell operators like &&, ||, `, $(), >, >>, ; are not available. Only plain `|` pipes between allowed commands are supported (received: 'cd . && ls')
[14 tool-calls remaining]
ターン2
- Antares の推論(think):
Determine current directory and list files.
- 実行コマンド:
pwd; ls
- コマンドの出力(Antares に返した内容):
[denied] Shell operators like &&, ||, `, $(), >, >>, ; are not available. Only plain `|` pipes between allowed commands are supported (received: 'pwd; ls')
[13 tool-calls remaining]
ターン3
- Antares の推論(think):
Locate files in repository root.
- 実行コマンド:
find . -maxdepth 2 | head -n 200
- コマンドの出力(Antares に返した内容):
.
./vagrant
./vagrant/default.conf
./vagrant/Vagrantfile
./vagrant/bootstrap.sh
./encryptionkeys
./encryptionkeys/jwt.pub
./encryptionkeys/premium.key
./.ai
./.ai/skills
./.codeclimate.yml
./ctf.key
./.codeium
./.codeium/instructions.md
./rsn
./rsn/cache.json
./rsn/rsnOutput.ts
./rsn/rsn.ts
./rsn/rsnUtil.ts
./rsn/rsn-update.ts
./i18n
./i18n/.gitkeep
./docker-compose.test.yml
...
ターン4
- Antares の推論(think):
Locate redirect logic and URL construction code.
- 実行コマンド:
rg -n "redirect\.|redirect\(" .
- コマンドの出力(Antares に返した内容):
./routes/redirect.ts:18: res.redirect(toUrl)
./routes/updateUserProfile.ts:44: res.redirect(process.env.BASE_PATH + '/profile')
./routes/profileImageFileUpload.ts:56: res.redirect(process.env.BASE_PATH + '/profile')
./routes/profileImageUrlUpload.ts:49: res.redirect(process.env.BASE_PATH + '/profile')
[11 tool-calls remaining]
ターン5
- Antares の推論(think):
Locate redirect responses and any validation of redirect targets.
- 実行コマンド:
rg -n "res\.redirect\(" .
- コマンドの出力(Antares に返した内容):
./routes/redirect.ts:18: res.redirect(toUrl)
./routes/updateUserProfile.ts:44: res.redirect(process.env.BASE_PATH + '/profile')
./routes/profileImageFileUpload.ts:56: res.redirect(process.env.BASE_PATH + '/profile')
./routes/profileImageUrlUpload.ts:49: res.redirect(process.env.BASE_PATH + '/profile')
[10 tool-calls remaining]
ターン6
- Antares の推論(think):
Locate redirect implementation and any checks preventing redirects to unsafe URLs.
- 実行コマンド:
rg -n "redirect" routes
- コマンドの出力(Antares に返した内容):
routes/redirect.ts:16: challengeUtils.solveIf(challenges.redirectCryptoCurrencyChallenge, () => { return toUrl === 'https://explorer.dash.org/address/Xr556RzuwX6hg5EGpkybbv5RanJoZN17kW' || toUrl === 'https://blockchain.info/address/1AbKfgvw9psQ41NbLi8kufDQTezwG8DRZm' || toUrl === 'https://etherscan.io/address/0x0f933ab9fcaaa782d0279c300d73750e1311eae6' })
routes/redirec
...
ターン7
- Antares の推論(think):
Locate implementation of redirect target validation or allowlist.
- 実行コマンド:
rg -n "isUnintendedRedirect|redirectAllowlist|redirectAllowlist\.|allowlist" .
- コマンドの出力(Antares に返した内容):
./routes/redirect.ts:17: challengeUtils.solveIf(challenges.redirectChallenge, () => { return isUnintendedRedirect(toUrl) })
./routes/redirect.ts:26:function isUnintendedRedirect (toUrl: string) {
./routes/redirect.ts:28: for (const allowedUrl of security.redirectAllowlist) {
./data/static/i18n/fi_FI.json:511: "You have to find a way to beat the allowlist of allowed re
...
ターン8
- Antares の推論(think):
Locate redirect validation implementation.
- 実行コマンド:
rg -n "isUnintendedRedirect" .
- コマンドの出力(Antares に返した内容):
./routes/redirect.ts:17: challengeUtils.solveIf(challenges.redirectChallenge, () => { return isUnintendedRedirect(toUrl) })
./routes/redirect.ts:26:function isUnintendedRedirect (toUrl: string) {
[7 tool-calls remaining]
ターン9
- Antares の推論(think):
Locate redirect allowlist configuration.
- 実行コマンド:
rg -n "redirectAllowlist" .
- コマンドの出力(Antares に返した内容):
./routes/redirect.ts:28: for (const allowedUrl of security.redirectAllowlist) {
./lib/insecurity.ts:122:export const redirectAllowlist = new Set([
./lib/insecurity.ts:135: for (const allowedUrl of redirectAllowlist) {
[6 tool-calls remaining]
ターン10
- Antares の推論(think):
Locate security constants and redirect allowlist implementation.
- 実行コマンド:
rg -n "redirectAllowlist" lib
- コマンドの出力(Antares に返した内容):
lib/insecurity.ts:122:export const redirectAllowlist = new Set([
lib/insecurity.ts:135: for (const allowedUrl of redirectAllowlist) {
[5 tool-calls remaining]
ターン11
- Antares の推論(think):
Locate redirect validation implementation details.
- 実行コマンド:
cat routes/redirect.ts
- コマンドの出力(Antares に返した内容):
/*
* Copyright (c) 2014-2026 Bjoern Kimminich & the OWASP Juice Shop contributors.
* SPDX-License-Identifier: MIT
*/
import { type Request, type Response, type NextFunction } from 'express'
import * as challengeUtils from '../lib/challengeUtils'
import { challenges } from '../data/datacache'
import * as security from '../lib/insecurity'
export function performRedirect
...
ターン12
- Antares の推論(think):
Locate redirect allowlist implementation and whether it validates schemes/hosts.
- 実行コマンド:
cat lib/insecurity.ts
- コマンドの出力(Antares に返した内容):
/*
* Copyright (c) 2014-2026 Bjoern Kimminich & the OWASP Juice Shop contributors.
* SPDX-License-Identifier: MIT
*/
import fs from 'node:fs'
import crypto from 'node:crypto'
import { type Request, type Response, type NextFunction } from 'express'
import { type UserModel } from '@juice-shop/models/user'
import expressJwt from 'express-jwt'
import jwt from 'jsonwebtoken'
...
ターン13
- Antares の推論(think):
Locate the redirect allowlist source and verify whether it contains only safe URLs.
- ツール呼び出し:
submit_vulnerable_files
{"name": "submit_vulnerable_files", "arguments": {"ranked_files": ["routes/redirect.ts", "lib/insecurity.ts"]}}
解析例2: CWE-327(リスクのある暗号アルゴリズムの利用)
CWE-327の解析では、検索を絞り込んで、本当に危険な1箇所だけを拾い出すという能力を確認できました。あるタスクでの探索ログから、主要なターンを引用します。
[ターン4] think: Locate cryptographic algorithm usage.
$ rg -n "crypto|md5|sha1|sha224|sha256|sha384|sha512|md4|md5-digest|md5-hash|ripemd160|crc32|des|rc4-des|rc4-128|blowfish|cast\(\)|arc4\(\)|sha1\.\.\." .
./terraform/main.tf:7: dockerfile_hash = filemd5("${path.module}/../infrastructure/Dockerfile")
./terraform/main.tf:8: compose_hash = filemd5("${path.module}/../infrastructure/docker-compose.yml")
./terraform/main.tf:103: desired_count = var.desired_count
./terraform/outputs.tf:2: description = "DNS name of the Application Load Balancer"
./terraform/outputs.tf:7...
◆ 暗号関連の語をまとめて検索したが、Terraform の設定など無関係な行が大量にヒットし、絞り込めていない。
[ターン5] think: Locate cryptographic implementations.
$ rg -n "crypto|md5|sha1|sha224|sha256|sha384|sha512" .
./routes/verify.ts:281: { [Op.like]: '%md5%' },
./ftp/package-lock.json.bak:97: "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
./ftp/package-lock.json.bak:111: "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
...
◆ 検索語を減らして再検索。まだ、パッケージの整合性を確認するためのハッシュなど、無関係な行が多い。
[ターン6] think: Locate cryptographic implementations in source files.
$ rg -n "md5" .
./infrastructure/terraform/main.tf:7: dockerfile_hash = filemd5("${path.module}/../Dockerfile")
./infrastructure/terraform/main.tf:8: compose_hash = filemd5("${path.module}/../docker-compose.yml")
./ftp/package-lock.json.bak:10086: "md5-hex",
./ftp/package-lock.json.bak:10116: "md5-hex": "^1.2.0",
...
◆ さらに、最も問題になりやすい `md5` だけに絞って再検索。しかしそれでも無関係な行が残っている。
ターン4〜6では、Terraform の設定(filemd5)やパッケージの整合性ハッシュ(sha512-...)など、暗号アルゴリズムの選択とは無関係な行が大量にヒットしています。これらは、設定ファイルの変更検知や整合性チェックに使われているだけで、仮に MD5 で書かれていたとしてもセキュリティ上の問題にならないものです。そこで Antares は、検索範囲を絞ることにしました。
[ターン7] think: Locate risky cryptographic implementations (MD5, deprecated algorithms) and their uses.
$ rg -n "md5" lib routes frontend infrastructure
infrastructure/terraform/main.tf:7: dockerfile_hash = filemd5("${path.module}/../Dockerfile")
infrastructure/terraform/main.tf:8: compose_hash = filemd5("${path.module}/../docker-compose.yml")
lib/insecurity.ts:41:export const hash = (data: string) => crypto.createHash('md5').update(data).digest('hex')
routes/verify.ts:281: { [Op.like]: '%md5%' },
◆ 検索範囲を、ソースコードのあるディレクトリに絞った。狙いの `lib/insecurity.ts:41`(`createHash('md5')`)が、初めて出力に現れた。(`routes/verify.ts` の `'%md5%'` は、文字列の検索条件であって、暗号処理ではない。)
ターン7で、探したいコード(lib/insecurity.ts:41)が初めて出力に現れました。ターン8〜12では、sha1 / sha256 など他のアルゴリズムの確認(lib/utils.ts:80 の createHmac('sha1', ...) も発見)と、同様の md5 検索を繰り返しています。
[ターン13] think: Locate any risky cryptographic algorithm usage in source files.
$ grep -RIn "md5" lib
lib/insecurity.ts:41:export const hash = (data: string) => crypto.createHash('md5').update(data).digest('hex')
◆ 検索範囲を `lib` だけに絞ると、該当する1行だけが残った。
[ターン14] think: lib/insecurity.ts implements MD5 and exposes a hash function using crypto.createHash('md5'), which is a broken/risky cryptographic algorithm.
submit_vulnerable_files(["lib/insecurity.ts"])
◆ 「MD5 はリスクの高い暗号アルゴリズム」と判断し、`lib/insecurity.ts` を提出。
検索範囲を段階的に絞り込み、ターン13で本当に危険な1行だけを拾い出しています。また、最後の14ターン目では、'insecurity.ts'がリスクの高いMD5を使ったハッシュ関数である旨まで正しく説明できています。
この例では cat でファイル全体を読んでいる訳では無く、grep の1行の出力だけを根拠に結論を出しています。今回は関数の定義が1行で完結していたため、catを使わずとも、該当箇所をAntaresに送信することができた形となります。
CWE-327 の提出結果(3回分)
| 回 | 提出されたファイル(順位順) |
|---|---|
| 1回目 | lib/insecurity.ts |
| 2回目 |
lib/insecurity.ts, lib/utils.ts, scripts/package.mjs, routes/verify.ts, ftp/package-lock.json.bak
|
| 3回目 |
lib/utils.ts, lib/insecurity.ts, models/feedback.ts, models/product.ts, frontend/src/styles.scss
|
2回目と3回目のタスクでは、どちらも正解の lib/insecurity.ts を含みつつも、5件ずつ別のファイルを提出していました。3回目は lib/utils.ts(sha1 を使う箇所)を第一候補として提出し、lib/insecurity.ts は第二候補として提出していました。提出リストの中には、暗号と関係のなさそうな frontend/src/styles.scss なども含まれています。
CWE-327: 全ターンのログ(計3タスクのうち1タスク分を抜粋)
CWE-327 / 最終提出: lib/insecurity.ts
Antares に渡したユーザープロンプト
Analyze this codebase for the following vulnerability class:
CWE-327: Use of a Broken or Risky Cryptographic Algorithm
The repository is at /workspace/repo/. Use the terminal tool to explore and determine if this vulnerability exists. Then either submit the vulnerable file(s) or declare no vulnerability found.
ターン1
- Antares の推論(think):
Start by identifying the project structure and key files.
- 実行コマンド:
cd /workspace/repo && ls
- コマンドの出力(Antares に返した内容):
[denied] Shell operators like &&, ||, `, $(), >, >>, ; are not available. Only plain `|` pipes between allowed commands are supported (received: 'cd . && ls')
[14 tool-calls remaining]
ターン2
- Antares の推論(think):
Determine current directory and list files.
- 実行コマンド:
pwd; ls
- コマンドの出力(Antares に返した内容):
[denied] Shell operators like &&, ||, `, $(), >, >>, ; are not available. Only plain `|` pipes between allowed commands are supported (received: 'pwd; ls')
[13 tool-calls remaining]
ターン3
- Antares の推論(think):
Locate source files.
- 実行コマンド:
find . -maxdepth 2 | head -n 200
- コマンドの出力(Antares に返した内容):
.
./vagrant
./vagrant/default.conf
./vagrant/Vagrantfile
./vagrant/bootstrap.sh
./encryptionkeys
./encryptionkeys/jwt.pub
./encryptionkeys/premium.key
./.ai
./.ai/skills
./.codeclimate.yml
./ctf.key
./.codeium
./.codeium/instructions.md
./rsn
./rsn/cache.json
./rsn/rsnOutput.ts
./rsn/rsn.ts
./rsn/rsnUtil.ts
./rsn/rsn-update.ts
./i18n
./i18n/.gitkeep
./docker-compose.test.yml
...
ターン4
- Antares の推論(think):
Locate cryptographic algorithm usage.
- 実行コマンド:
rg -n "crypto|md5|sha1|sha224|sha256|sha384|sha512|md4|md5-digest|md5-hash|ripemd160|crc32|des|rc4-des|rc4-128|blowfish|cast\(\)|arc4\(\)|sha1\.\.\." .
- コマンドの出力(Antares に返した内容):
./terraform/main.tf:7: dockerfile_hash = filemd5("${path.module}/../infrastructure/Dockerfile")
./terraform/main.tf:8: compose_hash = filemd5("${path.module}/../infrastructure/docker-compose.yml")
./terraform/main.tf:103: desired_count = var.desired_count
./terraform/outputs.tf:2: description = "DNS name of the Application Load Balancer"
./terraform/outputs.tf:7
...
ターン5
- Antares の推論(think):
Locate cryptographic implementations.
- 実行コマンド:
rg -n "crypto|md5|sha1|sha224|sha256|sha384|sha512" .
- コマンドの出力(Antares に返した内容):
./routes/verify.ts:281: { [Op.like]: '%md5%' },
./ftp/package-lock.json.bak:97: "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
./ftp/package-lock.json.bak:111: "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
./ftp/package-lock.json.bak
...
ターン6
- Antares の推論(think):
Locate cryptographic implementations in source files.
- 実行コマンド:
rg -n "md5" .
- コマンドの出力(Antares に返した内容):
./infrastructure/terraform/main.tf:7: dockerfile_hash = filemd5("${path.module}/../Dockerfile")
./infrastructure/terraform/main.tf:8: compose_hash = filemd5("${path.module}/../docker-compose.yml")
./ftp/package-lock.json.bak:10086: "md5-hex",
./ftp/package-lock.json.bak:10116: "md5-hex": "^1.2.0",
./ftp/package-lock.json.bak:10540: "md5-hex": "^
...
ターン7
- Antares の推論(think):
Locate risky cryptographic implementations (MD5, deprecated algorithms) and their uses.
- 実行コマンド:
rg -n "md5" lib routes frontend infrastructure
- コマンドの出力(Antares に返した内容):
infrastructure/terraform/main.tf:7: dockerfile_hash = filemd5("${path.module}/../Dockerfile")
infrastructure/terraform/main.tf:8: compose_hash = filemd5("${path.module}/../docker-compose.yml")
lib/insecurity.ts:41:export const hash = (data: string) => crypto.createHash('md5').update(data).digest('hex')
routes/verify.ts:281: { [Op.like]: '%md5%' },
[8 tool-calls
...
ターン8
- Antares の推論(think):
Locate other risky algorithms or broken crypto constructs.
- 実行コマンド:
rg -n "sha1|sha224|sha256|sha384|sha512" lib routes frontend infrastructure
- コマンドの出力(Antares に返した内容):
lib/insecurity.ts:42:export const hmac = (data: string) => crypto.createHmac('sha256', 'pa4qacea4VK9t9nGv7yZtwmj').update(data).digest('hex')
lib/insecurity.ts:150: const hmac = crypto.createHmac('sha256', privateKey)
lib/utils.ts:80: return crypto.createHmac('sha1', getCtfKey()).update(text).digest('hex')
[7 tool-calls remaining]
ターン9
- Antares の推論(think):
Locate broken or deprecated cryptographic algorithms and their uses.
- 実行コマンド:
rg -n "md5|sha1|sha224|sha256|sha384|sha512|md4|md5-digest|md5-hash|ripemd160|crc32" lib routes frontend infrastructure
- コマンドの出力(Antares に返した内容):
infrastructure/terraform/main.tf:7: dockerfile_hash = filemd5("${path.module}/../Dockerfile")
infrastructure/terraform/main.tf:8: compose_hash = filemd5("${path.module}/../docker-compose.yml")
lib/insecurity.ts:41:export const hash = (data: string) => crypto.createHash('md5').update(data).digest('hex')
lib/insecurity.ts:42:export const hmac = (data: string) => crypt
...
ターン10
- Antares の推論(think):
Locate all MD5 and SHA1 usage in source files.
- 実行コマンド:
grep -RIn "md5\.|sha1\.|sha224\.|sha256\.|sha384\.|sha512\.|md4" lib routes frontend infrastructure
- コマンドの出力(Antares に返した内容):
(no output)
[5 tool-calls remaining]
ターン11
- Antares の推論(think):
Locate MD5 implementation and usage.
- 実行コマンド:
grep -RIn "md5" lib routes frontend infrastructure
- コマンドの出力(Antares に返した内容):
lib/insecurity.ts:41:export const hash = (data: string) => crypto.createHash('md5').update(data).digest('hex')
routes/verify.ts:281: { [Op.like]: '%md5%' },
infrastructure/terraform/main.tf:7: dockerfile_hash = filemd5("${path.module}/../Dockerfile")
infrastructure/terraform/main.tf:8: compose_hash = filemd5("${path.module}/../docker-compose.yml")
[4 tool-calls
...
ターン12
- Antares の推論(think):
Locate SHA1 implementation and usage.
- 実行コマンド:
grep -RIn "sha1" lib routes frontend infrastructure
- コマンドの出力(Antares に返した内容):
lib/utils.ts:80: return crypto.createHmac('sha1', getCtfKey()).update(text).digest('hex')
[3 tool-calls remaining]
ターン13
- Antares の推論(think):
Locate any risky cryptographic algorithm usage in source files.
- 実行コマンド:
grep -RIn "md5" lib
- コマンドの出力(Antares に返した内容):
lib/insecurity.ts:41:export const hash = (data: string) => crypto.createHash('md5').update(data).digest('hex')
[2 tool-calls remaining]
ターン14
- Antares の推論(think):
lib/insecurity.ts implements MD5 and exposes a hash function using crypto.createHash('md5'), which is a broken/risky cryptographic algorithm.
- ツール呼び出し:
submit_vulnerable_files
{"name": "submit_vulnerable_files", "arguments": {"ranked_files": ["lib/insecurity.ts"]}}
解析例3: CWE-89(SQLインジェクション)
脆弱性の概要
SQLインジェクションは、ユーザーが入力した文字列を、そのままデータベースへの命令文(SQL)に埋め込んでしまうことで起きる脆弱性です。
データベースは「どこまでが入力データで、どこからが命令か」を区別できず、入力の中に紛れ込ませた命令を実行してしまいます。結果として、他人のデータの閲覧、ログイン認証の回避、データの改ざん・削除などを引き起こしてしまうリスクがあります。
Juice Shop のどこが問題か
問題の箇所は、商品の検索とログインの2か所です。コードブロックは、実際のコードの引用です(CWE-601 と同様、公開版との違いは vuln-code-snippet というヒント用コメントの有無のみ)。
① 商品検索(routes/search.ts 19〜23行目)
export function searchProducts () {
return (req: Request, res: Response, next: NextFunction) => {
let criteria: any = req.query.q === 'undefined' ? '' : req.query.q ?? ''
criteria = (criteria.length <= 200) ? criteria : criteria.substring(0, 200)
models.sequelize.query(`SELECT * FROM Products WHERE ((name LIKE '%${criteria}%' OR description LIKE '%${criteria}%') AND deletedAt IS NULL) ORDER BY name`)
- 21行目:
criteriaは、検索欄に入力された文字列(req.query.q)そのものです。 - 23行目: その
criteriaが${criteria}で、SQL 文の中にそのまま埋め込まれています(パラメータ化されていません)。
② ログイン(routes/login.ts 32〜34行目)
return (req: Request, res: Response, next: NextFunction) => {
verifyPreLoginChallenges(req)
models.sequelize.query(`SELECT * FROM Users WHERE email = '${req.body.email || ''}' AND password = '${security.hash(req.body.password || '')}' AND deletedAt IS NULL`, { model: UserModel, plain: true })
- 34行目: ログイン画面のメールアドレス(
req.body.email)が、SQL 文のemail = '...'の中にそのまま埋め込まれています。 - パスワードは
security.hash(...)を通してから埋め込まれます。このhashは、CWE-327 の節に出てくる MD5 のハッシュ関数と同じものです(lib/insecurity.ts:41)。 - 但しハッシュ化された文字列には SQL の記号は含まれないため、攻撃に使用されるリスクが高いのはメールアドレス欄の方です。
攻撃の流れ(時系列)
ログイン画面を通じて脆弱性を突かれてしまう例です。
- 攻撃者が、ログイン画面のメールアドレス欄に
' OR 1=1--と入力する(パスワードは何でもよい)。 - サーバーは、この文字列を
email = '...'の位置にそのまま埋め込む。できあがる SQL は次のとおり。SELECT * FROM Users WHERE email = '' OR 1=1--' AND password = '...' AND deletedAt IS NULL -
--以降は SQL のコメントなので、パスワードの条件が無視される。OR 1=1は常に真なので、条件が全ユーザーに一致する。 - 先頭のユーザー(Juice Shop では管理者アカウント)として認証され、攻撃者が管理者としてログインできてしまう。
本来は、入力値を SQL の文字列に連結させず、パラメータ化されたクエリ(プレースホルダ)として渡すべきです。
Antares はどう見つけたか
Antaresが実際の脆弱なSQL文字列を見つけた場面を抜粋します。
[ターン7] think: Locate database driver or query construction code.
$ rg "\.query\(" .
./routes/search.ts: models.sequelize.query(`SELECT * FROM Products WHERE ((name LIKE '%${criteria}%' OR description LIKE '%${criteria}%') AND deletedAt IS NULL) ORDER BY name`)
./routes/search.ts: void models.sequelize.query('SELECT sql FROM sqlite_master').then(([data]: any) => {
...
◆ DB への問い合わせ(`.query(`)を検索。`routes/search.ts` の1行目は、検索語 `criteria`(ユーザー入力)を `${criteria}` で SQL 文字列に直接埋め込んでしまっている。
※尚、このタスクの序盤には、find . -name '*.ts' | xargs grep -n "sql\.|query\.|execute\(" の出力が grep: Unmatched ( or \( となって失敗している様子がありました。これは正規表現の書き間違いであり、Antaresは度々このような間違いをしてしまうようです。
最終ターン(ターン15)の推論も、次のように正しく指摘できていました。
The backend uses Sequelize to construct SQL queries with interpolated variables
(`name LIKE '%${criteria}%'`, `email = '${req.body.email || ''}'`, etc.). These are
not parameterized, so user-controlled values can inject SQL. The vulnerable query
construction is in routes/search.ts and login.ts, ...
提出されたファイルは routes/search.ts / routes/login.ts / server.ts で、前の2つが正解です(server.ts は余分です)。
CWE-89 の提出結果(3回分)
| 回 | 提出されたファイル(順位順) |
|---|---|
| 1回目 | routes/search.ts |
| 2回目 |
routes/search.ts, routes/login.ts, server.ts
|
| 3回目 | 実在しないファイル名を提出 |
3回目では、実際には存在しないファイル名を提出してしまっていました。
CWE-89: 全ターンのログ(計3タスクのうち1タスク分を抜粋)
CWE-89 / 最終提出: routes/search.ts, routes/login.ts, server.ts
Antares に渡したユーザープロンプト
Analyze this codebase for the following vulnerability class:
CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
The repository is at /workspace/repo/. Use the terminal tool to explore and determine if this vulnerability exists. Then either submit the vulnerable file(s) or declare no vulnerability f
ターン1
- Antares の推論(think):
Start by identifying the project structure and key files.
- 実行コマンド:
cd /workspace/repo && ls
- コマンドの出力(Antares に返した内容):
[denied] Shell operators like &&, ||, `, $(), >, >>, ; are not available. Only plain `|` pipes between allowed commands are supported (received: 'cd . && ls')
[14 tool-calls remaining]
ターン2
- Antares の推論(think):
Determine current directory and list files.
- 実行コマンド:
pwd; ls
- コマンドの出力(Antares に返した内容):
[denied] Shell operators like &&, ||, `, $(), >, >>, ; are not available. Only plain `|` pipes between allowed commands are supported (received: 'pwd; ls')
[13 tool-calls remaining]
ターン3
- Antares の推論(think):
Locate source files.
- 実行コマンド:
find . -maxdepth 2 | rg 'src|app|server|database|query|orm'
- コマンドの出力(Antares に返した内容):
./server.ts
./infrastructure/terraform
./frontend/src
./views/dataErasureForm.hbs
./terraform
./terraform/networking.tf
./terraform/variables.tf
./terraform/outputs.tf
./terraform/main.tf
./app.json
./routes/appVersion.ts
./routes/appConfiguration.ts
./routes/countryMapping.ts
./app.ts
[12 tool-calls remaining]
ターン4
- Antares の推論(think):
Locate database access code and query construction.
- 実行コマンド:
find . -name '*.ts' | xargs grep -n "sql\.|query\.|execute\("
- コマンドの出力(Antares に返した内容):
grep: Unmatched ( or \(
[11 tool-calls remaining]
ターン5
- Antares の推論(think):
Locate TypeScript source files.
- 実行コマンド:
find . -maxdepth 2 | grep -E "src|app|server|database|query|orm"
- コマンドの出力(Antares に返した内容):
./server.ts
./infrastructure/terraform
./frontend/src
./views/dataErasureForm.hbs
./terraform
./terraform/networking.tf
./terraform/variables.tf
./terraform/outputs.tf
./terraform/main.tf
./app.json
./routes/appVersion.ts
./routes/appConfiguration.ts
./routes/countryMapping.ts
./app.ts
[10 tool-calls remaining]
ターン6
- Antares の推論(think):
Locate backend source files.
- 実行コマンド:
find . -name '*.ts' | head -n 200
- コマンドの出力(Antares に返した内容):
./rsn/rsnOutput.ts
./rsn/rsn.ts
./rsn/rsnUtil.ts
./rsn/rsn-update.ts
./server.ts
./models/imageCaptcha.ts
./models/basket.ts
./models/memory.ts
./models/recycle.ts
./models/card.ts
./models/user.ts
./models/complaint.ts
./models/wallet.ts
./models/basketitem.ts
./models/challenge.ts
./models/privacyRequests.ts
./models/captch
...
ターン7
- Antares の推論(think):
Locate database driver or query construction code.
- 実行コマンド:
rg "\.query\(" .
- コマンドの出力(Antares に返した内容):
./routes/search.ts: models.sequelize.query(`SELECT * FROM Products WHERE ((name LIKE '%${criteria}%' OR description LIKE '%${criteria}%') AND deletedAt IS NULL) ORDER BY name`)
./routes/search.ts: void models.sequelize.query('SELECT sql FROM sqlite_master').then(([data]: any) => {
./routes/login.ts: models.sequ
...
ターン8
- Antares の推論(think):
Locate SQL string construction and parameterization usage.
- 実行コマンド:
rg "\$\{|\"\\.|\.query\(" .
- コマンドの出力(Antares に返した内容):
./models/user.ts: `acc0unt4nt@${config.get<string>('application.domain')}`.toLowerCase()
./infrastructure/terraform/main.tf: dockerfile_hash = filemd5("${path.module}/../Dockerfile")
./infrastructure/terraform/main.tf: compose_hash = filemd5("${path.module}/../docker-compose.yml")
./infrastructure/terraform/main
...
ターン9
- Antares の推論(think):
Locate backend query construction and whether parameters are used or interpolated unsafely.
- 実行コマンド:
rg "query\(" . | head -n 200
- コマンドの出力(Antares に返した内容):
./routes/search.ts: models.sequelize.query(`SELECT * FROM Products WHERE ((name LIKE '%${criteria}%' OR description LIKE '%${criteria}%') AND deletedAt IS NULL) ORDER BY name`)
./routes/search.ts: void models.sequelize.query('SELECT sql FROM sqlite_master').then(([data]: any) => {
./routes/login.ts: models.sequ
...
ターン10
- Antares の推論(think):
Locate all backend query calls and whether they use parameter binding.
- 実行コマンド:
rg "\.query\(" .
- コマンドの出力(Antares に返した内容):
./frontend/src/app/chatbot/chat-conversation/chat-conversation.component.spec.ts: const chatInputDebug = fixture.debugElement.query((de) => de.componentInstance instanceof ChatInputBoxComponent)
./frontend/src/app/chatbot/chat-conversation/chat-conversation.component.spec.ts: const chatInputDebug = fixture.debu
...
ターン11
- Antares の推論(think):
Locate backend database access implementation.
- 実行コマンド:
rg "sequelize\.|db\.query\.|query\(" .
- コマンドの出力(Antares に返した内容):
./routes/search.ts: models.sequelize.query(`SELECT * FROM Products WHERE ((name LIKE '%${criteria}%' OR description LIKE '%${criteria}%') AND deletedAt IS NULL) ORDER BY name`)
./routes/search.ts: void models.sequelize.query('SELECT sql FROM sqlite_master').then(([data]: any) => {
./routes/login.ts: models.seq
...
ターン12
- Antares の推論(think):
Locate backend database module and query execution implementation.
- 実行コマンド:
rg "models\.|db\.|sequelize\.|query\(" .
- コマンドの出力(Antares に返した内容):
./routes/dataExport.ts: orders = await db.ordersCollection.find({ email: updatedEmail })
./routes/dataExport.ts: reviews = await db.reviewsCollection.find({ author: email })
./routes/trackOrder.ts: db.ordersCollection.find({ $where: `this.orderId === '${id}'` }).then((order: any) => {
./routes/search.ts:
...
ターン13
- Antares の推論(think):
Locate backend database driver implementation and whether it supports parameterized queries.
- 実行コマンド:
rg "sequelize\.|db\.|query\(" rsn routes server.ts
- コマンドの出力(Antares に返した内容):
server.ts:while (!expectedModels.every(model => Object.keys(sequelize.models).includes(model))) {
server.ts: logger.info(`Entity models ${colors.bold(Object.keys(sequelize.models).length.toString())} of ${colors.bold(expectedModels.length.toString())} are initialized (${colors.yellow('WAITING')})`)
server.ts:logger.info(`En
...
ターン14
- Antares の推論(think):
Locate the backend database connection and query execution implementation.
- 実行コマンド:
rg "sequelize\.|db\.|query\(" rsn routes server.ts
- コマンドの出力(Antares に返した内容):
server.ts:while (!expectedModels.every(model => Object.keys(sequelize.models).includes(model))) {
server.ts: logger.info(`Entity models ${colors.bold(Object.keys(sequelize.models).length.toString())} of ${colors.bold(expectedModels.length.toString())} are initialized (${colors.yellow('WAITING')})`)
server.ts:logger.info(`En
...
ターン15
- Antares の推論(think):
The backend uses Sequelize to construct SQL queries with interpolated variables (`name LIKE '%${criteria}%'`, `email = '${req.body.email || ''}'`, etc.). These are not parameterized, so user-controlled values can inject SQL. The vulnerable query construction is in routes/search.ts and login.ts, ...
3つのCWEの結果まとめ
| CWE | 内容 | 結果(3回中) |
|---|---|---|
| 601 | オープンリダイレクト | 3回とも正解を含む(2回は2ファイルとも、1回は routes/redirect.ts のみ) |
| 327 | 弱いMD5 | 3回とも正解を含む(1回は単独、2回は他のファイルも併記) |
| 89 | SQLインジェクション | 2回が正解を含む(うち1回は2ファイルとも)、残り1回は実在しないファイルのみ提出 |
なお、同じ条件で解析した他の2つの脆弱性 CWE-94(コードインジェクション)と CWE-918(SSRF) は、各3回とも正解のファイルを特定できませんでした。
検証から見えたAntaresの得意/不得意
検知が得意なもの
-
答えに至るまでの処理の呼び出し回数が少ないもの。
例: 一回のgrepの入力のみで危険な処理が出力されるコード(md5を使うハッシュ関数、SQL 文字列の結合など) -
CWE 名から検索ワードを連想しやすい脆弱性。
例:-「リスクのある暗号アルゴリズム」からmd5/sha1を検索
-「オープンリダイレクト」からredirectを検索
検知が苦手/不可のもの
- ソースコードが読めないファイル(コンパイル済みのバイナリファイル等)は、そもそも検知の対象外。
- 検索コマンドの初歩的な文法ミスが、それなりに発生する。
- 「このファイルが該当している CWE を教えて」という書き方の問(オープンクエスチョン)には対応できず、判定対象のCWEを事前に決めた上でAntaresに渡してあげる必要がある。
- 明らかに見当違いな CWE について該当の有無を聞くと、高確率で偽陽性判定になる。
実務でどう使えるか
検証結果を踏まえると、Antares 単体で脆弱性該当の有無を確定させる運用は推奨されず、あくまで人間のレビューの前段として使用するのが現実的と思いました。
- 解析依頼: ECサイトの特定機能の調査依頼を、Claude Code などの LLM に送る。
- プロンプトの準備: LLM が、その機能に存在しがちな CWE を絞り込む。
- プロンプトの送信: 絞り込んだ CWE と、解析対象のディレクトリを Antares に送る。
- Antares による解析: その CWE への該当可能性と、被疑ファイルを報告する。
- 脆弱性判定: 提示されたファイルの中身を人間の目でレビューし、該当有無を最終判断する。
Antares は「CWE を仮説として渡す」必要があるため、どの CWE を聞くかを決める前段(②)が結果を左右します。ここは人間または汎用 LLM が補う設計にしています。
活用のアイデア: 自社環境のスキャンを CI/CD に
昨今は、各社が運営する EC サイトの脆弱性が発見され、攻撃されたというニュースを、毎日のように見かけます。
Antares は、CPU のみの環境でも、実運用に耐える速度で動作します。この Antares を使って、自社の環境をスキャンする CI/CD フローを確立することが、対処策の一部として使えるかもしれません。
まとめ
- Antares(1B)は、GPU なしの CPU のみの物理サーバーでも、実用的な速度で動かせた。公式ベンチマークでは、多くの汎用モデルを上回る精度を、小さなモデルで実現している。
- 機密性の高いソースコードを外部のクラウドに送らず、自社の物理サーバー内だけでスキャンを完結できる。
- Juice Shop(963ファイル)では、5種の CWE のうち3種(601 / 327 / 89)で、正解のファイルに到達できた。
- CWE-601 では、リダイレクトの実行箇所から許可リストの定義まで、処理の流れを自律的にたどって正しい2ファイルに到達できた。
- 一方で、同じ条件で解析した CWE-94(コードインジェクション)と CWE-918(SSRF)は、正解のファイルを特定できなかった。
- CPU のみで動く軽さを生かして、自社環境のスキャンを CI/CD フローに組み込む使い方も考えられる。
- Antaresが提出したファイルはあくまで人間がレビューする前提と捉えた方が良い。とはいえ、Antares は大量のコードから候補を絞り込む前段のツールとしては一定の価値がある。