記事一覧へ戻る

 

As Published In
Oracle Magazine
2015年5/6月

テクノロジー:Ask Tom


よりセキュアなアプリケーションへ

Tom Kyte 著(Oracle Corporationデータベース・エバンジェリスト)

(原文はこちら)

 

~アプリケーション設計にセキュリティを実装する方法をオラクルのエキスパートが伝授します~

「私の作ったアプリケーションのセキュリティ、たとえば、SQLインジェクションといった脅威が心配です。アプリケーションがハックされる可能性を最小限にするには何をすればいいでしょうか?」

ハッキングのニュースを聞かない日がないほど、事件が毎日のように相次いでいる今日この頃ですので、これはとても良い質問です。IDやクレジットカード情報の盗難にしろ、個人情報の漏洩にしろ、新しいセキュリティ事故が頻繁に次々と発生しています。

こうした事故をなくす、あるいは減らすために、アプリケーション設計段階においてできる、いくつかのことがあります。

アプリケーションを安全なものにするには、アプリケーションの開発過程で策を講じることが必要であり、既存のアプリケーションにセキュリティを組み込むことは非常に難しいことです。

アプリケーションの構築設計段階であなたが出来る、最も重要なことは以下のとおりです。

  • (1) 以下のマニュアルを必ず読んでおいてください。これらのガイドで、セキュリティの観点で検討すべきことの概要や、Oracle Databaseがセキュリティの分野で提供する機能をよく理解することができます。
  • (2) ”最小権限”の概念を採用してください。
  • (3) 複数のスキーマを使用してください。オブジェクトを分離し、最小権限の原則を実現するのに役立ちます。
  • (4) バインド変数を使ってください。バインド変数は、拡張性やパフォーマンス向上のためだけのものではありません。アプリケーションをSQLインジェクション攻撃から守るのに役立ちます。
  • (5) 複数の防御層を採用してください。アプリケーションのコードだけにセキュリティ対策を施さないこと。異なるテクニックを用いて、データベースの中でできるだけ多くの防御を繰り返して下さい。これにより、仮に単一の防御層にバグがあっても、あなたのデータベースを守ることが出来ます。

上記のセキュリティ戦略の詳細について説明していきます。

最小権限


これは、データベース・セキュリティの基本原則です。アプリケーション・スキーマや、中間レイヤーからデータベースに接続するために用いられるスキーマにアクセスするDBへの特権付与を、出来るだけ最少にとどめましょう。

よくあることですが、アプリケーション開発者たちは単純に自分が楽をしたいために特権を欲しがりがちです。たとえば、開発者が、他のアプリケーション・スキーマから(複数の他のスキーマ内の複数のテーブルから)データを必要とするアプリケーションを扱う場合に、”SELECT ANY TABLE”権限を要求しがちです。

この権限があれば、他のスキーマから必要なテーブルを入手することができます。アプリケーション開発者は、コードの開発を、もっと高速にするために、より”機敏”になれたと感じるわけです。なぜなら、彼らはもはや、再びSELECTの許可を要求する必要がないのですから。

攻撃者は、そのアプリケーションにSQLインジェクションの弱点を見つけることができれば、ほぼ確実に、データベース内のすべてのデータに対して、少なくとも読み取ることが可能になります。そのアプリケーションがアクセスするテーブルだけでなく、データベースの全てのテーブルです。

”SELECT ANY TABLE”権限は、実際のセキュリティ監査を乗り切ることも非常に難しくします。 そのアプリケーションが本当に”SELECT ANY TABLE”権限が必要な理由を説明する方法はないでしょう。 さらに、そのアプリケーションが本当に必要とするテーブルについての記述も存在しないことでしょう。

”ANY”の許可は、アプリケーション・スキーマに決して与えるべきではないのです。 ”CREATE ANY CONTEXT", "SELECT ANY TABLE", "DROP ANY TABLE”といった、”ANY”のキーワードのついた許可のもつ力はアプリケーションが必要とするものをも超えています。開発者が必要とするものを得る他の方法は常に存在するのです。

