概要

Webアプリケーション上で利用される情報の多くはデータベースによって管理されています。
例えば、ログイン機能を有しているWebアプリケーションの場合、データベースとWebアプリケーションはSQLと呼ばれる言語を用いてやり取りしています。

img_gaiyou

SQLインジェクション【SQL Injection】とは、ユーザの入力値などを含んだSQL文を組み立てる処理に不備がある場合に、SQL文が書き換えられ意図していない処理を実行されてしまう脆弱性です。SQLインジェクションにより攻撃者は、情報窃取、データの改ざん、サービスの停止などを行うことが可能です。

関連ゼイジャッキー

SQLInjection

SQLインジェクション
とある悪の組織によって開発されたシャチ型ロボットゼイジャッキー。
からだに備えた注射器型の魚雷でデータベースを破壊して、中の情報を大量に食い荒らすぞ!!

ゼイジャッキーとは?



前提知識

データベースとDBMSについて

データベースとは「特定の条件によって集められたデータを決まった形で整理したもの」のことです。

しかし、データベースはただのデータの集まりに過ぎません。
データの追加、削除といったデータベースに対する操作には、一般にデータベースを管理するシステムが必要になります。
このようなシステムのことをデータベース管理システム(DBMS: Database Management System)と言います。

また、DBMSの中でもWebアプリケーション上でもっとも多く使われているものがリレーショナルデータベース管理システム(RDBMS: Relational Database Management System)です。

リレーショナルデータベース(RDB: Relational Database)とは、データどうしの関係をテーブル(表形式)で表現し、テーブルが相互に関連してデータを格納するデータベースのことです。

RDBMSの代表例としてOracle DatabaseやMySQL、Microsoft SQL Server、PostgreSQLなどが挙げられ、これらのRDBMSはWebアプリケーションで広く使われており、広く知られています。

データベースにはRDB以外にも様々な種類のものが存在しますが、本記事中で使用するDBMSはこのRDBMSを前提としてあつかいます。

データベースを構成する要素

データベースを構成する要素について、本記事であつかうWebアプリケーションを例にとって説明します。

以下の表のように、データベースは大きくわけて5つの要素から構成されています。

名前 説明
データベース テーブルの集まりのことを指します。1つのデータベース内に複数のテーブルを持つことができます。
テーブル 決まった形式でデータを保存しておくための「表」のことを指します。
カラム テーブルが持つそれぞれの項目にあたる「列」のことを指します。
レコード 1件分のデータに当たる「行」のことを指します。
フィールド レコード内の1つの「要素」のことを指します。

img01

ordersテーブルのuser_iditem_idは、usersテーブルのuser_iditemsテーブルのitem_idに、それぞれ対応しています。
たとえば、「オレンジ色で囲われているレコードはuser2というユーザがリンゴ(apple)を999個注文した」という意味になります。

SQLとは

SQLとはDBMS(データベース管理システム)上でデータやデータベースを制御するための言語です。
以下の表はRDBMS上でよく用いられるSQL文の代表例です。

命令文 操作 書き方の例 例の意味
SELECT文 データの読取 SELECT カラム名 FROM テーブル名; 指定したテーブルからカラム名のデータを読み取る
INSERT文 データの追加 INSERT INTO テーブル名(カラム名, カラム名, ...) VALUE (フィールド値, フィールド値, ...); 指定したテーブルにデータを追加する
UPDATE文 データの更新 UPDATE テーブル名 SET カラム名 = フィールド値; 指定したテーブルの指定したカラムを指定したフィールド値に更新する
DELETE文 データの削除 DELETE FROM テーブル名 WHERE 条件式; 指定したテーブルから条件式が真となるレコードを削除する

また命令文のほかに、例に示したように対象のデータを指定するFROM句、条件を絞るWHERE句、WHERE句の条件として書くことができる演算子などがあります。
後述するSQLインジェクションについての説明で使用されるSQLの表現を以下の表に示します。

SQL文 意味
FROM ~ ~のテーブルから
WHERE 条件 条件を満たすレコードを
WHERE 条件A OR 条件B 条件Aまたは条件Bのいずれかの条件を満たすレコードを
WHERE 条件A AND 条件B 条件Aと条件Bのいずれの条件も満たすレコードを
CONCAT (文字列A, 文字列B, 文字列C, ...) 値A,値B,値C..を結合した1つの文字列とする
-- 以降のSQL文をコメントとしてあつかう
SELECT文1 UNION SELECT文2 SELECT文1とSELECT文2の結果を統合する (SELECT分のカラム数とカラムのデータ型は一致する必要があります)

命令文、条件、演算子などを組み合わせて、SQLは構成されます。

例として、SELECTを用いたSQL文を紹介します。次のSQL文は
users_tableからnameuser1もしくはnameuser2の、idの列、nameの列、passwordの列を読み込む」という意味になります。

