Mastering .NET Application with Oracle

Oracleデータベースでのセキュア な.NETアプリケーション開発
John Paul Cook

.NETアプリケーションで Oracleのセキュリティ機能を活用する方法を学習します。

この記事に 必要なダウンロード:
サ ンプル・コード
Oracle Database 10g for Windows
Oracle Data Provider for .NET

『Oracleを使用した.NETアプリケーション開発の習得』の全項目

セキュリティへの関心がますます高まる中、企業は不正 なアクセスを拒否する程度の単純なセキュリティ以上のより高度なセキュリティを求めています。 国際法、国内法、県条例、市町村条例などの法令により、セキュリティに対する一層詳細で粒度の高いアプローチが要求されています。 もはや無認可アクセスからデータベースを保護するだけでは十分ではありません。 どのユーザーがどのような操作をデータベースで実行したかを示す監査証跡の取得も必要です。

データベース・プラットフォームとして Oracleを選択した.NET開発者は、Oracle Data Provider for .NET(ODP.NET)を使用して、Oracleのセキュリティ機能を活用し、包括的セキュリティ戦略の基礎を築くことができます。

『Mastering .NET Application Development with Oracle』のこのセクションでは、セキュリティを考慮したアプリケーションの設計方法を説明します。

  • 接続プーリングの利点のすべてを保持しな がら、プロキシ認証を使用して実際のエンド・ユーザーIDをOracleサーバーに渡す方法。 プロキシ認証では、接続プーリングの使用時にデータベース・サーバーに対するユーザーごとの認証も可能です。
  • クライアント識別子を使用して、カスタム 識別子文字列または実際のエンド・ユーザーIDをOracleサーバーに渡す方法。
  • OracleユーザーIDおよびパスワー ドを使用せずに、Windows認証をOracleで使用してデータベース・サーバーに対して認証する方法(シングル・サインオンとも呼ばれる)。
  • データベース・セキュリティに対する最大 の脅威の1つである、パラメータ化された問合せを介したSQLインジェクションを防止する方法。

比較的単純な方法から複雑な方法までの5つの実 践的演習で学習した内容は、後で実際に適用することができます。

Oracle Databaseの.NETアプリケーションの構築』というタイトルの前の記事と同様、この記事もユーザーがOracle Data Provider for .NETの使用とVisual Studio.NETでのプロジェクトの作成に精通していることを前提としています。

プロキシ認証

次のコードは、簡単なOracle接続文字列を 定義するために前の記事で使用したものです。

Dim oradb As String = 
"Data Source=OraDb;User Id=scott;Password=tiger;" ' VB.NET

string oradb =
"Data Source=OraDb;User Id=scott;Password=tiger;"; // C#

このような接続文字列は、ユーザーIDとパス ワードをユーザーに要求するアプリケーションで通常使用されます。 Webアプリケーションもその1つです。 接続プーリングにより、すべてのアプリケーション・ユーザーが接続プーリングの要件に対し同じOracle資格証明を使用して接続するため、パフォーマン スが改善されます。 すべてのデータベース・アクションは、実際のユーザーではなく接続文字列で定義されたユーザーごとに記録されるため、監査の観点からは、共通するユーザー IDを使用した接続は望ましくありません。 接続プーリングのパフォーマンス上のメリットを活用することは、匿名性を許可することになります。 データベース内で実際のユーザーのIDを識別することはできません。

Proxy認証は、Proxy User IdProxy Passwordを接続文字列に追加します。 すなわち、2つのユーザーIDが渡されます。1つは実際のユーザーID、もう1つはプールされたユーザー(プロキシ・ユーザー)IDです。 この機能により、1つの中間層接続プールを保持しながら、ユーザーのUser Idを通じてユーザーのアクティビティを監査できます。 プロキシ認証が使用されているかどうかにより、User IdPasswordの使用方法が多少異なります。 プロキシ認証が使用されると、プールされるユーザー資格証明は、User IdPasswordではなく、Proxy User IdProxy Passwordを 使用して渡されます。 次に例を示します。

Dim oradb As String = "Data Source=OraDb;User Id=ActualUser;Password=secret; 
Proxy User Id=scott;Proxy Password=tiger;" ' VB.NET