たとえば、私は、アプリ開発者が他のスキーマ内のテーブルを削除(TRUNCATE)する必要があるとの理由で”DROP ANY TABLE”がアプリケーション・スキーマに許可されているのを見たことがあります。

テーブルのTRUNCATEに関して、『データベースSQL言語リファレンス』 には、こう記述されています

「表を切り捨てるには、その表が自分のスキーマ内にあるか、自分にDROP ANY TABLEシステム権限が付与されている必要があります。」

これは確かに正しいのですが、他のスキーマのテーブルを削除するというゴールを満たすために、”DROP ANY TABLE”権限を付与される必要はないのです。

これはとても重要なところです。- ここではスキーマ”X”のテーブル”T”を削除するのがゴールです。このゴールを達成するには少なくとも2つの方法があります。

  1. 強力で危険な、”DROP ANY TABLE”を使用する。
  2. スキーマ”X”(そのテーブルのオーナー)としてストアド・プロシージャを実装して、削除を実行する。また、このプロシージャに”EXECUTE”権限を付与する。

もし、あなたがアプリケーション・スキーマに”DROP ANY TABLE”権限を付与され、攻撃者がそのアプリケーションにSQLインジェクションの弱点を見つけたとしたら、その攻撃者は、”DROP ANY TABLE”権限を持ってしまうのです。それによる損害がどれほどのものか考えてみて下さい。

最小限の権限でゴールを達成する別のアプローチが、取るべき正しい道です。

次の例について考えてみましょう。

SQL> create user a identified by a;
User created.

SQL> create user b identified by b
  2  default tablespace users
  3  quota 5m on users;
User created.

SQL> grant create session to a;
Grant succeeded.

SQL> grant create session,
  2        create table,
  3        create procedure
  4  to b;
Grant succeeded.

今、私は、”A”と”B”という2つのスキーマを得ました。Aは、ただログインするためだけの権限、 ”B”は、ログインしてテーブルとプロシージャを作成する権限があります。 では、私は、”B”としてログインして、オブジェクトを生成します。

SQL> connect b/b
Connected.

SQL> create table t
  2  as
  3  select *
  4    from all_users;
Table created.

SQL> create or replace
  2  procedure truncate_table_t
  3  authid DEFINER
  4  as
  5  begin
  6      execute immediate
  7        'truncate table B.T';
  8  end;
  9  /
Procedure created.

SQL> grant select on t to a;
Grant succeeded.

SQL> grant execute
  2    on truncate_table_t
  3  to a;
Grant succeeded.

スキーマ”B”は、今、あるデータを持つテーブルTと、また、テーブル”B.T.”を削除するプロシージャのデファイナーの権利を持ちます。デファイナーの権利のルーティン(ストアド・プロシージャのデフォルト・タイプ)は、そのプロシージャのオーナーに直接付与された権限で実行されます。つまり、役割を通じてスキーマ”B”に付与された権限を差し引いた、”B”のすべての権限です。 スキーマ”B”は、スキーマ”A”がテーブルTを読んだり、ストアド・プロシージャの ”B.TRUNCATE_TABLE_T”を実行することを許します

私は、”A”としてログインし、私が出来ることを確認します。

SQL> connect a/a
Connected.

SQL> select count(*) from b.t;

  COUNT(*)
------------
       55

テーブル”B.T”が存在するのが見えます。問い合わせると、データを持っています。 では、ユーザ”A”として、テーブル”B.T”を削除してみようと思います。

SQL> truncate table b.t;
truncate table b.t
                 *
ERROR at line 1:
ORA-01031: insufficient privileges

私は、このテーブルを削除するのに十分な権限が付与されていません。 ”A”によって削除の実行を成功させるには、”DROP ANY TABLE”権限を付与される必要があります。しかし、それは、私が、”B.T”を削除するために、”DROP ANY TABLE”権限を付与される必要がある、という意味ではないのです。私は次のストアド・プロシージャを実行することができます。

SQL> exec b.truncate_table_t;
PL/SQL procedure successfully completed.

SQL> select count(*) from b.t;

  COUNT(*)
