RDBMSから学ぶデータセキュリティ

(更新日: 2018/08/13)

RDBMSはデータセキュリティを学ぶには良い題材です。RDBMSはできる限り正しいデータを保存する仕組みがあるからです。RDBMSからどのようにデータのセキュリティを保証するのか紹介します。

プログラムが正しく動作する為には

  • 正しいコードであること
  • 正しい(妥当な)データであること

絶対条件です。どちらが欠けてもプログラムは正しく動作しません。

SQLiteは手軽で良いのですが、組み込み型であるためデータセキュリティを学ぶには機能的に問題があります。SQLiteにはデータ型を保証する機能もデータの内容を検証する機能もありません。1

利用するRDBMSはPostgreSQLを用い、データセキュリティに関連性の強い機能のみを対象とします。

RDBMSのデータセキュリティ

RDBMSはきっちりとデータを管理する仕組みを提供するシステムです。この為に色々な仕組みが提供されています。

  • データ型の制約
    • 文字列の長さ、文字エンコーディングの強制
    • 数値型の精度の強制
  • データの制約
  • データ関係の制約
  • プロシージャーによるデータバリデーション

データ型の制約

データ型には”正しいデータ形式”があります。使われている文字や値、データ構造は”正しいデータ形式”であることが要求されます。

以下のようなテーブルを作り、動作を確認してみましょう。

整数型

データ型の強制は強いデータ型を持つプログラミング言語のデータ型と同じです。

ただし、RDBMSデータ型はC言語などとは異りオーバーフローを許しません。2

このようにオーバーフロー/アンダーフローするデータを使うとバリデーションエラーになります。

PostgreSQLの場合、大きな数値が取扱える任意精度型のNUMERIC型が用意されています。INT8よりも大きな整数を取り扱う場合はNUMERIC型を利用します。

弱いデータ型を持つPHPやJavaScriptではオーバーフローすると”不正確な浮動小数点型”になります。

PHP

JavaScript(ChromeのConsoleで実行)

この動作では”正しいデータが変数に保存できた”とは言えません。”正しい整数”が必要な場合にはこういった動作は検出しないと、”正しいコード”であったとしても”間違った結果”となります。

浮動小数点型

浮動小数点型はそもそも正しい値を保存できません。”正しい値”を保存できるケースは限られています。このためRDBMSでも整数型のような厳しいチェックはありません。

RDBMSでもPHPやJavaScriptのような変換が行われます。

IEEE754の浮動小数点型には無限大(INFまたは-INF)が定義されています。しかし、PostgreSQLの場合は明示的に無限大(表現可能な範囲を超えた値)であると指定しないと保存できません。

このように浮動小数点型でもオーバーフローが検出されバリデーションエラーになります。

無限大(INF/-INF)を保存するには明示的に指定しないと保存できません。

明示的にINF/-INFを指定したときだけ、浮動小数点型の範囲を超える、つまりオーバーフローする値でも無限大として保存できます。

NUMERIC型で小数点以下の値を保存できるようにした場合も同じです。

指定精度への”丸め”は自動的に行いますが、オーバーフローはバリデーションエラーになります。

CやPHP、JavaScriptの浮動小数点型はオーバーフローする場合は”不正確な”INF/-INFへの変換を勝手に行いエラー/例外になりません。

PHP

JavaScript

値が何であるか分らないけれども、勝手に取り敢えず無限大にしてしまう、という動作では”正しい動作を保証している”とは言えません。無限大には、符号を変える以外、何をしても正しい結果になりません。

 

文字列型

RDBMSの文字列型は文字列の長さをデータ型として定義できます。

長過ぎる文字列はエラーになり保存できません。

文字列型には文字エンコーディングがあり、壊れた文字エンコーディングもチェックされます。テストに使っているデータベースの文字エンコーディングはUTF-8です。

しかし、これで完璧とは言えません。PostgreSQLにencode()/decode()関数があります。これを使うと壊れた文字エンコーディングでも文字エンコーディングチェックをバイパスできます。先ほどと同じ壊れた文字エンコーディングデータをdecode()を使って挿入してみます。

挿入できてしまいました。

これはdecode()を使うとデータがbytea型として保存されるのでこうなります。

データをよく見ると\xから始まっていることが分かります。これは\x以降の文字列がHEXエンコーディングでエンコードされたバイナリであることを意味しています。bytea型データの中身は何でも構いません。UTF-8文字列データとして妥当なので保存できます。

