SQLインジェクションがなぜ起きるのかとその対策について
はじめに
今回は、SQLインジェクションについて、発生する原因から対策まで、実演しながら説明いたします。
SQLインジェクションは、ユーザーの入力をデータベースに利用するシステムならその危険があるため、ほとんどのWebアプリケーションで気をつける必要があります。
もし発生してしまえば、情報漏えいからデータの改ざんまで、業務に対して甚大な被害が発生する可能性があります。
一方で、対策にあたっては、アプリケーション側で複雑な処理を追加で書かなくてはならない場合は少なく、比較的簡単な方法で対策することができます。
では、SQLインジェクションとは何かから始めていきます。
SQLインジェクションとは?
SQLインジェクションとは、ユーザーの入力値を用いてSQL文を発行する時、その処理に不備があることによりデータベースに対して不正な操作が行われることです。
ここで、不正な操作とは、情報の漏洩や改ざんなどのことです。
インジェクションを小文字のiで表して、SQLiと表記されることもあります。
SQLインジェクションが成立すると、本来見えてはいけない情報が見えてしまったり、さらにはデータベースの情報が削除されてしまう可能性があります。
以降では、デモサイトを用いてSQLインジェクションを実際に行ってみます。
注意点として、SQLインジェクションに限らず、正当な理由なく他人のコンピューターに攻撃を行うことは犯罪ですので、試す場合はご自身で用意した環境などで実施してください。
SQLインジェクションの実演
架空のショッピングサイトを例に、購入履歴の検索機能に不備があるという状況を想定して行います。

購入履歴の画面では、ユーザーからの入力を受けつけており、検索ボタンが押されると、現在のログインユーザーが購入したもののうち、入力値を含む商品をordersテーブルから取得するようなSQL文を文字列結合で生成しています。
なお、ordersテーブルには様々のユーザーの購入履歴のデータが入っています。
デフォルトでは、現在のログインユーザーであるaliceが購入した商品が表示されており、入力に「空気」と入力し検索をすると、「空気清浄機」だけが表示されるといったものです。

ではまず、SELECT文による情報漏えいを行います。
入力値に入れるのは「’ OR ‘1’ = ‘1’ –」です。

検索ボタンを押すと、このように現在のログインユーザーに関わらず、すべてのユーザーの購入履歴が取得されました。

続いて、INSERT文による改ざんを行います。
入力値に入れるのは「’; INSERT INTO orders(id, username, item, ordered_at) VALUES (100, ‘david’, ‘SQLインジェクション’, ‘2000-01-01 00:00:00.000000’); –」で、ordersテーブルに対してレコードを1件追加する命令が含まれています。

すると、実際にordersテーブルに対してレコードを追加することができ、あたかもその商品を注文したかのような状態にすることができます。

SELECT文とは異なり実際にデータベースを操作できていることがおわかりいただけたかと思います。
実際にはテーブルにレコードを入れられただけでは注文自体の処理が行われることは少ないと思います。
しかし、例えばアカウントを管理するテーブルにINSERTが行われた時、正当な手続きがなされていないユーザーがそのサイトを利用できるようになるかもしれません。
続いて、DELETE文による改ざんを行います。
入力値に入れるのは「’; DELETE FROM orders; –」で、ordersテーブルのレコードをすべて削除するという命令が含まれています。

検索ボタンを押すと、さきほどまで見れていた購入履歴のデータが削除されました。
実際のデータベースを見てみても、ordersテーブルのレコードがすべて削除されていることが確認できます。

もしこのショッピングサイトがデータベースのバックアップをとっていない場合、誰がどの商品をいつ購入したのかがわからなくなり、業務に大幅に影響が出てしまいます。
このように、SQLインジェクションはSELECT文による情報漏えいから、INSERT文やDELETE文によるデータベースの改ざんまで行える危険性があります。
SQLインジェクションの原因
続いて、SQLインジェクションが発生する原因についてです。
原因は「SQL文のクエリ部分がユーザーの入力により改変されてしまうため」です。
ここで、クエリ部分とはSQL文のうち、パラメーター以外の部分のことを指すことにします。
ショッピングサイトのアプリケーション側はユーザーの入力をテーブルの検索条件として使いたいため、SQL文の一部がそのパラメーターとなっています。

しかし、今回のケースのように、ユーザーの入力の1文字目にシングルクオートがあると、それが文字列リテラルの終端として扱われてしまい、ユーザーの入力の一番左のシングルクオート以降の入力がSQL文のクエリ部分として扱われることとなります。

結果として、SQL文は
SELECT username, item, ordered_at FROM orders WHERE username = ‘alice’ AND item LIKE ‘%’ OR ‘1’ = ‘1’ — %
となります。

