この記事は、AI製品のセキュリティ実装記録シリーズの1本である。前の記事では、子ども向けAI英語教材「Puku」のログインAPIが認証コードを応答にそのまま含めていた問題を扱った。同じ修正のタイミングで、コードの生成方式・保存方式・試行回数の制限そのものも作り込みが甘かったことが分かった。2026年9月の見直し内容をまとめる。
この記事で分かること
- コードの生成・保存・試行回数の制限に、それぞれどんな不備があったか
- 2026年9月にどう直し、どこまで実機で確認したか
この記事で分からないこと(正直に書く)
- 修正前の状態が実際に総当たりで突破された事例があったかどうか(一次資料には設計上の不備の記載のみで、実被害の記録はない)
何を作っていたか
前の記事で扱った「応答に認証コードをそのまま含めていた」問題を直す作業と並行して、コードそのものの生成方法・保存方法・試行回数を制限する仕組みを洗い直した。応答からコードを消しても、コードの生成や試行回数の制限そのものが甘ければ、別の経路で突破されうるためである(出所: 自社の運用記録、確認日2026-09-06)。
どこに穴があったか
見直し前の実装は、コードの生成に暗号論的に安全な乱数を使っているかの確認が抜けており、保存もコードの値をそのまま扱う設計に近かった。さらに試行回数を止める判定が一つの操作としてまとまっておらず、複数のリクエストがほぼ同時に届くと条件のチェックと書き込みの間に隙が生まれ、想定より多く試せる余地があった(出所: 自社の運用記録、確認日2026-09-06)。応答にコードが含まれていた不備と重なると、総当たりでの突破に近い状態だった。
どう直したか(2026年9月)
コードの生成を、偏りを取り除く方式(リジェクションサンプリング)を用いた暗号論的に安全な乱数に切り替えた。保存はコードの値そのものではなく、メールアドレスとコードを組み合わせてハッシュ化した値だけを持つ形に変更した。試行回数の判定はデータベースへの条件付き更新を1回の操作として扱い、5回誤ると以降のリクエストを拒否するようにした。使用済みかどうかの記録も同じ条件付き更新にし、複数の検証がほぼ同時に届いても一方しか成功しないようにした。ステージング環境で誤ったコードを5回試し、6回目でロックされることを実機で確認してから本番に反映した(出所: 自社の運用記録、確認日2026-09-06)。
NIST SP 800-63Bは、認証の試行にレート制限(スロットリング)を設け、失敗が上限に近づくほど待機時間を増やす技術を併用すべきだとしている。今回の設計はこの方向に沿う(出所: NIST SP 800-63B「Digital Identity Guidelines: Authentication and Lifecycle Management」https://pages.nist.gov/800-63-3/sp800-63b.html、確認日2026-09-06)。またOWASPの「Forgot Password Cheat Sheet」も、コードは暗号論的に安全なアルゴリズムで生成し使い切りにすべきだとしており、今回の見直しはこの基準と一致する(出所: OWASP Cheat Sheet Series「Forgot Password Cheat Sheet」https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html、確認日2026-09-06)。
同じ穴を持つ製品の見分け方
認証コードの入力欄に、意図的に間違ったコードを続けて送ってみて、何回目で拒否されるかを数えるのが分かりやすい確認方法である。何十回試しても拒否されない、あるいは非常に緩い回数までしか止まらないなら、試行回数の制限が甘い可能性がある。あわせて、同じコードを複数のリクエストからほぼ同時に送ってみて、両方とも成功してしまわないかも確認材料になる。両方成功するなら、試行回数や使用済みの判定が一つの操作としてまとまっていない可能性がある。
応答そのものにコードが含まれていた不備は前の記事、この修正と合わせて見直したオリジン制限はCORSの許可リストの記事で扱っている。
自社製品の認証設計について相談したい場合は、お問い合わせから編集部まで連絡してほしい。
よくある質問
Q1. 応答からコードを消せば、それだけで安全になるのか
ならない。コードの生成に暗号論的に安全な乱数を使っているか、試行回数の制限が原子的に効いているかも合わせて見直す必要がある。今回はその両方に不備があった。
Q2. 試行回数の制限は何回で区切ったのか
5回誤ると以降のリクエストを拒否する設計にした。ステージング環境で誤ったコードを5回試し、6回目でロックされることを実機で確認している。
Q3. コードはどのように保存するようにしたのか
コードの値そのものではなく、メールアドレスとコードを組み合わせてハッシュ化した値だけを保存するようにした。
Q4. 複数のリクエストがほぼ同時に届いた場合はどうなるのか
試行回数の判定と使用済みの記録を、データベースへの条件付き更新という1回の操作にまとめたことで、ほぼ同時に届いた複数の検証のうち、どちらか一方しか成功しないようにした。