SELECT文の例
mysql> SELECT user_id, name, password FROM users WHERE name = 'user1' OR name = 'user2' ;
+---------+-------+------------+
| user_id | name  | password   |
+---------+-------+------------+
|       1 | user1 | u1password |
|       2 | user2 | applesuki  |
+---------+-------+------------+
2 rows in set (0.000 sec)

SQLインジェクションの仕組み

img02

上記の画像はショッピングサイトのログイン機能において、SQLインジェクションを使用してログイン処理をバイパスするフローを示した図です。
例で使用されているWebアプリケーションはPHPで書かれており、下記はソースコードの一部抜粋になります。
SQLの単純な文字列結合を用いてSQL文を組み立てています。

$name = $_POST['name'];
$password = $_POST['password'];
$sql = "SELECT name FROM users WHERE name = '" . $name . "' AND password = '". $password."';"; 
//SQL文を実行する処理に続く

ログインフォームのユーザ名にadmin';--と入力し、パスワードにaと入力し送信します。
すると、データベース側では下記のようなSQLが実行されます。

SELECT * FROM users WHERE name = 'admin';--' AND password = 'a';

上記のSQLは--によってパスワードの検証部分がコメントアウトされており、下記のSQLと同様の意味になります。

SELECT * FROM users WHERE name = 'admin';

nameadminのレコードは存在するためログインに成功します。
このように、ユーザの入力値がSQLの構文に干渉することでSQLインジェクションが発生します。

攻撃例

攻撃例の説明のためにショッピングサイトの商品検索ページを用意しました。
このWebアプリケーションにはSQLインジェクションの脆弱性があります。
このサイトに対して攻撃し、最終的にユーザの認証情報を得ることをゴールとします。

サイトの動作

以下の画像はテキストボックスにappleと入力して検索ボタンをクリックしたときの画面です。

img04-1

商品名が一致する商品の「商品番号」「商品名」「価格」が表示されました。

1.脆弱性の有無を確かめる

次に、このサイトの入力がSQLの構文に干渉しているかを判断するために、商品検索ページのテキストボックスにSQLの構文を破壊する'\を入力します。
以下の画像はその結果の画面になります。

img04-2

MySQLのエラーメッセージが表示されていることから、このアプリケーションで利用しているDBMSはMySQLであることがわかりました。
DBMSによってSQLの構文が異なるため、DBMSの種類も重要になります。
また、このことから挿入された入力値がSQLの構文に干渉していることがわかりました。

2.INFORMATION_SCHEMAから情報を取得する

次は実際に、UNION句を用いてINFORMATION_SCHEMAというデータベース内の情報を取得します。

INFORMATION_SCHEMAとはMySQLのデータベースであり、MySQLが保持するほかのすべてのデータベースに関する情報を格納しているデータベースです。
このデータベース内には、すべてのデータベース名、すべてのテーブルのカラム名や、データ型などの情報も格納しています。
ほとんどのRDBMSが標準でこのデータベースを搭載しています。

INFORMATION_SCHEMA内の情報を取得するためにUNION句を使って読み込みます。
UNION句を使うには、最初のSELECT文とカラム数とカラムのデータ型を一致させる必要があります。

ブラウザに表示されている列は3つあるので、最初のSELECT文で読み込んでいるカラム数は最低3つ以上と推測できます。
手始めにカラム数が3つと推測し、apple' UNION SELECT NULL,NULL,NULL ;--を挿入します。

img04-3

SQLのエラーが出力されていないことから、最初のSELECT文は3カラム分を読み込んでいることがわかりました。

次は、検索で使われているSELECT文のカラムのデータ型を推測します。
表示されている値から、「商品番号」は数値型、「商品名」は文字列型、「価格」は数値型であると推測します。
apple' UNION SELECT 1,'A',1 ;--を挿入します。

img04-4

上記画像から、検索で使われているSELECT文は3カラム分で、数値型、文字列型、数値型の順番で読み込んでいることがわかりました。

UNION句を用いた攻撃では、

  • カラム数を特定する
  • データ型を特定する

という作業を地道に検証する必要があります。

では、最後にINFOMATION_SCHEMAの情報を取得します。
ユーザの認証情報を得るためには、それらが格納されている、テーブル名、カラム名の情報を得る必要があります。

テーブル名、カラム名、データ型の情報は文字列型なのに対し、元のSELECT文には文字列を出力するカラムは1つしかありません。
そのため、CONCAT()を使って文字列を結合させて、2列分の情報を1列にまとめて表示させます。

商品検索ページのテキストボックスに以下の値を入力します。

apple' UNION SELECT 1,CONCAT(COLUMN_NAME,'.',TABLE_NAME),1 FROM INFORMATION_SCHEMA.COLUMNS ;--

img03

