スパムメールが、深夜23時33分に大量に届きました。保守で預かっているサイトの管理者宛です。件名や本文の中身はばらばらですが、差出人のアドレスは「testing@example.com」のような、明らかな作り物の形が目立ちます。スパム自体は珍しいことではありません。ただ、本文の中に、常に真になる条件式など、SQLの構文がそのまま混ざっているものが複数含まれていました。
疑ったのは、フォームそのものでした
宛先を集めた業者が機械的に大量送信しているだけのスパムなら、内容に意味はありません。ただ、SQLの構文がそのまま本文に残っているとなると話は別です。フォームの入力欄に何かを流し込んだ跡が、送信された通知メールの中にそのまま残っていると考えたほうが筋が通ります。
この時点では、実際に何かが起きたのか、起きていないのかは分かっていません。アクセスログを見ることにしました。
アクセスログの実測
攻撃元は、海外の2つのIPアドレスでした。一方が合計40,056件、もう一方が合計3,631件のリクエストを送っていて、そのうちSQLインジェクションと判定できる書き方を含むものが、それぞれ3,435件と620件です。
時間帯は18時台と22時台の2回に分かれていました。18時台は、ログイン関連の入力欄2種類にそれぞれ214件と205件、会員登録の入力欄1種類に4,700件のアクセスが集中しています。22時台はお問い合わせフォームに36件のアクセスがあり、そのうち21件がデータベースに記録として残っていました。このフォームは通常、1日に1件あるかないかです。
この4つのページには、いずれも送信内容を管理者宛のメールで通知する仕組みがあります。スパムメールが大量に届いた理由は、ここにありました。
どこまで進んでいたのか
ログの書き方は、SQLインジェクションを自動で試す攻撃ツール「sqlmap」が使う定型のパターンと一致していました。このツールは、SQLインジェクションが成立するかを確かめたあと、データベースの種類を割り出し、次にテーブルの一覧、カラムの一覧という順に進んでから、最後に中身のデータを抜き出します。
ログに残っていた抽出の試みを数えました。データベースの利用者名を返す書き方が1,711回、データベース名を返す書き方が995回、バージョン情報を返す書き方が47回です。ここまでは、データベースの構造ではなく周辺のメタ情報にとどまっています。一方、テーブルの一覧を返す書き方は0回、カラムの一覧を返す書き方も0回でした。テーブル名を返す書き方だけ1回ありましたが、これは使われていないパラメータへの試行で、結果は画面にもログにも反映されていません。
種類の判定までは進んでいましたが、テーブルやカラムの列挙という次の段階には届いていませんでした。理由も確認できています。エラーメッセージは画面に出さず、ログにだけ残す作りでした。抽出したデータを画面に表示する仕組みもありません。先へ進める経路が、どちらも塞がっていたことになります。
実際に注入が成立していた場所は、この4つのページとは別でした。サイト内の検索機能が使っている共通の関数に、外部からの入力をそのままSQLへ渡す書き方が残っていて、しかも同じ処理がサイトの3つのバージョン(本体・スマートフォン版・新バージョン)にそれぞれ存在していました。3つとも、その場でエスケープ処理を入れています。
個人情報の漏洩はありませんでした
列挙の段階に届いていない以上、テーブルの構造もデータの中身も取れていません。保有しているデータへの参照も、ログ上には見当たりませんでした。今回の攻撃で、個人情報が漏れた形跡はありません。
通知メールに紛れ込んでいたSQLの断片についても、これで説明がつきます。フォームからの通知に、入力した内容がそのまま含まれる作りだったため、攻撃者が流し込んだ文字列も、そのままメールの本文として届いていました。件名も本文もばらばらに見えたスパムメールは、実は攻撃の副産物でした。
その日のうちに、もう一つ見つかりました
3箇所を直したところで、念のため他にも同じ作りの箇所がないか、ファイルを見て回りました。攻撃を受けた3箇所は、いずれもデータを読み取るだけの書き方でした。確認の途中、メールマガジンの配信解除を扱うページに、読み取りではなく削除を実行する書き方が残っていることに気づきました。
条件を満たす入力を1回送るだけで、対象のテーブルごと消える作りです。読み取りより一段階重い話になります。
実害の有無を確かめました
修正の前に、対象のテーブルをmysqldumpでバックアップしました。件数は262件です。
そのうえで、対象のテーブルを全件削除する狙いで、常に真になる条件式を入力欄に入れ、パソコン版・スマートフォン版の両方に対して試しました。件数は262件のまま変わりませんでした。この確認は、エスケープを入れたあとの状態で行っています。
次に、その日のアクセスログで、この配信解除のページが実際に呼ばれていたかを確認しました。件数は0件です。攻撃者が用意した経路には入っていましたが、実際にはたどり着いていませんでした。直しはしましたが、今回に関して実害はありませんでした。
全体を測ると、82行が残っていました
読み取り3箇所と削除1箇所を直したところで、同じ問題が他にどれだけあるのかを数えました。
稼働しているPHPファイルは225本です。このうち、SQLの文字列に変数をそのままつなげている行が409行、さらにそのなかで外部からの入力を直接埋め込んでいる行が82行ありました。この82行が、いちばん危険度の高い候補です。
内訳は、スマートフォン版に19行、別バージョンのページに19行、ログインが必要な管理画面に16行、共通処理に12行、実質使われていない箇所に5行、会員向けの機能に6行、サイトの入り口に近い共通処理に5行です。このうち入り口に近い5行だけ実際に開いて確認したところ、2行は本物のSQL連結で、残り3行はSQLとは関係のないテンプレート変数の誤検出でした。
残りの77行についても同じ比率だと考えていますが、1行ずつはまだ確認できていません。仮にその比率が当てはまるとすると、実際に対応が必要なのは30から40箇所です。
最初に伝えた数字は、ざっと見て3から4時間というものでした。実際に数えたうえでの内訳は、調査に3時間、修正に3から4時間、検証に2から3時間で、合わせて8から10時間です。数える前の数字と、数えたあとの数字は違いました。
直したところと、まだ手を付けていないところ
今回の原因は、古いバージョンのまま残っていたコードに、外部からの入力をそのままSQLへ渡している箇所があったことです。攻撃で実際に使われた4箇所は直し、確認も済んでいます。
攻撃で使われたクエリのうち1件は、通常の権限では強制終了できませんでした。別の手段を探し、同じ夜のうちに止めています。
本来なら見積もりを提示してから着手する話ですが、注入が実際に成立していた以上、緊急度を優先し、その場で保守として対応しました。残り30から40箇所は、今回の攻撃では触られておらず、緊急度はそこまで高くありません。こちらは、あらためて見積もりを提示しています。
古いバージョンのまま動いているシステムは、新しいものより狙われやすい状態にあります。今回は大量のスパムメールという分かりやすい形で気づけましたが、毎回そうとは限りません。
自分のフォームで似たような通知メールを見つけたら、まず本文に「OR」「UNION」「SLEEP」といった並びが混ざっていないかを確認してください。次に、その時間帯のアクセスが普段よりどれだけ多いかをログで数えます。多ければ、入力を受け取っている処理が、値をそのままSQLへ渡していないかをソースで確認してください。ここから先の切り分けが難しい場合は、お問い合わせからご相談ください。
FAQよくある質問
- Qスパムメールと、SQLインジェクションはどうつながっているのですか。
- Aフォームからの通知メールに、入力した内容がそのまま含まれる作りだったためです。攻撃者がフォームへ流し込んだSQLの文字列が、通知メールの本文としてそのまま届いていました。スパムのように見えた大量のメールは、実際には自動化された攻撃ツールがフォームへ送り続けていた入力の副産物でした。
- Q個人情報が漏れていないと言えるのは、なぜですか。
- ASQLインジェクションを試す攻撃ツールは、データベースの種類を調べたあと、テーブルの一覧、カラムの一覧という順に進んでからデータを抜き出します。今回のログでは、テーブルやカラムの一覧を返す書き方が0回で、次の段階に届いていませんでした。エラー表示を出さず、抽出結果を画面にも出さない作りだったため、これより先へは進めない状態でした。
- Q見つかった脆弱性は、すべて直したのですか。
- A攻撃で実際に使われた4箇所は直し、確認も済んでいます。注入が実際に成立していたため、緊急度が高いと判断し、見積もりを待たずその場で保守として対応しました。一方、同じ書き方が残っている箇所を全体で数えると、まだ手を付けていない候補が30から40箇所ほどあります。こちらは今回の攻撃では触られておらず、緊急度が高くないため、あらためて見積もりを提示しています。
- Q古いシステムは、見つかった箇所を全部すぐ直すべきではないのですか。
- A本来はそうすべきです。ただ、実際に攻撃で触られた箇所と、触られていない箇所は別に考える必要があります。触られていない30から40箇所を今日中に決める必要はありません。何が実際に狙われたかを測ったうえで、直す順番を決めることをおすすめします。
- Q古いシステムが狙われやすいのは、なぜですか。
- A公開されている既知の弱点が、時間が経つほど広く知られるためです。今回のような自動化された攻撃ツールは、特定のサイトを狙っているわけではなく、機械的に多くのサイトへ同じパターンを試します。古いバージョンのまま動いているシステムほど、その中のどれかに引っかかる確率が上がります。