記事一覧に戻る
guide2026年5月10日20分で読めます

Aikido で自分のサイトをスキャンしたら、放置していた宿題が発覚した話 — Domain と API、両方やってみた

Aikido のダッシュボードを久しぶりに開いたら自分のサイトが Domain not accessible に。通知設定の漏れで cert 切れに気付けなかった反省と、ついでに API DAST を試した結果。Domain と API の違い、認証セットアップでハマった話、検出された 1 件の Issue まで、実体験を整理した記事。

AikidoDASTAPI SecurityDomain MonitoringSurface MonitoringOpenAPI

発端

「Aikido の API DAST、まだ触ったことないな」と思って app.aikido.dev を開いた。

数ヶ月前に登録したまま放置していた Domain がいくつか並んでいた。何の気なしにこのサイト(aikido-community.jp)の行を見ると、赤いバッジで "Domain not accessible" と表示されていた。

Aikido の Domain 一覧 — aikido-community.jp が "Domain not accessible" 表示

冷や汗。

ダメな例:Aikido は教えてくれていたのに

詳細を開くと、複数の Findings。中でも目立つのは High: Domain SSL Certificate Expiration。

5件の Findings、SSL Certificate Expiration が High

確認すると、証明書は前日に切れていた。

notBefore=Feb  7 08:01:07 2026 GMT
notAfter=May  8 08:01:06 2026 GMT

調査すると:

  • certbot コンテナは docker-compose.yml に定義されていた
  • でも crontab -l は空っぽ
  • 「初回は手動で発行したけど、自動更新の cron を組み忘れていた」のである

90日経って、当然のように切れた。

ここで本当のダメさ:Aikido の通知(Email Forwarding / Slack / Teams 等)は何も設定していなかった。検知能力があっても、自分に届く経路を組んでいなければ知りようがない。ツール導入 ≠ 運用を地で行く状況だった。

復旧、ただし完璧ではない

certbot renew を手動で叩いて復旧。同時に cron も追加して自動更新を仕込んだ。

復旧後、Critical で出ていた CSP 抜けや X-Frame、X-Powered-By、Server header 漏洩も対応した。

ただし、全部は潰せていない。CSP 系で High が1件残っている(インライン JavaScript を許容しているため)。これは大量の onclick 属性を data-action に書き換える必要があり、別タスクとして切り出して別記事で取り組む予定。

つまり、Critical は対応したが Findings の総数は思ったほど減っていない。中身は変わったが、見た目の数値は微減。これも現実。

ついでに API DAST も試してみた

ここまでは Domain(Front-End / Surface Monitoring) のスキャン。Aikido のもう一つの目玉、API Scanning も試してみることにした。

ターゲットは個人で運用している古い検証用 API。Express + PostgreSQL 構成で、20以上のエンドポイントの REST API がある。本番サービスではない。

Domain と API、何が違うか

Domain (Front-End)API Scanning
やってることヘッダ・DNS・証明書・設定の検査実際に攻撃ペイロードをファジング
本番で実行OK(非破壊的)NG(必ず staging)
何を見つける設定漏れ、cert 期限、ヘッダ抜けなどSQLi、NoSQLi、IDOR、コマンド注入、認証バイパスなど
時間数十秒〜数分数分〜数十分
料金プランFree でも一部利用可Attack スイート(有料)

つまり 「Domain は健康診断」「API は実弾の侵入テスト」。並行で使うと Attack Surface の網羅率が上がる。

API DAST のやり方

Step 1: OpenAPI スペックを用意

API DAST は「どこに何を撃つか」を知る必要がある。Aikido の選択肢:

  • Zen 連携: 実トラフィックから自動発見(最も精度が高い)
  • Aikido AI: GitHub 連携でコードから自動生成
  • URL から fetch: 自社で公開しているスペック URL
  • Manual upload: 手書きの YAML をアップロード

今回はルート定義から手書きで openapi.yaml を作成。

openapi: 3.0.0
info:
  title: Sample API
  version: 1.0.0
servers:
  - url: https://example.com
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
paths:
  /api/match/check:
    get:
      security: [{bearerAuth: []}]
      ...

20数エンドポイント分で約200行。

Step 2: Domain として登録