DBMSが持っているすべてのカラム名とテーブル名が表示されました。
上記画像に表示されているitem_id.itemsname.itemsprice.items
商品の情報が格納されているテーブルの名前は「items」であり、「商品番号」「商品名」「価格」に対応していると推測できます。
また、上記画像に表示されている user_id.usersname.userspassword.users
ユーザ情報を格納されているテーブルの名前は「users」であり、「ユーザID」「ユーザ名」「パスワード」に対応していると推測できます。

3.別テーブルからUNION句を用いて実際に格納されている値を取得する

最後に、ユーザ情報を格納していると推測されるテーブルを取得します。
商品検索ページのテキストボックスにapple' UNION SELECT user_id,CONCAT(name,'.',password) ,1 FROM users ;--を入力します。

img04-5

上記より、このアプリケーションに登録されているすべてのユーザのユーザID、ユーザ名、パスワードを取得できました。

ブラインドSQLインジェクションについて

攻撃例の説明ではSQL文の実行結果がそのままブラウザに表示される場合でのSQLインジェクション攻撃について説明しました。
しかし、SQL文の実行箇所すべてが実行結果を返却するとは限りません。
SQLの実行結果が表示されない場合にもSQLの構文を駆使すれば送信後の応答時間や、応答の可否によってデータベース内の情報を特定できます。
そういった攻撃手法のことをSQLインジェクションの中でも、ブラインドSQLインジェクションと呼びます。
攻撃の難易度としては、ブラインドSQLインジェクションの方が難しいですが、影響範囲としては通常のSQLインジェクションと差はありません。

被害例

JVN ipediaに登録された本脆弱性の件数

直近10年間で実際にJVNでSQLインジェクション(CWE-89)の脆弱性が存在する可能性があるとして報告された件数です。

年代 件数
2012 8件
2013 18件
2014 48件
2015 133件
2016 165件
2017 484件
2018 505件
2019 492件
2020 511件
2021 651件
2022 913件

 

img04

※上記の表はMyJVN APIを利用してCWE識別子をもとに統計した件数です。
JVNDBRSS - JVN iPediaにて公開している脆弱性対策情報の概要

対策方法

根本的な対策方法として以下2つを紹介します。

  • プレースホルダの使用
  • エスケープ処理を行う

根本的な解決

プレースホルダの使用

プレースホルダとは、SQL文内の可変にしたい値の場所に?などの記号で示しておき、後から?などの記号で示した場所に対して実際の値を機械的な処理で割り当てる手法のことを指します。
プレースホルダでは、?部分を単純な文字列としてあつかうため、ユーザから' or'a'='aのような入力があったとしても'or'a'='aという文字列としてあつかいます。
また、プレースホルダに実際の値を割り当てる処理をバインドと呼びます。

SELECT user_id FROM users WHERE name = ? AND password = ?;

上記のようなSQLの場合、ユーザ名をadmin、パスワードを' or 'a' = 'aでバインドし、「usersテーブルからnameadminかつ password' or 'a' = 'aであるuser_idの列を読み込む」という意味になり、ログイン失敗となります。

プレースホルダには動的プレースホルダと静的プレースホルダの二種類があり、それぞれ下記のような特徴があります。

  • 静的プレースホルダ
    • プリペアードステートメントによって事前に構文解析などの準備をする
    • SQL文の組み立てはDB側で行う
    • SQL文がそれ以降変化することはないのでセキュリティ面ではもっとも安全

img05

  • 動的プレースホルダ
    • SQL文の組み立てはアプリケーション上のライブラリを用いて行う
    • セキュリティ面では静的プレースホルダに劣る

img06

以上より、SQL側で構文解析を先に行い、その後SQL文が変更されることのない静的プレースホルダが推奨されます。
SQL文を実行する際は静的プレースホルダを使用してください。

エスケープ処理を行う

こちらの対策は、プレースホルダを用いた実装ができない場合にのみ対策として使用してください。
SQL文として特別な意味を持ってしまう文字を別の文字に置き換えます。
以下はMySQLでエスケープ処理を行う例です。DBMSの種類や設定によってエスケープ処理をするべき文字が異なるため、DBMSに応じて対策する必要があります。

  • '」→「''
  • \」→「\\

まとめ

このように、SQLインジェクションの脆弱性があることで、SQL文を書き換えられてしまいアプリケーションの意図していない操作をされてしまいます。
想定される被害としては、情報漏洩、データの改ざん、認証回避などがあります。
こういった被害を防ぐにはセキュアなコーディングが必要です。
そのために運用ルールの整備やガイドラインの導入をおこなうといった運用の整備と、実際に作りこまれたものを検知する仕組みが必要です。
すでに出来上がっているサイトについては、定期的に脆弱性診断を実施して、安全な状態になっていることを確認することをお勧めします。