--------------
        0

これで、”B.T”を削除するという目的を達成しました。でも、”DROP ANY TABLE”権限は必要としませんでした。脅威にさらされる危険をかなり制限されましたが、それがなくなったわけではありません。スキーマ”A”が実行するコードの中にSQLインジェクションの標的となるバグを見つけた攻撃者は、”B.TRUNCATE_TABLE_T”プロシージャを実行することができそうですが、それでも私はまだその脅威を大幅に減らしています。

そのデータベース内のすべてのテーブルを失うリスクから、1つのテーブル内のデータ、すでに削除されたテーブルを失うだけのリスクにとどめることができました。ストアド・プロシージャの使用は、スキーマをまたがって必要とされる権限を減らすための優れた方法であり、最小の権限という概念を実現するのに役立ちます。 ここでは、スキーマ”A”は、対象とする1つのテーブルだけを削除できるプロシージャでのみ、 ”EXECUTE”権限を必要とします。

【メモ】Oracle Database 12c には、最小の権限という概念を実行するのに役立つ、新しい権限分析ツールが含まれています。詳しくは、『Oracle Database Vault管理者ガイド』をご参照ください。

複数スキーマの使用


たぶん、このアイデアは、私が提案した他のどのセキュリティ対策案よりも開発者達の抵抗を受けるでしょう。そこで私は、過去のコラムの質問をもう一度投げかけたいと思います。

「職場のあるデータ設計者が、アプリケーションのコード(パッケージ、プロシージャ、ビューなど)とデータ(テーブル、マテリアライズド・ビュー、インデックスなど)を保持するのに別々のデータベース・アカウントを使おうという提案をしました。

私はこれまでこのようなアイデアに出会ったことはありませんでした。カプセル化の概念に逆らうように見えますし、そのアプリケーションは少なくとも2つのスキーマにまたがって分断され、それらの間で必要な権限を管理する負荷が増えてしまいます。

これのどこが推奨できるアプローチだと思いますか? もし、あなたがこの方法を実行するとして、アプリケーション・スキーマからデータ・スキーマ内のオブジェクトをどのように参照するのが望ましいと思いますか?そして最後の質問ですが、あなたは、コードかデータ・スキーマにビューを使いますか?」 

この問いに対する私の最初の回答は、こちらこちらで知ることができますが、もう一度この質問を読み返してみると、質問者は、セキュリティの観点で非常に有益なことを”しない”理由を探そうとしているように見えます。

開発者たちは、”カプセル化”といった言葉を持ち出すでしょうし、(実際、複数のスキーマを持つことがカプセル化を促進しますが)”アプリケーションの作成には、最小の権限という概念を持つべきだ”とポイントがずれたことを言いつつ、”必要な権限を管理する負荷が増える”と訴えるのでしょう。ある開発者には欠点とみなすものが、私には、有益なものに見えるのです。

私のアプローチは、テーブル・データを含む、少なくとも1つのスキーマを持つことです。たぶん1つ以上ですが、テーブル・データだけを持つ最低1つのスキーマと、この記事の最後で説明するようないくつかのプロシージャも必要に応じて持ちます。
これらのテーブルにアクセスする独自のコード(PL/SQL、Javaストアド・プロシージャなど)が第2のスキーマとして存在しえます。それは必要とされるさまざまなテーブルのビューも持つでしょう。

テーブル・データを含む第1のスキーマは、第2の”コード”のスキーマに、テーブル上で必要な権限だけを付与します。
(”GRANT ALL ON TO another_schema”はありえません)データ・スキーマは、INSERT, UPDATE, DELETE, SELECTといった、必要なアクセスだけを付与します。

そして、第3のスキーマがあるとして、このスキーマは、ログインするための”CREATE SESSION”以上の権限は付与されず、アプリケーションによるプロシージャの実行や、ビューにアクセスするために必要な最小限の権限が、第2のスキーマに付与されます。この第3のスキーマ(データベース・アカウント)は、アプリケーション・サーバがデータベースに接続するために使用するものです。

