【ITニュース解説】The Scanner Came Back Clean. The Discount Code Still Worked 40 Times.
2026年08月24日に「Dev.to」が公開したITニュース「The Scanner Came Back Clean. The Discount Code Still Worked 40 Times.」について初心者にもわかりやすく解説しています。
ITニュース概要
自動スキャンで見逃されがちな脆弱性に「レースコンディション」がある。これは、割引コードの有効性チェックと使用済み処理の間に生じる時間差を悪用し、複数リクエストを同時に送ることで、単一コードを複数回適用させるものだ。スキャナーでは検知が難しく、人間による同時実行テストが重要となる。
ITニュース解説
Webアプリケーションのセキュリティ診断において、自動スキャンツールは非常に強力な味方である。SQLインジェクションやクロスサイトスクリプティング(XSS)など、既知の多くの脆弱性を素早く検出する能力を持つ。しかし、それだけでは見つけられない、巧妙な脆弱性が存在する。今回紹介する事例は、まさにその典型であり、システムエンジニアを目指す皆さんにとって、自動ツールの限界と人間の思考の重要性を学ぶ良い機会となるだろう。
あるECサイトのチェックアウトフローに対するセキュリティ診断での出来事だ。自動スキャンツールは、SQLインジェクション、XSS、認証の不備など、OWASP Top 10と呼ばれる主要な脆弱性項目に関して、「問題なし」という結果を返した。全ての項目が緑色の「安全」マークで満たされており、経験の浅いペンテスターであれば、「重大な発見はなし」と報告書を書き上げ、次のターゲットへ進んでいたかもしれない。実際、ほとんどの自動スキャンツールもこれに同意するだろう。
しかし、チームの一員が「割引コード適用」のエンドポイントに注目した。このサイトの割引コードは「一回限り有効」という設定だったが、コードが「使用済み」とマークされるタイミングが、注文が確定した後であることがわかった。ここでチームメンバーは疑問を投げかけた。「もし、注文が完了する前に、同じ割引コードを適用するリクエストを20回、ほぼ同時に送ったらどうなるだろう?」と。
結果は驚くべきものだった。なんと、一回限りの50%割引コードが、わずか1秒足らずの間に40回も適用されてしまったのだ。これには特別な技術や複雑な攻撃手法は一切使われていない。単に、同じリクエストを複数回、同時にサーバーへ送っただけだ。SQLインジェクションのような不正なデータ挿入もなければ、アクセス制御の不備があったわけでもない。ただ、「このコードは有効か?」という確認の処理と、「このコードは使用済みである」とマークする処理の間に、わずかながらも致命的な「隙間」が存在しただけだった。この隙間は、カートいっぱいの注文を通り抜けさせるほど広かったのである。
このような種類の脆弱性は、「レースコンディション」と呼ばれている。これは、複数の処理が同時に実行された際に、それらの処理の実行順序やタイミングによって結果が変わってしまう現象を指す。今回の事例では、「コードの有効性を確認する(Read)」という処理と、「コードを使用済みとして記録する(Write)」という処理が、別々のステップとして行われていた。通常、人間が操作する場合や、自動スキャンツールがリクエストを一つずつ送る場合には、これらの処理は順番に実行されるため、問題は発生しない。しかし、複数のリクエストが同時にサーバーに到着すると、サーバーはまだコードが使用済みだと認識する前に、次のリクエストに対しても「このコードは有効である」と判断してしまう。そして、結局、本来一回しか使えないはずのコードが複数回適用されてしまうのだ。
自動スキャンツールがレースコンディションを発見できないのは、その構造的な限界による。スキャナーは、常に一つずつリクエストを送り、その応答を分析する。二つのリクエストを同時に送るという状況を想定していないため、複数のリクエストが互いに干渉し合うことで発生する脆弱性を検出する機会がないのだ。レースコンディションは、二つのリクエストの間にある「時間差」の中にしか存在せず、その時間差を意図的に作り出さなければ、その脆弱性は姿を現さない。
では、どのような場所でレースコンディションが発生しやすいのだろうか。それは、「確認してから実行する(Check-then-Act)」というパターンが使われているエンドポイントだ。例えば、クーポンの利用、紹介ボーナスの適用、銀行口座からの残高引き出し、投票の受付、限定在庫商品の請求といった処理が当てはまる。これらの処理の多くは、「これがまだ有効か?」という読み込み(Read)のステップと、「これを実行した、使用済みとマークする」という書き込み(Write)のステップが、別々の手順として実装されている場合が多い。特に、金銭に関わる処理や、数量に限りがあるリソースを扱うビジネスロジックは、このように実装されていることが多い。これは、開発者にとってシンプルにコードを書けるためであり、コードレビューの際にも、この「時間差」がセキュリティ上の問題を引き起こす可能性は、なかなか指摘されにくい傾向がある。
この脆弱性を実際に検証するには、単に高速にリクエストをループで送るだけでは不十分だ。通常のスクリプトで素早くリクエストを送信しても、ネットワークのわずかな遅延(ジッター)が発生し、結果的にサーバーはほとんどのリクエストを順番に処理してしまう可能性が高い。それではレースコンディションは誘発されない。本当に「同時に」リクエストを送る必要があるのだ。Burp Suiteのレースコンディションタブや、Turbo Intruderのような専用ツールは、これを可能にする。これらのツールは、同じTCP接続内や、場合によっては同じネットワークパケット内に複数のリクエストをまとめ、サーバーが受け取る時間差を限りなくゼロに近づけることで、レースコンディションを効果的に誘発できる。
攻撃が成功したかどうかを判断する際も、注意が必要だ。すべてのリクエストが成功を示す「200 OK」のような応答コードを返したからといって、必ずしも脆弱性が存在しないとは限らない。多くのレースコンディションのケースでは、不正に処理されたリクエストであっても、サーバーは「成功」の応答を返すことがある。したがって、重要なのは、サーバーからの応答コードだけでなく、攻撃後にシステムが実際にどのような状態になったかを確認することだ。例えば、割引コードが何回適用されたのか、ユーザーの残高がマイナスになっていないか、限定商品の在庫数が0を下回っていないかなど、最終的な状態を検証することが、脆弱性の有無を証明する唯一の方法だ。
このように、自動スキャンツールが一つずつリクエストを実行する限り、どんなに網羅的にテストしているように見えても、今回の事例のような脆弱性はレポートに上がってくることはない。これは、クリーンなスキャン結果を鵜呑みにせず、アプリケーションが多数のリクエストを「同時に」処理した場合にどうなるのか、という疑問を人間が持ち、深く掘り下げて初めて見つけられる種類の問題なのである。システムエンジニアとして、常に自動化ツールの限界を理解し、その裏に隠された潜在的なリスクを見抜く「人間による思考」の重要性を忘れてはならない。ビジネスロジックの複雑な相互作用の中にこそ、スキャナーが見過ごす大きな落とし穴が潜んでいることを心に留めておくべきである。