SQLite3の全てのカラムがテキスト型である問題に対する誤解

(更新日: 2015/04/08)

以前にSQLite3のデータ型は基本的には全てテキスト(例外は整数型プライマリーキー ※)である、という解説をしました。

どうもこの問題は強い型を持っている言語には影響がないとの誤解があるようなので解説します。ついでに明らかだとは思いますが、他のリスクも紹介します。

※ 正確にはBLOB型も例外になります。テキストではないデータも保存できます。SQLiteのサイトでは整数型プライマリーキーは例外と記述されていましたが、手元のSQLiteで試すと文字列も保存できてしまいました。

SQLiteのデータ型変換問題

SQLiteは基本的にカラムのデータ型に関わらず”テキスト”として保存します。弱い型を持つ言語(例えばPHP)の場合、データベースへの保存する前のバリデーションが甘い場合、整数型などにテキストが保存されます。プリペアードクエリ、エスケープのどちらを使っているのか、は関係ありません。

この結果、一般的なDBではレコード保存時にエラーとなり保存できない”文字列”が保存できてしまい、

などとすると$idには0が入ります。

0に変換される問題以外にも問題となる場合があります。

現在は全ての変数はHTMLエスケープしてから出力するのが当たり前ですが、SQLiteの整数型などのカラムはHTMLコンテクストにそのまま出力しても安全、と思っているとインジェクション攻撃に脆弱になる可能性があります。

数値だからNULL文字/改行インジェクションはできないはず、と思っていると問題になる可能性があります。

整数型に変換した場合は意図せず整数の0になる、という問題を紹介しましたが、”文字列”として保存されて、”文字列”として返されるのでありとあらゆるインジェクション攻撃の可能性があります。型が強い言語で、整数型の変数に保存している場合は「意図せず整数の0になる」「サービス不能攻撃に脆弱になる可能性」という問題に限定されている、というだけです。

整数型などに文字列が入ってしまうとエラーになるようなアプリケーションでは、これらのリスク以外にサービス不能攻撃に脆弱になる可能性があります。

 

RDBMSが戻すデータはテキスト

RDBMSが戻すデータは基本的に”テキスト”です。(DBMS/APIによって異る場合もある)

例えば、PostgreSQL/MySQLのC APIでは以下の様にカラムのデータが”文字列型”として返されていることが分かります。

PostgreSQL

http://www.postgresql.org/docs/9.4/static/libpq-exec.html#LIBPQ-EXEC-SELECT-INFO

MySQL – APIシグニチャがないのでサンプルプログラムを掲載。rowは”文字列型”配列。

http://dev.mysql.com/doc/refman/5.7/en/mysql-fetch-row.html

データベースが表現できる”データ”はプログラミング言語のデータ型で表現できる範囲に収まるとは限りません。データが別の値に変わってしまうことを防ぐため、データベースは基本的にテキストとしてデータを返します。

 

プログラムの処理が正しいか?も重要

“テキスト”として返って来たデータをどのように扱うかはプログラム次第です。例えば、PHPのPostgreSQLモジュール、MySQLiモジュールはデータベースカラムの値を”テキスト”として返します。

例えば、静的な型を持つC言語の文字列変換関数atoi()、atol()などの場合、

atoi() 関数は、nptr によって指示される文字列のはじめの部分を int 型整数に変換する。 この振る舞いは、
atoi() 関数がエラーを見つけない点以外は、

strtol(nptr, NULL, 10);

と同じである。

となっています。言語が静的な型を持っているのかいないのか、はSQLiteのカラムが基本的に”テキスト”である問題とは関係ありません。

プログラムの処理が正しいか?も問題になります。

データベースアクセスライブラリの利用者はライブラリがエラー(文字列型データを非文字列型データのカラムへ入力/出力した場合のエラー)を検出できるかどうか?が問題になります。通常の強い型付けのデータベースのみを想定しているライブラリの場合、全くエラーとならない場合もあるでしょう。

エラーが発生する場合でもサービス不能攻撃に脆弱性になるリスクもあるので、結局はライブラリなどに頼らず「データベースへの出力が正しい」ことを保証する方が良いです。

 

まとめ

データベースへ正しい値を渡す責任はアプリケーションにあります。SQLiteも利用する場合、カラムへ保存するデータ、保存されてデータに追加の注意が必要で、これは言語のデータ型仕様がどうなっているのか?はあまり関係がありません(リスクがゼロではない)。型が弱い言語の場合、様々なインジェクション攻撃のリスク、サービス不能攻撃のリスクが増加するだけで、データ型の強い/弱いに関わらず同じ注意が必要です。

入力を受け付けた時にバリデーション(境界防御)、データベースに保存(出力)する前にバリデーション(多層防御)を組み合わて対応すると良いでしょう。

ライブラリを利用していて、そのライブラリがエラーなく処理(数値でない文字列を数値に変換)してしまう場合は特に注意が必要です。動作がどうなっているかよく分からない場合は、整数型などのカラムに”文字列”を保存し、取得した時どうなるか確認すると良いでしょう。

セキュリティを維持するには入力/出力を確実に制御することが重要です。確実に制御するには入力先/出力先の仕様を良く理解します。Webアプリの場合、ほとんどの入力/出力がテキストなので正確なテキスト処理が重要です。

 

追記

データベースおかしな値が保存される事になりますが、”0”に特別な意味を持たさない、という消極的な対策でも十分な場合も多いでしょう。

Comments

comments

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です