上記のアイデアがもたらす利点について考えてみて下さい。
もし、ハッカーがそのアプリケーション・スキーマに入れたとしても、損害は限定的なものとなります。彼らは、どのテーブルも読むことは出来ませんし、読めたとしてもごく稀でしょう。そして、データ・アクセス・レイヤーとしてストアド・プロシージャを使えば、ハッカーはどのテーブルにもアクセスすることができなくなります。彼らが唯一できることは、あなたのアプリケーションを実行させることだけです。ハッカーは、どのテーブルも削除できません。もしすべてに単一のスキーマを使っていたら、テーブルの削除や更新もできてしまっていたでしょう。

話をもう少し具体的にしましょう。あなたのアプリケーションが、アプリケーション監査証跡を持っていると仮定します。典型的なアプリケーション・ユーザが、この監査証跡に加えられる必要がありますが、そのユーザが、それを読み出すことも、削除することも、変更することも出来るべきではありません。また、あなたは、監査証跡を読み取る必要がある管理アプリケーションも持っているかもしれませんが、それを監査証跡に追加したり、更新したり、削除する必要もありません。

もし、単一のスキーマを使っているならば、そのアプリケーションと管理アプリケーションの両方とも、このテーブルでの”READ/WRITE”のフル・アクセス権限を持ちます。あなたは、「我々のアプリケーションはセキュリティを強化しているので、心配ないです。」というかもしれません。でも、私は心配です。いつ、どこに、あなたのアプリケーションにバグができるとも限らないし、それに、その監査証跡は、改ざんの危険にさらされてしまいます。

 

代わりに、あなたがその監査証跡をそれ自身のスキーマの中に入れ、2つのコード・スキーマ(1つは典型的なアプリケーション・ユーザ用、もう1つが典型的な管理アプリケーションユーザ用)を作成すれば、1つ目のコードスキーマには、その監査証跡のテーブル上での”INSERT”権限を、そして2つ目のコード・スキーマには”SELECT"権限を付与することができます。
これで、1つ目のスキーマは、その監査証跡に追加するコードを作成でき、2つ目のスキーマは、レポーティングのためのビューを作成したり、かわりにREF CURSORを返すストアド・プロシージャを使用することができます。

最後に、1つ目のアプリケーション・スキーマのコードに対して、”CREATE SESSION”と”EXECUTE”の権限を持つスキーマ、そして、2つ目のスキーマのコードに対して、”CREATE SESSION”と”EXECUTE”の権限を持つ管理ログインを作成します。これが、最小特権の原則を最大限に生かすことになります。その管理スキーマは、自身を監査するためにアプリケーション・スキーマのコードを使用し、監査証跡上で(変更することなしに)レポートすることができます。そのアプリケーション・スキーマも、監査証跡を読み取らずに自身を監査することができます。

この複数スキーマ・アーキテクチャのアイデアを実践するには、「Oracle Database 2日で開発者ガイド」の第9章、「簡易的なOracle Databaseアプリケーションの開発」で詳細を参照してください。

バインド変数の使用


SQL文で、実行のたびに変わりえるすべての変数にバインド変数を使用するとコードがSQLインジェクション攻撃を受けないことを知っていましたか?一方で、SQLにこれらの変数を入れるために文字列連結を使うと、そのコードがSQLインジェクション攻撃を受けてしまいます。

つまり、あなたが”SELECT * FROM EMP WHERE ENAME LIKE ?”といったSQLを発行して、その”?”にある値を結びつけた場合は、誰もあなたのSQLの意味を変更することができません。

一方、次のような文字列連結を使用してSQL文を作成したとします。

SELECT * FROM EMP WHERE ENAME 
LIKE '" + some_variable +"'

これだと、あなたのコードはいとも簡単にSQLインジェクション攻撃をうけてしまうでしょう。

私の経験では、殆どではないにしても多くのデータベース攻撃は、SQLインジェクションによって行われています。具体的には、攻撃者が、SQLをあなたが意図している結果とは違うものにするようなインプットをすることによって行われます。

