SQLインジェクションの仕組みと対策方法を徹底解説!

世の中には様々なサイバー攻撃が存在していて、エンジニアはそれぞれに対処しなければなりません。全てに対処することは、莫大なコストを必要としますが、可能な限り手を打っておくことが重要です。
特に開発の場面で、優先的に対応したいものとしてSQLインジェクションに関するものが挙げられます。SQLはデータベースを操作するものであり、これに関連する攻撃を受けてしまうと、情報漏えいなどに繋がりかねません。今回は、SQLインジェクションの概要から攻撃の手法、そして攻撃の手法を踏まえた対策について解説します。
SQLインジェクションの概要や仕組み
SQLインジェクションの概要や仕組みなど、基本的な知識について解説します。
SQLインジェクションとは
SQLインジェクションとは、データベースを操作するSQLクエリに不正な入力を挿入し、システムのセキュリティを突破する攻撃です。攻撃者は、ユーザの入力フォームやURLパラメータなどに悪意のあるSQLコードを入力します。そして、これがデータベースに直接実行されるようになり、データの漏洩や改ざん、不正アクセスを実現しようとするのです。
ただ、すべてのアプリケーションが攻撃の対象となる訳ではありません。主に、ユーザ入力を適切にエスケープ処理せず、SQLクエリに組み込むシステムが対象となってしまいます。
SQLインジェクションの仕組み
SQLインジェクションの仕組みは、攻撃者がWebアプリケーションに入力するデータを通じて、データベースに対して悪意のあるSQLクエリを実行させるというものです。通常、ユーザがフォームやURLを通じて入力するデータは、プログラムがデータベースに問い合わせる仕組みとして安全に開発されます。しかし、エンジニアが適切な対策を施していない場合、その入力値がそのままSQL文として解釈されかねないのです。
例えば、以下のような単純なクエリがあるとします。
SELECT * FROM users WHERE username = 'ユーザー入力' AND password = 'ユーザー入力';
このクエリは、ユーザーの入力したusernameとpasswordに基づいてデータベースを検索するものです。しかし、攻撃者がusernameに次のように入力すると、SQLインジェクションが発生します。
' OR '1'='1
この入力をクエリに挿入すると、以下のようなSQL文が実行されるからです。
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'パスワード';
結果として、OR ‘1’=’1’という常に真になる条件が追加され、すべてのユーザー情報が取得される可能性が生じます。
これは一例ですが、上記のように攻撃者は意図しないSQL文を作り出すことを目指しています。そして、データの不正な閲覧、データベースの破壊、あるいは管理者権限を奪おうとするのです。
SQLインジェクションが発生しうる4つの事例

SQLインジェクションが発生しうる状況は多岐にわたりますが、その中でもイメージしやすい4つの事例を紹介します。
ログインフォームでの攻撃
多くのWebアプリケーションはログイン機能があり、ユーザー名とパスワードを入力するフォームが設けられています。通常、ログインフォームのクエリは次のように記述されるでしょう。
SELECT * FROM users WHERE username = 'ユーザー入力' AND password = 'ユーザー入力';
ここで、攻撃者がユーザー名に以下のような値を入力します。
' OR '1'='1
その結果、クエリは次のように改変されてしまうのです。
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'パスワード';
このSQLは、OR ‘1’=’1’という常に真になる条件が追加されているため、正しいパスワードを入力せずともログインが成功する可能性があります。
検索機能の悪用
検索機能でユーザーの入力が直接SQLクエリに反映される場合もSQLインジェクションの対象となります。例えば、ユーザーが商品名を検索できるフォームがあるとし、次のようなSQLクエリが実行される場合を考えましょう。
SELECT * FROM products WHERE name = 'ユーザー入力';
ここで、攻撃者が検索欄に次のような文字列を入力したとします。
' OR '1'='1
この場合、クエリは次のように改変されて実行されます。
SELECT * FROM products WHERE name = '' OR '1'='1';
これにより、OR ‘1’=’1’という条件が常に真となってしまうのです。結果、データベース内のすべての商品が表示されることになりかねません。
データ削除に関連した攻撃
ユーザーがデータを削除する処理に関連し、SQLインジェクションが発生することも考慮すべきです。例えば、次のようなクエリがアカウント削除の際に使用されるとします。
DELETE FROM users WHERE id = 'ユーザー入力';
ここで、攻撃者が入力欄に次のような文字列を入力すると仮定しましょう。
'; DROP TABLE users; --
これによりクエリは次のように変更されます。
DELETE FROM users WHERE id = ''; DROP TABLE users; --';
この場合、まずユーザーの削除が試みられ、その後DROP TABLE users;が実行されてしまいます。結果、データベースのusersテーブルが削除されてしまうかもしれません。
URLパラメータの改ざん
URLにユーザーの入力を反映させてクエリを生成する場合も危険が潜んでいると考えましょう。例えば、商品の詳細ページのURLが次のような形式であったとします。
https://example.com/products?id=123
この商品を表示するために、内部で次のようなSQLクエリが実行されていると仮定します。
SELECT * FROM products WHERE id = 123;
この状況で、攻撃者がURLのidパラメータを次のように改ざんしたと仮定しましょう。
https://example.com/products?id=123 OR 1=1
結果、クエリは次のように変更されます。
SELECT * FROM products WHERE id = 123 OR 1=1;
この結果、OR 1=1という常に真になる条件が追加され、データベース内のすべての商品が表示されかねません。本来は表示してはいけない内容が表示されてしまうことで、情報漏洩などが起きる可能性があります。
SQLインジェクションの対策方法