Aikido ダッシュボード → Add Domain → Type: REST API → URL 入力 → Upload openAPI spec manually。

「Do NOT run REST API checks on production. Use staging only.」と警告が出る。必ず staging(または無人の検証環境)で実行する。本番でファジングを撃つと DB にゴミレコードが大量に入るし、決済処理を誤発火する可能性もある。

Step 3: 認証 — ここがハマりどころ

API のほとんどが JWT 認証だったので Auth via Custom Headers を選択。フォームには2つの欄がある:

  • Header name(ヘッダ名)
  • Header value(ヘッダ値)

正解の入力:

欄入力値
Header nameAuthorization
Header valueBearer eyJhbGc...(実トークン)

ここが重要。Bearer は value 側に入れる。Header name は単なるヘッダ名で、スキーム(Bearer)と一緒に書かない。

最初これを取り違えてハマった。後述する。

JWT トークンの取得方法:

  1. ブラウザでアプリにログイン
  2. F12 → Console タブ
  3. copy(localStorage.getItem('token')) を実行
  4. クリップボードに完全な JWT がコピーされる

これを Custom Header の value に貼り付けて、頭に Bearer を付け、Confirm Authentication → Start scan。

Aikido の Auth via Custom Headers 設定画面

認証の罠:自分でハマった話

最初のスキャンで 1067リクエスト中 832 が 401 Unauthorized。Authentication Failed バナーが出た。

1067 リクエストのうち 832 が 401 Unauthorized になった状態

curl で同じトークンを直接叩くと 200 OK で通る。なのに Aikido からは全部 401。

Request Logs から1件開いて確認:

"authorization ": "Bearer eyJh********vG1W0"
        ↑ 末尾に半角スペース

ヘッダ名 authorization の後ろに見えないスペースが1個入っていた。Express は req.headers['authorization'](スペース無し)を見るので、authorization (スペース付き)は別物として無視される。だから "No token provided" のエラーが返っていた。

原因は私のミス。最初に Header name 欄に Authorization Bearer と書いていた(value 欄に入れるべき Bearer を name 欄に書いてしまった)。あとから「Bearer」を削除したら、間にあったスペースだけ取り残されて、Authorization というヘッダ名で保存されていた。

教訓は単純:Bearer は value 欄、name 欄は Authorization だけ。これが正しい使い方。

地味だけど、curl では再現しない、GUI 越しで初めて引っかかるタイプのバグ。Aikido の Request Logs 機能が無かったら原因特定はかなり困難だっただろう。

ヘッダ名フィールドを再入力して保存 → 再スキャンで認証通過。

認証修正後、1 issue を検出した状態

API DAST が見つけたもの

スキャン結果として Aikido が「Issue」として正式に上げたのは1件だけ だった。

NoSQL Injection via body parameter(High Risk, Score 75)

3エンドポイントが該当:

  • /api/ai/tts の text フィールド
  • /api/user/profile の nickname フィールド
  • /api/ai/message の message フィールド

検出パターンは共通:文字列が来るべきフィールドに、NoSQL 演算子付きオブジェクトを送る。

{
    "text": {
        "$nonexistentoperator": "function() { return 1; }"
    }
}

これに対して全エンドポイントが 500 を返した。Aikido の TL;DR:

A NoSQL injection vulnerability was discovered, which allows an attacker to exfiltrate data from your NoSQL database.

修正方針:

if (typeof text !== 'string') {
  return res.status(400).json({ error: 'Invalid text' });
}

23 routes に対して Issue 1件(3 sub-issue)。

この Finding を見て気になった3点

そのまま受け取る前に、見ながら考えたことがある。

1. このアプリは NoSQL を使っていない

PostgreSQL バックエンド。Redis も使っているがセッションキャッシュ用途で、ユーザー入力を直接クエリに渡す経路ではない。/api/ai/tts に至っては DB すら触らず、text を OpenAI の Audio API に丸投げするだけ:

const response = await fetch('https://api.openai.com/v1/audio/speech', {
    body: JSON.stringify({ input: text, ... })  // ← OpenAI に投げるだけ
});

「NoSQL injection」というラベルはこのアプリの構成と合っていない。

2. これはむしろ OpenAPI / バリデーションの問題