Bytea型にエンコーディングすると壊れた文字エンコーディングでも保存できるように見えたかも知れませんが、仕組的に壊れた文字列データを文字列型データカラムに保存できないようになっていることが分かります。3

他のデータ型

ENUMやinet、uuid、json、xmlとといったデータ型も”指定した形式”の仕様にあっていないとデータを保存できません。XMLのような複雑なデータ型でも中身がバリデーションされます。

サンプルテーブルで定義したデータ型の制約(バリデーション)くらいは紹介しよう、と思っていましたが長くなりすぎるので省略しました。試しに色々な値を挿入してみてください。おかしな値は可能な限り挿入できないようになっています。

データの制約

“データ型として正しい”だけでは”正しいデータ”とは言えません。単純な数値型であっても”正しいデータ”には”正しい範囲”があることが多いです。

PostgreSQLの場合、データ型の範囲外である場合は自動的にバリデーションされエラーになります。しかし、これでは正しいデータとしてまだ不十分です。この為、CHECK制約が用意されています。

CHECK制約

年齢データの場合、0才以上かつ150才未満である、とするのが今のところ妥当でしょう。

CHECK制約はデータの挿入/更新前に確認され、妥当なデータだけが保存できます。

文字列型のように何でも保存できてしまうデータ型の場合、データ形式がチェックしないと”正しいデータ”であることを保証できません。

例えば、HEXエンコーディングされた32文字のハッシュ値である場合は以下のようにCHECK制約でバリデーションします。

文字列の場合、実行効率を考えると長さのチェックを最初にすべきです。その後、正規表現で内容をチェックし、データ形式が”確実に正しい”形式であることを保証しています。

改行などHEX文字列に含まれない文字がある場合はバリデーションエラーとなります。

より複雑なデータ型のCHECK制約

複合型、配列型、JSON型、XML型などのデータ内容によってバリデーションしたい場合があります。

PostgreSQLではこういった場合でも比較的容易に複数の要素を持つデータ型のバリデーションが行えます。

CHECK制約をデータ型の直後に書くと制約が大きく成りすぎて、テータ定義の見通しが悪くなります。制約は別途に記載することも可能です。

 

NOT NULL制約

特定のデータは必ず存在しなければならない、といった場合はNOT NULL制約が利用できます。

空文字列とNULLでは大きな違いがあり、全く別の物です。CHECK制約を使って1文字以上の制約を付けても、NULLデータの挿入は防げません。mytable4ではNOT NULL制約を付けてNULLデータをバリデーションしています。

CHECK制約で文字数をバリデーションしているので’’(空文字列)はエラーです。

文字エンコーディングはデフォルトでバリデーションされます。スペースや記号といった名前として不適切と考えられる文字のチェックも行うべきですが、ここでは省略しています。

UNIQUE制約

ID型データの場合、その値はデータセット中で一意である必要があります。この場合はUNIQUE制約を使います。

UNIQUE制約を適用すると同じ値のデータの存在をバリデーションし、重複を避けます。

データバリデーションの多くは形式的なチェックですが、UNIQUE制約は論理的な制約の一種です。

データ関係の制約

このデータセットが存在する為には他のデータセットが存在しなければならない、といった制約が必要(親子関係が必要)になることがあります。

RDBMSのRはRelational(関係)で、その名前の通り関係の整合性を強制する仕組みも持っています。

FOREIGN(外部)キー制約

1対多、多対多という関係が正しく保たれないとカラム/テーブルの”データ形式/データセット”として正しくても、”論理的には壊れている”データベースが出来てしまいます。FOREIGN(外部)キー制約を使うと論理的関係の正しさ保証できます。

Weatherテーブルのreferences cities(city)でCitiesテーブルのcityプライマリキーを参照しています。

存在しないcityに対してweather情報を挿入しようとしてもバリデーションエラーとなりクエリに失敗します。

REFERENCES制約も論理的な制約の1つです。

プロシージャーによるデータバリデーション

ここまで様々な制約によるデータバリデーションをざっと見てきました。これでも足りない!といった事は十分にあります。

RDBMSの場合、ストアードプロシージャー(プログラム)によるバリデーションが可能です。しかも、事前条件(データ操作前)のチェックのみでなく、事後条件(データ操作後)のチェックも可能です。