SQLインジェクションの発生は、セキュリティの観点から避けなければならないものです。そのため、以下の対策方法を用いて、可能な限り攻撃を防げるようにしていきましょう。
プリペアドステートメントの活用
プリペアドステートメントを使用することで、SQLクエリにユーザの入力を安全に組み込むことが可能です。これは、ユーザーの入力をクエリに直接埋め込むのではなく、「?」や「:name」などのプレースホルダを使用し、後から値を安全にバインドする仕組みです。これにより、入力データがSQL文として解釈されることを防ぎ、SQLインジェクションのリスクが大幅に低減できます。
例えば、PHPではプリペアドステートメントが提供されていて、以下のような記述が可能です。
PHP
// PDOを使用したプリペアドステートメントの例
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);
この方法では、$usernameや$passwordがどのような値でもSQL文として解釈されることはなく、常にデータとして扱われます。そのため、SQLインジェクションの例として挙げた「1=1」のような常に真になる条件が入力されても、意図しない実行を回避できるのです。
ストアドプロシージャでの実装
ストアドプロシージャもSQLインジェクション対策として使用されます。データベース内に保存されたSQLコードの集まりで、アプリケーションはこれを呼び出すだけで処理を実行できる仕組みです。一般的には処理を高速化するために利用されますが、SQLインジェクションを防止するためにも利用できます。
例えば、MySQLではストアドプロシージャを使用して、以下のようなクエリを作成できます。
SQL
DELIMITER //
CREATE PROCEDURE GetUser(IN user_name VARCHAR(50), IN pass_word VARCHAR(50))
BEGIN
SELECT * FROM users WHERE username = user_name AND password = pass_word;
END //
DELIMITER ;
ストアド・プロシージャは事前に定義されたSQLクエリであるため、実行時に構造を変化させることができません。そのため、「1=1」のような値をユーザが入力した場合、想定されている構造との不一致が発生し、エラーで処理を中止することが可能です。
入力チェック(入力バリデーション)とエスケープ
ユーザーの入力バリデーションとエスケープを活用することも、SQLインジェクション対策に有効です。入力バリデーションは、入力が期待される形式であることを評価する機能で、エスケープは特殊文字など想定していない入力を受け付けないことを指します。
例えば、バリデーションを利用することで「数値が期待されるフォームに整数値が入力されているか」をシステム的に評価できます。
PHP
if (filter_var($user_input, FILTER_VALIDATE_INT) === false) {
// 無効な入力である場合の処理
}
また、エスケープを利用することで、特殊文字がSQLの中に組み込まれてしまうことを回避できるのです。
PHP
$user_input = mysqli_real_escape_string($connection, $user_input);
なお、PHPではmysqli_real_escape_stringなどを使用しますが、基本的にはプリペアドステートメントの方がより安全と考えられています。そのため適切な使い分けを意識するようにしてください。
Webアプリケーションファイアウォール(WAF)の導入
Webアプリケーションへの攻撃を防ぐという観点で、WAFの導入も考えるべきです。これを導入することで、SQLインジェクションはもちろん、多くのサイバー攻撃からWebアプリケーションを保護しやすくなります。
WAFにはさまざまな製品があり、それぞれが有している機能も異なっていることが特徴です。ただ、一般的にはWebアプリケーションの通信内容を監視して、外部から不正なアクセスや処理が実行されていないかを評価してくれます。また、レスポンスに不正な内容が含まれていないかどうかもチェックしてくれるのです。
とはいえ、WAFだけでSQLインジェクションなど、すべての攻撃を防げるわけではありません。セキュリティ対策全般にいえることですが、多角的な対策が必要となるのです。
まとめ
サイバー攻撃の一種であるSQLインジェクションについて解説しました。フォームなどにSQL文と解釈できるような文言を入力することで、意図しないSQLを実行させる攻撃です。データベースの内容が漏洩したり、改ざんされたりするリスクがあるため、開発の段階で対策しなければなりません。
ただ、SQLインジェクションの仕組みは、大まかにパターン化されているため、それぞれを理解していれば容易に対策が可能です。今回は攻撃のパターンとそれの対策を紹介しているためぜひとも参考にしてください。プログラミング言語によって実装方法は少々異なるため、その点には注意が必要です。