これと闘うためのプログラム的な方法が存在します。たとえば、SQLを作成するときに、PL/SQLの”DBMS_ASSERT”パッケージを使い、そのインプットがSQLと連結しても大丈夫かどうかを検査する、サニタイズの独自のルーチンを書き、また沢山のコードを書くことです。

それでも、あなたが考えたこともなかった攻撃ベクトルについて心配しなければならないでしょう。 (私のブログの過去記事で、ほとんどの人が予測できないSQLインジェクション攻撃の興味深い例をとりあげています)どんなプログラム的な戦略を使っても、貴方のコードは、あなたが思っているほど安全ではない可能性は残されているのです。

別の手段として、バインド変数を使うことができます。
バインド変数を使うと、攻撃者は、”SELECT * FROM EMP WHERE ENAME LIKE ?”を他のいかなるSQLにも変えることは不可能になります。

一方、攻撃者は、

SELECT * FROM EMP WHERE ENAME 
LIKE '" + some_variable +"' 

を、”' or 1=1 ? ”のインプットを与えることによって、

SELECT * FROM EMP WHERE ENAME 
LIKE '' or 1=1 – ' 

に、比較的簡単に変えてしまうことができてしまいます。

そのインプットは、あなたのクエリの意味を完全に変えてしまいます。さらに、攻撃者は

'UNION ALL SELECT… FROM T – ' 

をかわりにインプットするかもしれません。

あなたのクエリをどうすればいいか考えて見ましょう。
”EMP”テーブルを照会するかわりに、攻撃者は、他のテーブル”T”に照会しているかもしれません。(SQLインジェクションのバグが見つかると、そのスキーマが読み取りアクセスを持つすべてのオブジェクトに対して、最低でもREADアクセスを与えることになりえます。)

もし、あなたが、クエリにインプットするアプリケーションでバインド変数を使わないとすると、あなたは次のことをしなければならなくなるでしょう。

  • インプットをサニタイズするための手続き型のコードを追加で沢山書く。(そして、完璧にできたかどうかが気になって、夜も眠れなくなる)
  • あなたのコードを、最低でも5人の、あなたのことが好きでない人たちにレビューしてもらう。なぜ、”好きでない”人にするかというと、彼らはあなたの侵した間違いを、時間をかけて一生懸命見つけようとするにちがいないからです。もし彼らがあなたのことを好きか、それどころか尊敬していたりしたら、よく見てくれないでしょうから。

しかし、これらのステップを踏んだとしても、セキュリティは保証されません。あなたのコードは依然としてSQLインジェクション攻撃されるかもしれません。なぜなら、それが完璧でもなければ、レビューアーたちもすべてを見つけられないでしょうから。

覚えておきましょう: バグは誰でも起こしえます。
バグの発生は、SQLインジェクションを許してしまうようなものも含めて、私も幾度となく起こしました。何年も前にSQLインジェクションについて私が書いた記事の話をしましょう。
この記事のSQLインジェクションに関するセクションの後、私は最後のセクションまで読むように皆さんに勧めました。

そこでは、この記事の最初のほうのTRUNCATEの例と同様の、”選択的権限付与”を実行するストアド・プロシージャを使いました。しかし、その記事ページは後日修正されたことに注目してください。この記事は、雑誌(Oracle Magazine)として印刷されましたが、私のストアド・プロシージャは、SQLインジェクションの欠陥をもったままの状態でした。そう、私は、SQLインジェクションに関する記事で、SQLインジェクションの欠陥があるコードを提供してしまったのです!こうしたことは誰にでも、高度に熟練したプログラマーにも、新米プログラマーにも、起こりえるのです。

複数の防御層を持つ


複数の防御層を持つことは、最小権限の概念と同じくトップクラスのセキュリティの原則です。 徹底的なセキュリティとして、複数レベルのセキュリティを持ちたいものです。

あなたが、アプリケーションにすべてのセキュリティ・ロジックを入れたら、 ”network/database/storage”レベルの人たちは何にも心配することはしないでしょう。しかし、誰かがそのセキュリティを避ける方法を見つけるかもしれません。問題はいつ攻撃者がその方法を見つけるかなのです。