OR でつながれた「’1′ = ‘1’ 」は常に真であるため、ログインユーザーに関わらず、ordersテーブルのレコード全てが抽出対象となります。
なお、ハイフンを2個つなげると以降はコメントとなるため、SQLの構文としてのエラーは起きません。
ユーザーの入力のシングルクオートが問題であったため、シングルクオートをエスケープする必要があります。
標準SQLでは、シングルクオート自体を指定するには、2回重ねることになっています。
したがって、ユーザーの入力にシングルクオートがあったら、シングルクオート2個に置き換える処理をおこなってSQL文を発行するなどの対策が必要です。
置き換え処理を行うと、発行されるSQL文は無害なものとなります。

ここまでの話は架空のショッピングサイトでの話であり、実際の対策について以降で説明いたします。
SQLインジェクションの対策
続いて、SQLインジェクションの対策についてです。
前述の通り、エスケープ処理が必要なのですが、実際にそれを開発者が書くことは、あえてそうする必要がない場合がほとんどです。
また、エスケープ処理だけでは対応できない、SQLインジェクションが可能な状況も存在します。
例えば、ユーザーの入力を数値条件としてWHEREに使う場合、数値部分が終端となりますが、数値の後ろにSQLのクエリ部分がある場合はそれも実行されてしまうので、ユーザーの入力の妥当性検証を行う必要があります。
加えて、アプリケーション側でSQL文の組み立てから発行するまでの全てを自作してしまうと、その処理にバグが混入する可能性もあります。
今回は、エスケープ処理や妥当性検証がそもそも必要なくなる、静的プレースホルダーを用いたSQL文の発行についてご紹介します。
前提として、プレースホルダーを用いたSQL文の発行とは、SQL文全体のうち、パラメーター部分をプレースホルダーと呼ばれる、’?’のような文字にしておき、実際の値の割当を行ったあとSQL文を実行するものです。
特に、実際に値を割り当てることをバインドと呼び、プレースホルダーのことをバインド変数と呼ぶこともあります。
プレースホルダーを用いた方法のもともとの目的は、パラメーター部分のみ変化するSQL文の効率的な実行ですが、SQLインジェクション対策にも一定の効果があります。
プレースホルダーには、静的プレースホルダーと動的プレースホルダーが存在します。
静的プレースホルダーの場合、アプリケーション側でクエリ部分とパラメーター部分を送り、データベース側で解析、コンパイル、バインド、実行を行います。

動的プレースホルダーの場合、解析からバインドまでがアプリケーション側で行われ、データベース側でコンパイルと実行が行われます。

動的プレースホルダーがクエリ部分に対してパラメーターのバインドを行ったあとコンパイルするのに対して、静的プレースホルダーはデータベース側でクエリ部分がコンパイルされたあとパラメーターのバインドを行うため、クエリ部分の構造が変更されないことから一般的に静的プレースホルダーのほうが安全とされています。
今回の例でいうと、itemフィールドは文字列であり、バインドされるパラメーターも自動的に文字列として解釈されるため、SQLのクエリ部分が変更されるようなことを避けることができます。

これが、静的プレースホルダーでSQLインジェクション対策になる理由です。
では、実際に静的プレースホルダーを用いてSQLインジェクション対策の効果を見てみます。
今回は、Spring Frameworkが提供するJdbcTemplateを使って実行してみます。
先ほどまでは、単なる文字列でSQLを発行していましたが、これをJdbcTemplateを使ったものに変更します。

変更したうえで、先ほどSELECT文による情報漏洩で使った文字列を入力し、検索ボタンを押します。

すると今回は全件レコードが表示されていないことがわかります。

そしてデータベースのログを見てみると、こちらのようにクエリ部分に対してパラメーターを割り当てて実行していることも確認できます。

以上が、静的プレースホルダーを用いたSQL文の発行についてです。
今回はSpring FrameworkのJdbcTemplateを用いましたが、その他のフレームワークやORマッパーなどでも同様の機能が提供されていると思います。
SQL文を発行する際は、極力静的プレースホルダーを用いることを覚えていただけると幸いです。
まとめ
SQLインジェクションとは、ユーザーの入力値を用いてSQL文を発行する時、その処理に不備があることによりデータベースに対して不正な操作が行われることです。
原因としては、SQL文のクエリ部分がユーザーの入力により改変されてしまうためです。
対策としては、静的プレースホルダーが利用できる場合には、静的プレースホルダーを用いることが第一です。
もしプレースホルダー自体が利用できない場合は、ユーザーの入力に対して、エスケープ処理や妥当性検証を行い、SQL文のクエリを変更されないようにすることが必要です。
最後までご覧いただきましてありがとうございました。
コメント