string oradb = "Data Source=OraDb;User Id=ActualUser;Password=secret;
Proxy User Id=scott;Proxy Password=tiger; "; // C#

実際のアプリケーションでは、実際のユーザー IDとパスワードをハードコードすることはありません。 そのかわりに、ユーザーIDとパスワードを入力するためのテキスト・ボックスをユーザーに提供し、それを接続文字列に渡します。 WindowsフォームまたはWebページ(Visual StudioではWebフォームと呼ばれる)でユーザーIDとパスワードを入力するダイアログ・ボックスがユーザーに提供されると、Microsoftは これをフォーム認証として参照します。

プロキシ認証は、軽量セッションと呼ばれる第2 のセッションをデータベースで作成します。これはデータベースに対して実際のユーザーを知らせるために使用されます。 これが機能するためには、軽量接続を作成する権限がデータベース管理者によりプロキシ・ユーザーに明示的に付与されている必要があります。 ここでは、プロキシ・ユーザー(プールされるユーザー)としてSCOTTを使用します。

alter user ActualUser grant connect through scott;

実際のユーザーのパスワードを接続文字列に含め るかどうかを選択できます。 実際のユーザーがパスワードの提供なしで使用された場合、接続は成功します。 実際のユーザーが認証されるようにするには、Password接続文字列属性を使用する必要があります。 ユーザーに対する無効なパスワードが提供されると、認証は失敗します。

プロキシ接続は、ユーザーに代わって呼び出され るデータベース・アクティビティのために使用されます。 プロキシ接続がプールに戻されると、軽量セッションは終了します。

クライアント識別子の使用は、Password 接続文字列属性なしでプロキシ認証を使用する方法と類似しています。 大きな違いは、クライアント識別子の使用によりデータベースで第2のセッションが作成されない点です。 もう1つの違いは、どのような文字列でもクライアント識別子として使用できることです。 データベース・ユーザーの名前に対応している必要はありません。 クライアント識別子を設定するには、接続のオープン後に接続オブジェクトのClientIdプロパティに文字列値を割り当てます。

conn.ClientId = "SomeUser"  ' VB.NET

conn.ClientId = "SomeUser"; // C#

この割当てでは、ユーザーのセッションのためにCLIENT_IDENTIFIERという値を設定します。 アプリケーションがフォーム認証を使用している場合、ユーザーにより入力されたユーザーIDを使用してClientIdの値を設定できます。 ClientIdUSERENVの一部であるた め、ユーザーがALL_USERSに定義された実際のユーザーでない場 合でも、それを使用してOracle Virtual Private Databaseでアクセスを制限できます。ClientIdは、スケーラビリティの拡張にも使用できます。 たとえば、Webアプリケーションは、ユーザーがClientIdを終了後に、それを次のユーザーに再設定する場合、データベース接続をオープンしたままで再設定できます。

Windowsにログオンする場合、 Windowsユーザーはオペレーティング・システムに対して既知であるため、Windows.Security.Principalクラスを使用することにより、.NETアプリケーション内でWindowsユーザーを取得できます。 この実行のためのコード、およびClientIdにWindowsユー ザをに設定するコードは次のとおりです。

Dim user As New WindowsPrincipal(WindowsIdentity.GetCurrent()) 
conn.ClientId = user.Identity.Name ' VB.NET

WindowsPrincipal user = new WindowsPrincipal(WindowsIdentity.GetCurrent());
conn.ClientId = user.Identity.Name; // C#

この方法により、Windowsユーザーを Oracleデータベースに知らせることができます。 プロキシ認証がPassword接続文字列属性なしに使用された場合、User Id接続文字列属性を通じてもWindowsユーザーIDをデータベースに渡すことができます。 この場合、前述のとおり、User Id接続文字列属性は実際のユー ザーの認証ではなく、識別のためにのみ使用されます。

Windows認証

この記事では、OracleユーザーIDを使用 してデータベースに対してユーザーを認証する方法を説明しました。 さらに、Windowsオペレーティング・システムを使用し、シングル・サインオン機能により、Oracleに対してユーザーを認証することもできます。 これについては、『Windows IT Pro』誌の「Implementing Windows Authentication from Oracle (英語)」で詳しく説明しています。 Windows認証の使用には、接続文字列の修正が必要です。