一方、もし、あなたが複数の防御層(複数の反復的な防御層)をもっていた場合、いずれか1つの防御層に穴があっても、それによって必ずしもデータが侵蝕されることにはなりません。たとえば、ある理由であなたのアプリケーションが文字列連結を使用し、バインド変数を使用しないと仮定します。その場合、貴方のアプリケーションのインプットを検証するために手続き的にサニタイズすることを勧めるでしょう。あなたの文字列連結のコードを複数の人にレビューしてもらってください。

  • (バインド変数を使わないことから)SQLインジェクションの欠陥が不可避となる場合は、それを捕捉するためにOracle Database Firewalを導入する。
  • 他のすべての防御が失敗した場合にリスクを最小化するために、最小権限の概念を使用する。
  • さらにセキュリティのリスクを軽減するために、複数のスキーマを使用する。(そして可能な限り、最小権限を採用する。)
  • アプリケーション・レベル、ファイアーウォール・レベル、そしてデータベース・レベルでの監査を実施する。その情報を一元管理するために Oracle Audit Vaultの使用を検討する。そして、不審なアクティビティを探すリアルタイム監査ポリシーを設定する。

以上で、少なくとも6つの防御層が存在することになりますが、それらの層のそれぞれはどこかに欠陥(攻撃対象となる穴)があるかもしれません。1つ(またはそれ以上)の層が打破された場合のために、複数の防御層を使用しましょう。

まとめ


セキュリティは、今日、最大の懸念点です。インターネット以前の時代は、セキュリティはもっと簡単でした。かつて、我々のデータベースが大勢の潜在的な攻撃者に脅かされるようなこともなく、またその中に非常にセンシティブな情報を持つこともありませんでした。

現在では、子供が遊び半分で貴方のデータベースを攻撃するかもしれません。(“sql injection toolkit”と検索して、出てくるものにびっくりすることでしょう。)ウェブサイトを攻撃することだって難しくありません。

幸い、あなた自身を守ることも、攻撃の危険を最小化することも難しくはないのです。

最小権限を採用しましょう。 開発チームの仕事は増えそうですが、そこから得られるものを見て下さい。つまり、危険の最小化、より良いドキュメント、どのオブジェクトを誰が何のために使うのかの確実な理解、といったことです。それはセキュリティの機能だけではありません。あなたのコード・ベース全体をより良く、よりメンテナンスが容易で、よりわかりやすいものにすることもできるのです。

さまざまなコンポーネント間の”壁”を設けるために、複数のスキーマを通じてカプセル化とモジュール化を使いましょう。もう一度言います。いろいろな要素を一元化するために、最小権限を使いましょう。

バインド変数を使用することによって、SQL インジェクションのような主要な攻撃要素全体を回避しましょう。バインド変数は、パフォーマンスと拡張性のために良いだけでなく、セキュリティ面でも大変有効なのです。

思いつく限りたくさんの防御層を採用しましょう。それは冗長なのではありません。冗長だと考えないようにしましょう。それらは、明確な形であなたのセキュリティのフットプリントに加わります。

そして、たぶん最も重要なことになりますが、アプリケーションを開発する1日目から、これらのすべてを設計に盛り込みましょう。既存のアプリケーションに最小権限と複数のスキーマを組み込んだり、バインド変数を使っていないコードを使うように修正しようとすることは、困難なばかりではなく問題が発生しやすくなります。

おわり

次のステップ


データ保護を中心とした、最新のデータベース・セキュリティ対策について、資料をご案内しています。あわせてご参考下さい。

 データベースのセキュリティを再点検する
 そして、再び・・・・・オラクルのセキュリティ対策ソリューション まとめ


Tom Kyteの顔写真


Tom Kyteは、オラクルのServer Technologies部門に籍を置くデータベース・エバンジェリストで、1993年からオラクルに勤務しています。Expert Oracle Database Architecture(Apress、2005年/2010年)、Effective Oracle by Design(Oracle Press、2003)などの著書があります。

 

▲ ページTOPに戻る

記事一覧へ戻る