Aikido に渡した openapi.yaml で各フィールドを type: string と宣言した。が、Express は OpenAPI スキーマを自動で強制しない。スキーマはドキュメントとして渡しているだけで、リクエストが実際にスキーマ通りかは別途バリデーションが必要。

DAST が「object を投げたら 500 になる」と検出したのは、要するに OpenAPI で宣言した型契約と、backend の実装が一致していない という話。これは「injection」というより「スキーマ契約違反 / 入力検証欠如」と呼ぶ方が正確。

3. 500 のレスポンスを見ても、何が脆弱性なのかは見えない

/api/ai/tts の実際の応答:

{"error": "TTS failed"}

データ漏洩なし、認証バイパスなし、ただ落ちてるだけ。Aikido の TL;DR は「データを exfiltrate される可能性」と書いているが、このアプリで成立する経路は今回見当たらない。

つまり

ラベルは違うが、検出している本質は 「入力の型バリデーション欠如」 で、これは事実。修正方針も Aikido の言う通り正しい。型チェック1行で 500 が 400 になり、Issue は消える。

ただ「NoSQL injection の脆弱性が3件あった」と読者が受け取ると、別の場所(DB 周り)を直そうとする可能性がある。Aikido の Findings は中身を読んで自分のスタックに翻訳するプロセスが要る、というのが今回の発見。

わかったこと

3点に集約:

1. Domain と API、両方やる価値がある

Domain は cert 期限・ヘッダ抜けなどの広く浅い健康診断で、本番 OK。運用ミスを継続的に検知する装置として最適。

API は実弾で深く狭い検証。staging 必須だが、入力検証漏れ・認証バイパス・IDOR など、コードレビューでは見逃しがちな問題を機械的に発見できる。

両方を組み合わせて初めて Attack Surface の網羅率が上がる。

2. Aikido は「気付くきっかけ」をくれる装置で、対応するのは人間

私の cert が切れた最大の原因は「Aikido の通知経路を何も設定していなかった」こと。Slack も Email Forwarding も Teams も、Settings の Chat & Alerts は全部未接続だった。ダッシュボードに行けば High Findings は出ていたが、自分から見にいかない限り気付けない状態。ツールは入れただけでは仕事をしない。導入と通知設定はセット。

Settings → Chat & Alerts、すべて未接続だった

3. DAST のラベルは「翻訳」が要る

Aikido が出した「NoSQL injection」というラベルは、汎用的な検出ロジック(オブジェクトを送って 500 を観測)の名前であって、実際にそのアプリで NoSQL injection が成立するとは限らない。今回がまさにそれで、PostgreSQL バックエンドかつ DB を触らないエンドポイントにまで「NoSQL injection」のラベルが付いていた。

DAST は 自動・継続・広範囲で既知の攻撃パターンを機械的に試す装置。検出するパターン自体は妥当だが、ラベルを自分のスタックに翻訳して、本当の問題が何かを見極めるのは人間の仕事。

ビジネスロジックの欠陥・複雑な認証フロー・複数ステップを連鎖させる攻撃など、DAST には検出できない領域もある。深い脆弱性を本気で探すなら、人間の侵入テスト(Pentest)が必要。Aikido も Pentest は別商品として用意している。

DAST が出した結果は「ここまでは機械的に確認済み」というベースラインであって、「これで安全」を意味しない。今回検証用 API でやったのは「広く浅く」の方だけ。

TODO(次にやること)

  • aikido-community.jp の残り High 1件への対応(インライン JS 撤去、別記事化)
  • Aikido の通知経路を設定(Email Forwarding、Slack、Teams のいずれかを接続)
  • 検証用 API の NoSQL injection 修正(型バリデーション追加)

記事中の Findings は2026年5月時点のもの。Aikido は継続的に新しい検出パターンを追加しているので、最新の情報は公式ドキュメントを参照してください。

キーワード

  • Aikido
  • DAST
  • API Security
  • Domain Monitoring
  • Surface Monitoring
  • OpenAPI
A

Norihisa Ando

公開日: 2026/5/10

最終更新: 2026/5/10

シェア:

ニュースを購読

セキュリティの最新情報をお届けします。

Aikido Community Japan からのお知らせの受信に同意します。詳しくはプライバシーポリシーをご確認ください。