"Data Source=ORCL10g;User Id=/;"

スラッシュ(/)は、Windows認証の使用 をOracleに通知します。 Password接続文字列属性は、 データベース認証を利用する場合以外には使用しないため、削除します。 Windows認証の使用時にPasswordが接続文字列に残っている場合、それは無視されます。

Windows認証では、Windowsユー ザーがOracleサーバー上でORA_DBAのような権限を付与され たWindowsグループに属しているか、または外部認証が有効化されている必要があります。 外部認証は、グループ・メンバーシップを利用したアクセスより安全度が低いため推奨できません。

SQLインジェクション攻撃について

ここまでで、誰がデータベースにアクセスしてい るかを追跡する方法を説明しましたが、 それ以上に、データベースの内容に危害が加えられないようにすることが重要です。 データベースに対する最大脅威の1つは、SQLインジェクションです。これは、悪意のあるユーザーがセキュアでないコードを通じてアプリケーションに SQLコマンドを差し込んだ場合に発生します。 SQLインジェクションに対する脆弱性は、特定のベンダー製品の使用が原因ではなく、ユーザー入力によりSQL文が構成された結果として発生します。 プログラミング保護対策が遵守されていない場合、どのベンダーのSQLデータベースでもすべて脆弱になります。

特定の職務カテゴリの従業員数を数える簡単なア プリケーションを考えてみます。

図1:

データベース・コマンド文字列を構成するために 使用されるコードは次のようになります。

cmd.CommandText = "select count(ename) from emp where " _
+ "job = '" + TextBox1.Text + "'" ' VB.NET

cmd.CommandText = "select count(ename) from emp where "
+ "job = '" + TextBox1.Text + "'"; // C#

ユーザーが上でCLERKと入力した場合、デー タベースが受け取るコマンド・テキストは次のようになります。

select count(ename) from emp where job = 'CLERK'

ユーザーがロジックを変更しないかぎり、すべて 問題なく機能します。 実行時にユーザー入力に基づくSQL文字列の構築を許可すると、SQLロジックを変更するをユーザーに与えることになります。 ユーザーがCLERKと入力せずに、次のように入力したとします。

' or 1=1 --

これにより、問合せのロジックが次のとおり根本 的に変更されます。

select count(ename) from emp where job = '' or 1=1 --'

アプリケーション・コードは末尾に常に一重引用 符を追加します。 このインライン・コメントは、SQLパーサーに末尾の一重引用符を無視させます。 インライン・コメントを使用しないと、変更されたSQLは次のような無効な構文になります。

select count(ename) from emp where job = '' or 1=1 '

ユーザーが変更したSQL構文により、表のすべ ての行がカウントされます。 where job = ''のみの場 合はカウントがゼロになりますが、or 1=1の場合はすべての行がカ ウントされます。

前述の例のSQLインジェクションの影響は、 ユーザーがアプリケーション設計者の意図とは異なる回答を得たことのみです。 ここで、ユーザー認証に使用する次のアプリケーション・ウィンドウを検討します。

図2:

これは、ログイン資格証明を処理するためのコー ドです。

cmd.CommandText = "select user_role from app_login where " _
+ "user_id = '" + TextBox1.Text + "' and " _
+ "password = '" + TextBox2.Text + "'" ' VB.NET

cmd.CommandText = "select user_role from app_login where "
+ "user_id = '" + TextBox1.Text + "' and "
+ "password = '" + TextBox2.Text + "'"; // C#

悪意のあるユーザーが次のように入力したと仮定 します。

admin' or 1=1 --

結果のSQLは次のようになります。

select user_role from app_login
where user_id = 'admin' or 1=1 --' and password=''

この場合も、意図した問合せロジックの無効化に インライン・コメントが重要な役割を果たしています。 インライン・コメント--により、問合せ文字列の残りの部分はコメントとして処理され、その結果、パスワードが不要になります。 その結果の問合せ文字列には、常に真であるwhere句があるため、 ユーザーはパスワードを提供せずに管理者としてログインできます。基本になる問い合わせ文字列の構成によっては、ユーザーIDもパスワードも提供せずにア プリケーションにログインできる場合さえあります。