例えば「より複雑なデータ型のCHECK制約」の長さチェックをストアードプロシージャーでチェックするとこうなります。

PostgreSQLの場合、PL/PgSQL以外にもPerl、Python、PHP、JavaScriptといった言語でもストアードプロシージャーを記述できます。

ストアードプロシージャーはプログラムなので、どのような複雑なバリデーションでも自由自在に行うことが可能です。

例えば銀行口座などの取引で、取引後の口座残高は必ず0以上でなければならない、といった場合には以下のようにプロシージャーでバリデーションできます。

 

RDBMSのデータバリデーションから学ぶプログラムのデータバリデーション

データベースはデータを”セキュア”に保存するためのシステムです。データのセキュリティに関係する仕様や制約により安全にデータを保存する(おかしなデータはバリデーションで弾く)仕組みであることが分かりました。

「正しい(妥当)なデータ」であることがとても重要です。

アプリケーションも正しいコードだけではなく、

  • 正しい(妥当)なデータ

であることを保証する(=バリデーションする)ことがとても重要です。「正しい(妥当)なデータ」でしかプログラムは正しく動作しないので重要というよりも、「正しい(妥当)なデータ」は必須の必要条件です。

ライブラリレベルでは”正しい(妥当)なデータ”であることを保証するコードを書かないことがとても多いです。仕組的に不可能であるモノも少なくありません。

コマンド実行はライブラリ呼び出しではありませんが、ライブラリ関数呼び出しでも同じようなことが起きます。

コマンド実行時、コマンドと引数を分離すれば完璧?

DBでも雑なデータセキュリティ対策

RDBMSはかなりしっかりしたデータセキュリティ対策が取れる仕組みを提供しています。しかし、多くのシステムは”かなり緩い”、キツい言い方をすると”雑な”データセキュリティ対策しかしていません。

DB側で厳格にバリデーションしない正当な理由として”パフォーマンス問題”があると思います。しかし、多くの場合は”更新そこそこ”で”参照のみパフォーマンスが必要”といった場合でもかなり雑なデータセキュリティ対策しか実装していないです。

アプリのデータセキュリティ対策はDBよりももっと緩い

プログラムが正しく動作する為には正しい(妥当な)データが必要なのですが、Webアプリのほとんどはデータセキュリティ対策があってもとても緩いデータセキュリティ対策であるか、多くの場合はそもそも対策していないケースが目立ちます。

インジェクション攻撃対策は出力対策として行うモノ、と考えている方も少なくないと思います。実際、送信され保存されるデータの内容がデタラメであるかどうか、は別にして出力する際には「出力データが出力先で誤作動を起こさない無害化されたデータであること」を保証することは必須です。

しかし、インジェクション攻撃対策は出力対策”だけ”で行うセキュリティ対策はとても雑なセキュリティ対策です。そこら中で出鱈目なデータが保存される可能性、そこら中で対処が難しいセキュリティ問題が発生する可能性があるので「雑なセキュリティ対策」と言えます。

雑で不十分とはいっても、「プリペアードクエリだけでSQLインジェクション対策は十分!」とする不十分/不完全な対策「コマンドインジェクション対策はコマンドと引数の分離で十分!」とする雑な対策、だけでもそれなりには効果を上げてきています。

効果を上げてきているからそれで良いという物ではなく、そもそも「出鱈目なデータはアプリケーションレベルで全て完全に廃除する」のが丁寧なセキュリティ対策に欠かせないモノです。これを書き出すととても長くなるので、

データの正しさ(妥当性)をバリデーションしていないアプリケーションのセキュリティは”ユルユルです”と言われても仕方ない状況に現在はあります

と書いて結びにします。丁寧なセキュリティ対策を実装したアプリケーションには、丁寧なテーブル定義をしたデータベースと同等以上のデータセキュリティ対策が必要です。詳しくは参考リンクをご覧ください。

参考リンク:

 


  1. 関数を作る機能はありますが、フル機能のRDBMSとは比べ物になりません。 
  2. MySQLの場合はデフォルトのモードではオーバーフローします。ANSIモードの場合はオーバーフローを許しません。 
  3. PostgreSQLはASCIIエンコーディングもサポートしており、これをデータベース文字エンコーディングに設定するとバイナリでも何でも保存できます。 
Facebook Comments
Pocket