2025.06.16
お疲れ様です。
社内システム開発部のハシジです。
にわかですが情報セキュリティ関係の話題に興味があり、
そんな話ができる仲間を増やすべくプログラミング初学者や畑違いの業務をされている方にもわかりやすい内容で書いてみようと思います。
今回はサービスを提供するにあたり必須な「SQLインジェクション」の対策について扱っていきます。
◾️注意事項
記載されている内容を自分用の開発環境以外で実施しないでください。
その他の環境に承諾を得ずに実施した場合、
不正アクセス禁止法違反、電子計算機使用詐欺罪、電子計算機損壊等業務妨害罪、不正指令電磁気記録強要罪などに問われる危険性があります。
この記事は脆弱性の解消を啓発する意図で投稿しています。
脆弱性対策の不足は悪いですが、脆弱性を突いて攻撃する方がよっぽど悪いです。
◾️対象とする読者
学生、サービス開発者、情報セキュリティに興味のある人
◾️「SQLインジェクション」とは
ちゃんとした説明はIPAの記事を参照してください。とても簡潔に解決方法も記載されています。
データベースと連携したウェブアプリケーションの多くは、利用者からの入力情報を基にSQL文(データベースへの命令文)を組み立てています。ここで、SQL文の組み立て方法に問題がある場合、攻撃によってデータベースの不正利用をまねく可能性があります。このような問題を「SQLインジェクションの脆弱性」と呼び、問題を悪用した攻撃を、「SQLインジェクション攻撃」と呼びます。
つまりは
・ウェブサイトの入力欄(文字列やファイル)
・APIのパラメータ
などにSQL(データベースを制御する命令文)を紛れ込ませる攻撃です。
脆弱性がある状態だとこの攻撃が通ってしまい、データベース(以降、DBと略記)が不正に使用されるので対策しましょう。
◾️とても雑な説明
端的に言うと、この脆弱性を突かれるとDBでなんでもできてしまいます。
例として以下に脆弱なログイン画面で説明します。
脆弱なログイン画面
ログイン画面で入力された「ログインID」と「パスワード」から、該当するユーザーが存在すればそのユーザーとして認める。
ユーザーの存在確認には、DBに対して「ログインID及び、パスワードが一致するユーザー」を要求する。
つまり、
ユーザーテーブルからlogin_id = ’ここにログインID’ 及び、password = ’ここにパスワード’を満たすレコードを取得するSQL
で
ここにログインID
に入力されたログインID、ここにパスワード
に入力されたパスワードを当てはめてDBに問い合わせを行う。
…ツッコミたくなる方もいらっしゃるかもしれません。
少なくともパスワードを無加工でDBに登録しているのは良くないですが、イメージしやすさ優先です。
テストを考える
この機能のテストを考えたとき、
・正しいログインIDとパスワードの組み合わせなら、該当するユーザーがヒットする
・正しくないログインIDとパスワードの組み合わせなら、ユーザーはヒットしない
正しくない~は、1文字足したり消したり大文字小文字変えてみたり…でヒットしないケースを作ってみるくらいはすぐに思いつくと思います。
SQLインジェクション攻撃を知らない人はここまでで満足してテストOK!としてしまうでしょう。
脆弱性を突く
それでは、実際に脆弱性を突いてみましょう。
例えばログインIDにmyLogin1d’; --
、パスワードにデタラメな値を入力すると
--
はSQLではコメントアウトを意味するため、そこから先は実行されない。
「ログインID及び、パスワードが一致する~」の「及び」つまりAND条件でパスワードの条件を記述していた箇所も
コメントアウトにより実行されなくなってしまうため、
ログインIDだけを見てユーザーを確認するようにSQLがインジェクションされたわけです。
ユーザーテーブルからlogin_id = ’myLogin1d’を満たすレコードを取得するSQL
つまり、今回の例ではログインIDさえわかればなりすましができてしまいます。
インジェクションの内容次第でログインIDがわからない誰かになりすますことも、データ改ざんもできます。
◾️原因と対策
プログラムがSQLを組み立てる際に使うパラメータを、チェックや加工が甘いままSQLの構築に使用してしまうとインジェクションができてしまいます。
なので、
・バリデーション
・型変換
・エスケープ
・プレースホルダーの利用
が基本的な対策となります。
バリデーション
入力されたパラメータが、妥当な形式かどうかを確認します。
例えば数字系の入力値を期待したものなら、
値は有効範囲内か?数字として扱える状態か?など。
想定外ならエラー扱いが妥当です。
これそのものがSQLインジェクションの対策というより、アプリケーションの基本的な動きとしてまずやるべきことだと思います。
型変換
先述のバリデーションを行ったうえで、
例えば数値として扱いたい入力なら、文字列を数値変換した値を用います。
数字になっていれば、少なくともSQLを勝手にコメントアウトしたり条件の差し込みなどは起こらなくなります。
エスケープ
SQLの構文として、文字列は"ここは文字列です"
や'ここも文字列です'
のようにダブル/シングルクォーテーションで囲みます。
その中でダブル/シングルクォーテーションを使うには同じ文字を""2回""記述
するか、エスケープ文字を追加して"このように\"文字列\"を表現"
します。
文字列として扱う入力では、適切にエスケープする必要があります。(エスケープが必要な文字はクォーテーションにもあります)
これを行わずにそのまま使ってしまったのが「脆弱性を突く」の例です。
エスケープすれば
SELECT * FROM users WHERE BINARY login_id = 'myLogin1d\'; -- ' AND BINARY password = 'detarame';
となるため、login_idがmyLogin1d'; --
かつ、passwordがdetarame
であるデータをusersテーブルに問い合わせるようになります。
プレースホルダの利用
SQLのテンプレートと、それに当てはめるパラメータ群に分けてDBに引き渡します。
SELECT * FROM users WHERE BINARY login_id = ? AND BINARY password = ?;
?のことをプレースホルダと呼びます。
SQLと値を別に処理されるため、
SQLテンプレートに含まれるプレースホルダ2つに対して2つの値を引き渡すと、DBではそれを解釈して適切に処理をする動きになります。
結果的にインジェクションが阻止されます。
具体的にどうするの?
開発言語、フレームワーク等によってSQLインジェクションが発生しないように実装する方法が提供されています。
SQL全文を文字列で組み立てるようなやり方をするとインジェクションの余地が出てくるので、そんな実装をするのはやめましょう。
面倒でしょう?型変換したりエスケープしたり、それを細かくテストするの。
追加で必要なテストを考える
細かく書き出すと長大になるので端的に書き下すと、
・【型変換不備の確認】数値系パラメータにSQLインジェクションできないこと
・【エスケープ不備の確認】ダブル/シングルクォーテーションを混ぜてSQLインジェクションできないこと
の観点でのテストを追加すると良さそうです。
DBバックアップを取らないまま、いい気になって過剰に破壊的な内容をインジェクションをすると大変な目に遭うので注意しましょう。(遭いました)
またテストとはいえインジェクションできちゃったら大変なので、周知と許可の上でテスト用の環境までに留めて本番環境での実施は止めておきましょう。
まとめ
・SQLの組み立て方が悪いとSQLインジェクション攻撃に対する脆弱性ができてしまう
・脆弱性を突かれると簡単にサービスに致命的な障害や情報漏洩を起こしてしまう
・入力されたパラメータはバリデーションする
・全文を文字列でSQLを組み立てる場合は、パラメータを型変換や、文字列ならエスケープする
・プレースホルダを使ってSQLテンプレートと値を明確に分離した方が楽
・試すなら自分の環境だけでやる
型変換だエスケープだと面倒くさそうだと思いきや、
インジェクション対策済みの方法がほとんどの環境で用意されているから使ってね。
ということを言いたかったわけです。
「ほとんど」の中に自分の環境・使い方が含まれているかは、各々ご確認のほどよろしくお願いします。
といったところで以上です。これにて失礼します。
引用一覧
・安全なウェブサイトの作り方 – 1.1 SQLインジェクション – 独立行政法人情報処理推進機構