SQLインジェクション攻撃の防止

ユーザー入力をいかに厳密に検証しようとして も、悪意のあるユーザーは様々な方法で攻撃します。 入力の検証によってすべてのSQLインジェクションの試行を識別することはできません。 問題の根源は、ユーザーが管理者の意図に反したSQL構文を入力することではなく、ユーザーの入力が単なる文字列としてではなくSQL構文として扱われる ことにあります。

入力を文字列パラメータとして扱うと、ユーザー 入力はSQL文の一部として処理されず、SQL問合せに対して値として渡される単なる文字列となります。 OracleParameterオブジェクトを使用すると、これは次のように解釈されるため、不正な入力が無効になります。

select user_role from app_login
where user_id = 'admin' or 1=1 --' and password = ''

2個のハイフンは、インライン・コメントとして は処理されず、単なるテキスト文字列として扱われます。 ユーザー入力は、実行されるSQL問合せの構文の一部にはなりません。

パラメータ化された問合せを作成するには、問合 せ文字列を次のように変更します。

cmd.CommandText = "select user_role from app_login where " _
+ "user_id = :user_id and password = :password" ' VB.NET

cmd.CommandText = "select user_role from app_login where "
+ "user_id = :user_id and password = :password"; // C#

次にOracleParameterオブジェクトをインスタンス化し、それをOracleParametersコレクションに追加します。

Dim p1 As New OracleParameter("dname", OracleDbType.Varchar2) ' VB.NET
p1.Value = TextBox1.Text
cmd.Parameters.Add(p1)

Dim p2 As New OracleParameter("loc", OracleDbType.Varchar2)
p2.Direction = ParameterDirection.Input ' optional property
p2.Size = 13 ' optional property
p2.Value = TextBox2.Text
cmd.Parameters.Add(p2)

OracleParameter p1 = new OracleParameter("dname", OracleDbType.Varchar2);
p1.Value = textBox1.Text; // C#
cmd.Parameters.Add(p1);

OracleParameter p2 = new OracleParameter("loc", OracleDbType.Varchar2);
p2.Direction = ParameterDirection.Input; ' optional
p2.Size = 13; ' optional
p2.Value = textBox2.Text;
cmd.Parameters.Add(p2);

Parameters.Addで は、OracleParameterのためのいくつかのコンストラクタと複数のオー バーロードがあります。 さらに、実際の問合せの内容に応じて、設定が可能または必要な各種プロパティがあります。 ストアド・プロシージャにパラメータを渡すためには、前述と同様のコーディング・パターンを使用します。

演習1: データベース・ユーザーの表示

1. Oracle.DataAccessに対する参照を追加することから始めます。詳細は、「Oracle Databaseの.NETアプリケーションの構築」を参照してください。

2. Windowsフォームにボタン・コントロールとラベル・コントロールを追加します。

3. Oracleデータベースからデータを取得して結果をフォームに表示するためのコードを追加します。 ボタン用のクリック・イベント・ハンドラをコードに置きます。 ボタンをダブルクリックするとイベント・ハンドラのスタブが作成され、簡単にこのタスクを開始できるようになります。

4. VB.NETのPublic Class宣言前にImports 文を追加するか、名前領域宣言の前にC#のusing文を追加します。 (注意: 「ダウンロード可能なコード・サンプル」を使用して、実際にはコードを一切入力せずに、この実習をすべて実行できます。)

Imports System.Data              ' VB.NET
Imports Oracle.DataAccess.Client ' ODP.NET Oracle managed provider

using System.Data; // C#
using Oracle.DataAccess.Client; // ODP.NET Oracle managed provider

5. この例では、ORCL10gのtnsnames.oraエイリアスを持っているものと仮定しています。. tnsnames.oraを使用していない場合、接続文字列を変更する必要があります。 このシリーズの前の記事に、tnsnames.oraに依存 しない接続文字列が示されています。

VB.NETでは、ボタンのクリック・イベント・ハンドラのPrivate Sub文とEnd Sub文の間に次のVB.NETコードを追加します。

Dim oradb As String = "Data Source=ORCL10g;User Id=scott;Password=tiger;"

Dim conn As New OracleConnection(oradb) ' VB.NET
conn.Open()

Dim cmd As New OracleCommand
cmd.Connection = conn
cmd.CommandText = "select user from user_users"
cmd.CommandType = CommandType.Text

Dim dr As OracleDataReader = cmd.ExecuteReader()
dr.Read()
label1.Text = dr.Item("user") ' or dr.Item(0)

conn.Dispose()

C#では、ボタンのクリック・イベ ント・ハン ドラの中カッコ { と } の間に、クリック・イベント・ハンドラに対する次のC#コードを追加します。

string oradb = "Data Source=ORCL10g;User Id=scott;Password=tiger;";

OracleConnection conn = new OracleConnection(oradb); // C#
conn.Open();

OracleCommand cmd = new OracleCommand();
cmd.Connection = conn;
cmd.CommandText = "select user from user_users";
cmd.CommandType = CommandType.Text;

OracleDataReader dr = cmd.ExecuteReader();
dr.Read();
label1.Text = dr.GetString(0);

conn.Dispose();

6. アプリケーションを実行し、ボタンをクリックします。 次の画面が表示されます。

図3:
演習2: プロキシ認証の追加

これでOracleデータベース・ サーバーが認識しているユーザーを確認できるようになりました。次の手順では、接続文字列にプロキシ認証を追加します。

1. データベース・サーバーにSCOTTとしてログインした場合に実際のユーザーとして扱われるよう、新しいユーザーを作成します。

create user ActualUser identified by secret;
grant connect to ActualUser;
alter user ActualUser grant connect through scott;

2. ActualUserを含め接続文字列を変更し、プロキシ認証を使用します。

Dim oradb As String = "Data Source=ORCL10g;" _
+ "User Id=ActualUser;Password=secret;" _
+ "Proxy User Id=scott;Proxy Password=tiger;" ' VB

string oradb = "Data Source=ORCL10g;"
+ "User Id=ActualUser;Password=secret;"
+ "Proxy User Id=scott;Proxy Password=tiger;"; // C#

3. アプリケーションを実行し、ボタンをクリックします。 次のような画面が表示されます。

図4:
4. 接続文字列からPassword=secretを削除し、アプリケーションを再度実行します。 ボタンをクリックします。 パスワードを提供しなかったにもかかわらず、前の手順と同じ結果になります。 これは、実際のユーザーを認証しているのではなく、単に実際のユーザーの名前をデータベースに渡しているためです。

5. 接続文字列にPassword=wrongsecretを追加し、アプリケーションを再度実行します。 アプリケーションは実行時エラーで失敗します。実際のユーザーに対する認証が失敗するためです。

演習3: クライアント識別子の使用

1. 演習2で使用した接続文字列を次のように変更します。

Dim oradb As String = "Data Source=ORCL10g;" _
+ "User Id=scott;Password=tiger;" ' VB

string oradb = "Data Source=ORCL10g;"
+ "User Id=scott;Password=tiger;"; // C#

2. 接続のOpenメソッドを探し、その下の行にClientIdプロパティを設定します。

conn.Open()  ' VB
conn.ClientId = "SomeUser" ' add this

conn.Open(); // C#
conn.ClientId = "SomeUser"; ' add this

3. CommandTextプロパティを次のように変更します。

cmd.CommandText = 
"SELECT SYS_CONTEXT('USERENV','CLIENT_IDENTIFIER') FROM DUAL" ' VB

cmd.CommandText =
"SELECT SYS_CONTEXT('USERENV','CLIENT_IDENTIFIER') FROM DUAL"; // C#

4. DataReaderのItemプロパティを次のように変更します。

Label1.Text = dr.Item(0)  ' VB

label1.Text = dr.GetString(0); // C#

5. アプリケーションを実行し、ボタンをクリックします。 次のような画面が表示されます。

図5:

演習4: Windows.Identity.Principalの使用

1. Imports文またはusing文を追加し、 演習3のコードを変更します。

Imports System.Security.Principal  ' VB

using System.Security.Principal; // C#

2. WindowsPrincipalオブジェクトをインスタンス化し、それを使用してClientIdプロパティを設定します。

Dim user As New WindowsPrincipal(WindowsIdentity.GetCurrent())
conn.ClientId = user.Identity.Name ' VB

WindowsPrincipal user = new WindowsPrincipal(WindowsIdentity.GetCurrent());
conn.ClientId = user.Identity.Name; // C#

3. アプリケーションを実行し、ボタンをクリックします。 次のような画面が表示されます。

図6:

テスト・マシンでWindows ユーザーWinuserとしてORAWINドメインにログオンしたことがわかります。 ドメイン名またはマシン名、およびWindowsユーザー名が表示されています。

演習5: パラメータ化された問合せ

1. 2つのラベルと2つのテキスト・ボックスを追加して前の演習で使用したフォームを変更します。 結果は次のようになります。

図7:
2. 問合せ文字列を作成するコードを変更し、ユーザー入力を受け入れます。

cmd.CommandText = "select count(deptno) from dept where " _
+ "dname = '" + TextBox1.Text + "' and " _
+ "loc = '" + TextBox2.Text + "'" ' VB

cmd.CommandText = "select count(deptno) from dept where " _
+ "dname = '" + textBox1.Text + "' and " _
+ "loc = '" + textBox2.Text + "'"; // C#

新しい表を作成および移入するかわ りに、DEPT表を使用し、DNAME列にユーザーIDが、LOC列に パスワードが入っているものとして使用します。

3. アプリケーションを実行し、次のように有効な値を入力します。

ユーザーIDとしてSALESを入 力します。
パスワードとしてCHICAGOを入力し ます。

フォームに1というカウントが表示 されます。 これは有効なログインを再現しています。

3. アプリケーションを実行し、次のように無効な値を入力します。

ユーザーIDとしてSALESを入 力します。
パスワードとしてINVALIDを入力し ます。

フォームに0というカウントが表示 されます。 これは失敗したログインを再現しています。

4. アプリケーションを実行します。 最初のテキスト・ボックスに、次のように無効な値を入力します。

SALES' and 1=1 --

この入力は、パスワードなしで有効 なユーザーIDを入力したことに相当します。 この入力は無効なため、カウント0が表示されるはずですが、そうはなりません。 かわりに、カウント1が表示されます。これは、パスワードの入力なしにアプリケーションに対してユーザーを認証できることを示しています。

5. 問合せ文字列を変更して、パラメータを使用します。

cmd.CommandText = "select count(deptno) from dept where " _
+ "dname = :dname and loc = :loc" ' VB

cmd.CommandText = "select count(deptno) from dept where " _
+ "dname = :dname and loc = :loc"; // C#

6. 問合せのパラメータに入力値をバインドするコードを追加します。

Dim p1 As New OracleParameter("dname", OracleDbType.Varchar2)
p1.Value = TextBox1.Text
cmd.Parameters.Add(p1)

Dim p2 As New OracleParameter("loc", OracleDbType.Varchar2)
p2.Direction = ParameterDirection.Input ' optional
p2.Size = 13 ' optional
p2.Value = TextBox2.Text
cmd.Parameters.Add(p2) ' VB.NET


OracleParameter p1 = new OracleParameter("dname", OracleDbType.Varchar2);
p1.Value = textBox1.Text;
cmd.Parameters.Add(p1);

OracleParameter p2 = new OracleParameter("loc", OracleDbType.Varchar2);
p2.Direction = ParameterDirection.Input; ' optional
p2.Size = 13; ' optional
p2.Value = textBox2.Text;
cmd.Parameters.Add(p2); // C#

7. C#を使用している場合は次のように変更します。

label1.Text = dr.GetOracleDecimal(0).ToString(); // C#

8. アプリケーションを実行し、最初のテキスト・ボックスに次のような無効な値を入力します。

SALES' and 1=1 --

今回はカウント0が表示されます。 これは、入力テキストがSQL問合せ文字列の一部としてではなく、varchar2変数で単なる一連の文字として処理されたことを示しています。


John Paul Cookjohnpaulcook@email.com)は、ヒューストン在住のデータベースおよ び.NETのコンサルタントです。 .NET、Oracleなどに関する多数の著述があり、1986年からリレーショナル・データベース・アプリケーションを開発してきました。現在では、セ キュリティ、IT管理、Visual Studio 2005、Oracle 10g などに関心を持っています。 Oracle認定DBAおよびMicrosoft MCSD for .NETの資格を持っています。