この章の内容は、次のとおりです。
安全なデータベース・アプリケーションを作成する最初のステップは、アプリケーション・セキュリティ・ポリシーの作成です。アプリケーション・セキュリティ・ポリシーは、アプリケーション・セキュリティの要件およびデータベース・オブジェクトへのユーザー・アクセスを規制するルールのリストです。
セキュリティ・ポリシーは、データベース・アプリケーションごとに立案する必要があります。たとえば、各データベース・アプリケーションには、アプリケーションの実行時に異なるレベルのセキュリティを提供する1つ以上のデータベース・ロールが必要です。データベース・ロールは、他のロールに付与するか、または特定のユーザーに直接付与できます。
(SQL*PlusやSQL Developerなどのツールを使用して)SQL文を制限なしで処理できるアプリケーションの場合でも、機密扱いのスキーマ・オブジェクトや重要なスキーマ・オブジェクトへの不当なアクセスを防ぐために、セキュリティ・ポリシーが必要です。特に、使用するアプリケーションでパスワードが安全に処理されることを確認する必要があります。
次の各項では、安全なデータベース・アプリケーションの計画と開発に使用できる、アプリケーション・セキュリティとOracle Databaseの機能について説明します。
アプリケーション・セキュリティを構築および実装する際の2つの主な課題を、次の各項で説明します。
できるかぎり、アプリケーション・ユーザーがデータベース・ユーザーであるアプリケーションを作成する必要があります。これによって、データベースに本来備わっているセキュリティ・メカニズムを活用できます。
多くの市販のパッケージ・アプリケーションでは、アプリケーション・ユーザーは、データベース・ユーザーではありません。これらのアプリケーションでは、複数のユーザーがユーザー自身をアプリケーションに対して認証し、次に、アプリケーションが単一の高い権限を持つユーザーとしてデータベースに接続します。これを、One Big Application Userモデルと呼びます。
この方法で作成されたアプリケーションは、通常、データベースに本来備わっているセキュリティ機能の多くを使用できません。これは、ユーザーの識別情報がデータベースで認識されないためです。
表5-1では、One Big Application Userモデルが様々なOracle Databaseセキュリティ機能にどのように影響するかを説明します。
表5-1 One Big Application Userモデルの影響を受ける機能
ユーザーがデータベース・ユーザーでもあるアプリケーションは、アプリケーションの中にセキュリティを組み込むか、きめ細かい権限、仮想プライベート・データベース(アプリケーション・コンテキストによるファイングレイン・アクセス・コントロール)、ロール、ストアド・プロシージャ、および監査(ファイングレイン監査を含む)などのデータベースに本来備わっているセキュリティ・メカニズムを使用できます。アプリケーションでできるだけ多くのデータベース・セキュリティ・メカニズムが使用されるようにすることをお薦めします。
セキュリティがアプリケーション内ではなくデータベース自体で規定されている場合は、セキュリティを回避することはできません。アプリケーション・ベースのセキュリティの主なデメリットは、ユーザーがアプリケーションを回避してデータにアクセスすると、セキュリティが回避されることです。たとえば、データベースへのSQL*Plusアクセス権を持つユーザーは、人事管理アプリケーションを介さずに問合せを実行できます。したがって、このユーザーは、アプリケーション内のセキュリティ対策のすべてをバイパスします。
One Big Application Userモデルを使用するアプリケーションでは、データベースのセキュリティ・メカニズムを使用せずに、アプリケーション内にセキュリティ規定を作成する必要があります。ユーザーを認識するのは、データベースではなくアプリケーションであるため、アプリケーション自体が各ユーザーに対してセキュリティ対策を規定する必要があります。
このアプローチでは、データにアクセスする各アプリケーションでのセキュリティの再実装が必要になります。組織では複数のアプリケーションに同じセキュリティ・ポリシーを実装する必要があるため、セキュリティが高コストになり、新規アプリケーションごとに高コストな再実装が必要になります。
この項では、バッチ・ジョブ、スクリプト、インストール・ファイルまたはアプリケーションからパスワードで保護されたサービスを安全に起動する戦略について説明します。パスワード保護に加えて、これらの戦略の多くはその他の機密データ(暗号化鍵など)に適用できます。
この項の内容は、次のとおりです。
これらのガイドラインは次のカテゴリに分類されます。
次の潜在的なセキュリティへの脅威は、わかりにくい可能性があるので注意してください。
UNIXおよびLinuxプラットフォームでは、同じホスト・コンピュータ上のすべてのオペレーティング・システム・ユーザーがコマンド・パラメータを表示できます。結果として、コマンドラインに入力したパスワードは他のユーザーに公開される可能性があります。ただし、UNIXおよびLinux以外のプラットフォームがこの脅威を回避できるとは考えないでください。
HP Tru64およびIBM AIXなど一部のUNIXプラットフォームでは、すべてのオペレーティング・システム・ユーザーがあらゆるプロセスの環境変数を表示できます。ただし、UNIXおよびLinux以外のプラットフォームがこの脅威を回避できるとは考えないでください。
Microsoft Windowsでは、コマンド・リコール機能(上矢印)に、コマンド起動間のユーザー入力が記憶されます。たとえば、SQL*PlusのCONNECT SYSTEM/password表記法を使用し、終了してから上矢印を押してCONNECTコマンドを繰り返した場合、コマンド・リコール機能により接続文字列が明らかになり、パスワードが表示されます。また、Microsoft Windows以外のプラットフォームがこの脅威を回避できるとは考えないでください。
パスワードの入力を対話形式で求めるアプリケーションを設計します。コマンドライン・ユーティリティの場合、コマンド・プロンプトでパスワードを公開することをユーザーに強制しないでください。
アプリケーションの設計に使用するプログラミング言語のAPIについて、ユーザーからのパスワードを処理する最適な方法を確認します。この機能を処理するJavaコードの例については、「Javaでのパスワード読取り例」を参照してください。
SQLインジェクション攻撃からデータベースを保護します。SQLインジェクション攻撃では、PL/SQLアプリケーションで想定されない方法でSQL文が追加または変更されます。たとえば、侵入者はWHERE句をTRUEに設定してパスワード認証を回避できます。
SQLインジェクション攻撃の問題に対処するには、バインド変数引数を使用するか妥当性チェックを作成します。バインド変数を使用できない場合は、DBMS_ASSERT PL/SQLパッケージを使用して入力値のプロパティを検証することを検討してください。DBMS_ASSERTパッケージの詳細は、『Oracle Database PL/SQLパッケージおよびタイプ・リファレンス』を参照してください。また、PUBLICなどのロールに対する権限付与も検討してください。
SQLインジェクションの防止の詳細は、『Oracle Database PL/SQL言語リファレンス』を参照してください。
可能な場合は、認証を遅延するアプリケーションを設計します。次に例を示します。
ログインに証明書を使用します。
オペレーティング・システムで提供される機能を使用してユーザーを認証します。たとえば、Microsoft Windows上で動作するアプリケーションはドメイン認証を使用できます。
パスワードをマスクまたは暗号化します。パスワードを格納する必要がある場合、パスワードをマスクまたは暗号化します。たとえば、ログ・ファイルでパスワードをマスクし、リカバリ・ファイルでパスワードを暗号化できます。
各接続を認証します。たとえば、スキーマAがデータベース1に存在する場合、データベース2のスキーマAが同一のユーザーであるとは考えないでください。同様に、ローカル・オペレーティング・システム・ユーザーpsmithは、必ずしもリモート・ユーザーpsmithと同じユーザーではありません。
ファイルまたはリポジトリにクリアテキスト・パスワードを格納しないでください。パスワードをファイルに格納すると、侵入者がパスワードにアクセスする危険性が高まります。
1つのマスター・パスワードを使用します。次に例を示します。
1人のデータベース・ユーザーに、他のデータベース・ユーザーとなるためのプロキシ認証を付与できます。この場合、必要となるデータベース・パスワードは1つのみです。詳細は、「プロキシ・ユーザー・アカウントの作成と、作成したプロキシ・ユーザー・アカウントを介したユーザー接続の認可」を参照してください。
マスター・パスワードでオープンできるパスワード・ウォレットを作成できます。ウォレットにはその他のパスワードが含められます。Wallet Managerの詳細は、『Oracle Database Advanced Security管理者ガイド』を参照してください。
次のガイドラインに従ってください。
パスワードの存続期間を制限します。パスワード存続期間を設定して、その期間が過ぎるとパスワードが期限切れになり、変更しないとユーザーがアカウントにログインできなくなるようにすることができます。パスワードの存続期間を制御するために使用できるパラメータは、「パスワード・エイジングおよび期限切れの制御」を参照してください。
ユーザーが古いパスワードを再利用する機能を制限します。詳細は、「ユーザーによる以前のパスワードの再利用の制御」を参照してください。
強力かつ安全なパスワードを作成するようユーザーに要求します。強力なパスワードの作成に関するガイドラインは、「パスワードの保護に関するガイドライン」を参照してください。「パスワードの複雑度検証の規定」では、パスワードの要件をカスタマイズする方法について説明しています。
パスワードで大/小文字を区別できるようにします。詳細は、「パスワードでの大/小文字の区別の有効化または無効化」を参照してください。
次のガイドラインに従ってください。
プログラムまたはスクリプトのコマンドラインにパスワードを指定してSQL*Plusを起動しないでください。パスワードが必須であるにもかかわらず省略した場合、SQL*Plusではパスワードの入力を求めるプロンプトが表示され、パスワードが表示されないようにエコー機能が自動的に無効となります。
次の各例は、パスワードがコマンドライン上で公開されないため安全です。また、Oracle Databaseではこれらのパスワードがネットワークを介して自動的に暗号化されます。
$ sqlplus system Enter password: password SQL> connect system Enter password: password
次の例では、パスワードが他のオペレーティング・システム・ユーザーに公開されます。
sqlplus system/password
次の例は、2つのセキュリティ上のリスクをもたらします。1つ目の例では、覗き込んでいる可能性のある他のユーザーにパスワードを公開してしまいます。2つ目の例では、Microsoft Windowsなどの一部のプラットフォームにおいて、パスワードがコマンドラインのリコール攻撃を受けやすくなります。
$ sqlplus /nolog
SQL> connect system/password
たとえばパスワードまたは秘密鍵を必要とするSQLスクリプトの場合、アカウントを作成したりあるアカウントでログインするには、置換変数&1、&2などの位置パラメータを使用しないでください。かわりに、ユーザーに値の入力を求めるプロンプトを表示するスクリプトを設計します。また、スクリプトから、またはスプール・モードを使用している場合に出力を表示するエコー機能を無効にする必要があります。エコー機能を無効にするには、次の設定を使用します。
SET ECHO OFF
スクリプトにより値の目的を明白にする必要があります。たとえば、値によりアカウントまたは証明書などの新しい値が設定されるかどうか、または既存のアカウントへのログインなど、値が認証されるかどうかを明白にする必要があります。
次の例は、セキュリティ上のリスクをもたらす方法でユーザーがスクリプトを起動することが回避されるため安全です。パスワードはエコーされず、スプール・ファイルに記録されません。
SET VERIFY OFF ACCEPT user CHAR PROMPT ’Enter user to connect to: ’ ACCEPT password CHAR PROMPT ’Enter the password for that user: ' HIDE CONNECT &user/&password
この例の説明は、次のとおりです。
SET VERIFY OFF:は、パスワードの表示を防止します。(SET VERIFYは、置換の前後にスクリプトの各行をリストします。)SET VERIFY OFFコマンドをHIDEコマンド(3行目)と結合する方法は、パスワードおよびその他の機密入力データを非表示にする便利なテクニックです。
ACCEPT password: ACCEPT passwordプロンプトにHIDEオプションを含めます。これによって入力パスワードのエコーが回避されます。
次の例では位置パラメータを使用しており、ユーザーがコマンドライン上でパスワードを渡すことでスクリプトを起動できるため、セキュリティ上のリスクをもたらします。ユーザーがパスワードを入力せず、入力を求めるプロンプトが表示される場合、ユーザーが入力した内容がすべて画面およびスプール・ファイル(スプールが有効な場合)にエコーされるため危険です。
CONNECT &1/&2
バッチ・スクリプトのログイン時間を制御します。パスワードを必要とするバッチ・スクリプトでは、実行されることになっている時間内のみにバッチ・スクリプトがログインできるようにアカウントを構成します。たとえば、毎日午後8時から1時間実行するバッチ・スクリプトを想定します。この時間のみスクリプトがログインできるようにアカウントを設定します。侵入者がアクセスしようとした場合、安全性の低いアカウントが利用される可能性は低くなります。
パスワードの入力を求めるDML文またはDDL SQL文を使用する場合は注意してください。この場合、機密情報がネットワークを介してクリアテキストで渡されます。Oracle Advanced Securityを使用してこの問題を解決できます。詳細は、『Oracle Database Advanced Security管理者ガイド』を参照してください。
次のパスワード変更例は、パスワードが公開されないため安全です。
password psmith Changing password for psmith New password: password Retype new password: password
この例は、パスワードがコマンドラインおよびネットワーク上の両方に公開されるため、セキュリティ上のリスクをもたらします。
ALTER USER psmith IDENTIFIED BY password
データベースに接続するためのパスワード資格証明は、クライアント側のOracleウォレットを使用して格納できます。Oracleウォレットは、ユーザーのログインに必要な認証および署名用証明書を格納する安全性の高いソフトウェア・コンテナです。
安全性の高い外部パスワード・ストアの詳細は、「パスワード資格証明用の安全性の高い外部パスワード・ストアの管理」を参照してください。また、Oracle Wallet Managerを使用したOracleウォレットの構成方法は、『Oracle Database Advanced Security管理者ガイド』を参照してください。
ネットワークを介してSYSDBAまたはSYSOPER権限を使用してアプリケーションに接続する必要があるユーザーについて、パスワード・ファイルを作成できます。パスワード・ファイルを作成するには、ORAPWDユーティリティを使用します。パスワード・ファイルの作成およびメンテナンスの詳細は、『Oracle Database管理者ガイド』を参照してください。
例5-1では、パスワードの読取りに使用できるJavaパッケージの作成方法を示しています。
例5-1 パスワードを読み取るためのJavaコード
// Change the following line to a name for your version of this package
package passwords.sysman.emSDK.util.signing;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PushbackInputStream;
import java.util.Arrays;
/**
* The static readPassword method in this class issues a password prompt
* on the console output and returns the char array password
* entered by the user on the console input.
*/
public final class ReadPassword {
//----------------------------------
/**
* Test driver for readPassword method.
* @param args the command line args
*/
public static void main(String[] args) {
char[] pass = ReadPassword.readPassword("Enter password: ");
System.out.println("The password just entered is \""
+ new String(pass) + "\"");
System.out.println("The password length is " + pass.length);
}
* Issues a password prompt on the console output and returns
* the char array password entered by the user on the console input.
* The password is not displayed on the console (chars are not echoed).
* As soon as the returned char array is not needed,
* it should be erased for security reasons (Arrays.fill(charArr, ' '));
* A password should never be stored as a java String.
*
* Note that Java 6 has a Console class with a readPassword method,
* but there is no equivalent in Java 5 or Java 1.4.
* The readPassword method here is based on Sun's suggestions at
* http://java.sun.com/developer/technicalArticles/Security/pwordmask.
*
* @param prompt the password prompt to issue
* @return new char array containing the password
* @throws RuntimeException if some error occurs
*/
public static final char[] readPassword(String prompt)
throws RuntimeException {
try {
StreamMasker masker = new StreamMasker(System.out, prompt);
Thread threadMasking = new Thread(masker);
int firstByte = -1;
PushbackInputStream inStream = null;
try {
threadMasking.start();
inStream = new PushbackInputStream(System.in);
firstByte = inStream.read();
} finally {
masker.stopMasking();
}
try {
threadMasking.join();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupt occurred when reading password");
}
if (firstByte == -1) {
throw new RuntimeException("Console input ended unexpectedly");
}
if (System.out.checkError()) {
throw new RuntimeException("Console password prompt output error");
}
inStream.unread(firstByte);
return readLineSecure(inStream);
}
catch (IOException e) {
throw new RuntimeException("I/O error occurred when reading password");
}
}
//----------------------------------
/**
* Reads one line from an input stream into a char array in a secure way
* suitable for reading a password.
* The char array will never contain a '\n' or '\r'.
*
* @param inStream the pushback input stream
* @return line as a char array, not including end-of-line-chars;
* never null, but may be zero length array
* @throws RuntimeException if some error occurs
*/
private static final char[] readLineSecure(PushbackInputStream inStream)
throws RuntimeException {
if (inStream == null) {
throw new RuntimeException("readLineSecure inStream is null");
}
try {
char[] buffer = null;
try {
buffer = new char[128];
int offset = 0;
// EOL is '\n' (unix), '\r\n' (windows), '\r' (mac)
loop:
while (true) {
int c = inStream.read();
switch (c) {
case -1:
case '\n':
break loop;
case '\r':
int c2 = inStream.read();
if ((c2 != '\n') && (c2 != -1))
inStream.unread(c2);
break loop;
default:
buffer = checkBuffer(buffer, offset);
buffer[offset++] = (char) c;
break;
}
}
char[] result = new char[offset];
System.arraycopy(buffer, 0, result, 0, offset);
return result;
}
finally {
if (buffer != null)
Arrays.fill(buffer, ' ');
}
}
catch (IOException e) {
throw new RuntimeException("I/O error occurred when reading password");
}
}
//----------------------------------
/**
* This is a helper method for readLineSecure.
*
* @param buffer the current char buffer
* @param offset the current position in the buffer
* @return the current buffer if it is not yet full;
* otherwise return a larger buffer initialized with a copy
* of the current buffer and then erase the current buffer
* @throws RuntimeException if some error occurs
*/
private static final char[] checkBuffer(char[] buffer, int offset)
throws RuntimeException
{
if (buffer == null)
throw new RuntimeException("checkBuffer buffer is null");
if (offset < 0)
throw new RuntimeException("checkBuffer offset is negative");
if (offset < buffer.length)
return buffer;
else {
try {
char[] bufferNew = new char[offset + 128];
System.arraycopy(buffer, 0, bufferNew, 0, buffer.length);
return bufferNew;
} finally {
Arrays.fill(buffer, ' ');
}
}
}
//----------------------------------
/**
* This private class prints a one line prompt
* and erases reply chars echoed to the console.
*/
private static final class StreamMasker
extends Thread {
private static final String BLANKS = StreamMasker.repeatChars(' ', 10);
private String m_promptOverwrite;
private String m_setCursorToStart;
private PrintStream m_out;
private volatile boolean m_doMasking;
//----------------------------------
/**
* Constructor.
* @throws RuntimeException if some error occurs
*/
public StreamMasker(PrintStream outPrint, String prompt)
throws RuntimeException {
if (outPrint == null)
throw new RuntimeException("StreamMasker outPrint is null");
if (prompt == null)
throw new RuntimeException("StreamMasker prompt is null");
if (prompt.indexOf('\r') != -1)
throw new RuntimeException("StreamMasker prompt contains a CR");
if (prompt.indexOf('\n') != -1)
throw new RuntimeException("StreamMasker prompt contains a NL");
m_out = outPrint;
m_setCursorToStart = StreamMasker.repeatChars('\010',
prompt.length() + BLANKS.length());
m_promptOverwrite = m_setCursorToStart + prompt + BLANKS
+ m_setCursorToStart + prompt;
}
//----------------------------------
/**
* Begin masking until asked to stop.
* @throws RuntimeException if some error occurs
*/
public void run()
throws RuntimeException {
int priorityOriginal = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
try {
m_doMasking = true;
while (m_doMasking) {
m_out.print(m_promptOverwrite);
if (m_out.checkError())
throw new RuntimeException("Console output error writing prompt");
try {
Thread.currentThread().sleep(1);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return;
}
}
m_out.print(m_setCursorToStart);
} finally {
Thread.currentThread().setPriority(priorityOriginal);
}
}
//----------------------------------
/**
* Instructs the thread to stop masking.
*/
public void stopMasking() {
m_doMasking = false;
}
//----------------------------------
/**
* Returns a repeated char string.
*
* @param c the char to repeat
* @param length the number of times to repeat the char
* @throws RuntimeException if some error occurs
*/
private static String repeatChars(char c, int length)
throws RuntimeException {
if (length < 0)
throw new RuntimeException("repeatChars length is negative");
StringBuffer sb = new StringBuffer(length);
for (int i = 0; i < length; i++)
sb.append(c);
return sb.toString();
}
}
}
ほとんどのデータベース・アプリケーションでは、異なるスキーマ・オブジェクトごとに異なる権限が関与します。各アプリケーションに必要な権限の追跡は、複雑な場合があります。また、アプリケーションを実行するユーザーの認可には、多くのGRANT操作が関与する場合があります。
アプリケーションの権限管理を簡素化するために、アプリケーションごとに作成した1つのロールに、1人のユーザーがそのアプリケーションを実行するために必要なすべての権限を付与できます。実際には、1つのアプリケーションに複数のロールがある可能性があり、各ロールには、アプリケーションの実行中に使用できる機能の多少を決める権限の特定サブセットが付与されます。
たとえば、すべての管理アシスタントが休暇アプリケーションを使用して、部門のメンバーが取得した休暇を記録するとします。このアプリケーションを効率的に管理するには、次の操作手順が必要です。
VACATIONロールを作成します。
休暇アプリケーションに必要なすべての権限をVACATIONロールに付与します。
VACATIONロールをすべての管理アシスタントに付与します。より効率的な方法は、管理アシスタントが持つ権限を定義したロールを作成し、VACATIONロールをそのロールに付与します。
複数のアプリケーション権限を1つのロールにグループ化すると、権限の管理に役立ちます。次の管理オプションを考えてみます。
アプリケーションを実行するユーザーに、多数の個別の権限ではなく、ロールを付与できます。したがって、従業員が業務を変更するときは、多くの権限ではなく、1つのロールのみを付与または取り消す必要があります。
アプリケーションに対応付けられている権限の変更は、そのアプリケーションのすべてのユーザーが保持する権限ではなく、ロールに付与されている権限のみを修正することで実行できます。
特定のアプリケーションの実行に必要な権限は、ROLE_TAB_PRIVSとROLE_SYS_PRIVSの各データ・ディクショナリ・ビューを問い合せることで判断できます。
どのユーザーに、どのアプリケーションの権限があるかは、DBA_ROLE_PRIVSデータ・ディクショナリ・ビューを問い合せることで判断できます。
|
関連項目:
|
「セキュア・アプリケーション・ロールを使用したロール権限の保護」で説明したように、セキュア・アプリケーション・ロールは、ロールに対応付けられているPL/SQLパッケージまたはプロシージャを介してのみ有効化できるロールです。このパッケージは、アプリケーションへのアクセスを制御するために必要なポリシーを定義します。
この項の内容は、次のとおりです。
|
関連項目: セキュア・アプリケーション・ロールの作成例は、『Oracle Database 2日でセキュリティ・ガイド』を参照してください。 |
セキュア・アプリケーション・ロールは、IDENTIFIED USING句が指定されたCREATE ROLE SQL文を使用して作成します。この文を実行するには、CREATE ROLEシステム権限が必要です。
たとえば、sec_mgr.hr_adminパッケージに対応付けられるhr_adminというセキュア・アプリケーション・ロールを作成するには、次の手順を実行します。
次のようにセキュア・アプリケーション・ロールを作成します。
CREATE ROLE hr_admin IDENTIFIED USING sec_mgr.hr_admin_role_check;
この例から次のようなことがわかります。
作成されるhr_adminは、セキュア・アプリケーション・ロールです。
ロールを使用可能にできるのは、PL/SQLプロシージャsec_mgr.hr_admin_role_check内で定義されたモジュールのみです。この時点で、このプロシージャは終了する必要がありません。「手順2: アプリケーションに対するアクセス・ポリシーを定義するPL/SQLパッケージの作成」で、パッケージまたはプロシージャを作成する方法を説明しています。
セキュア・アプリケーション・ロールに対して、このロールに通常対応付ける権限を付与します。
たとえば、HR.EMPLOYEES表に対するSELECT、INSERT、UPDATEおよびDELETE権限をhr_adminロールに付与するには、次の文を入力します。
GRANT SELECT, INSERT, UPDATE, DELETE ON HR.EMPLOYEES TO hr_admin;
このロールをユーザーに直接付与しないでください。ユーザーがセキュリティ・ポリシーを通過した場合は、PL/SQLプロシージャまたはパッケージによってこのロールが自動的に付与されます。
セキュア・アプリケーション・ロールを使用可能または使用禁止にするには、PL/SQLパッケージ内にロールのセキュリティ・ポリシーを作成します。個別のプロシージャを作成してこれを実行することもできますが、パッケージを使用すると、一連のプロシージャをグループ化できます。これにより、一緒に使用するポリシーのグループを作成して、アプリケーションを保護するための強固なセキュリティ戦略を提示できます。セキュリティ・ポリシーに失敗したユーザー(潜在的な侵入者)については、監査チェックをパッケージに追加して、その失敗を記録できます。通常、このパッケージは、セキュリティ管理者のスキーマに作成します。
このパッケージまたはプロシージャは、次の内容を実行する必要があります。
実行者権限を使用してロールを有効にする必要があります。実行者権限を使用してパッケージを作成するには、AUTHIDプロパティをCURRENT_USERに設定する必要があります。定義者権限を使用してパッケージを作成することはできません。
実行者権限と定義者権限の詳細は、『Oracle Database PL/SQL言語リファレンス』を参照してください。
ユーザーを検証するためのセキュリティ・チェックを1つ以上組み込む必要があります。ユーザーを検証する方法の1つは、SYS_CONTEXT SQLファンクションを使用することです。SYS_CONTEXTの詳細は、『Oracle Database SQL言語リファレンス』を参照してください。ユーザーに関するセッション情報を検索するために、アプリケーション・コンテキストでSYS_CONTEXTを使用できます。詳細は、第6章「アプリケーション・コンテキストを使用したユーザー情報の取得」を参照してください。
ユーザーがセキュリティ・チェックを通過するときにSET ROLE SQL文またはDBMS_SESSION.SET_ROLEプロシージャを発行する必要があります。パッケージは実行者権限を使用して作成するため、SET ROLE SQL文またはDBMS_SESSION.SET_ROLEプロシージャを発行することによりロールを設定する必要があります。(ただし、このタイプのロール有効化でSET ROLE ALL文を使用することはできません。)PL/SQL埋込みSQL構文はSET ROLE文をサポートしませんが、動的SQL(たとえばEXECUTE IMMEDIATEにより)を使用することによりSET ROLEを起動できます。
EXECUTE IMMEDIATEの詳細は、『Oracle Database PL/SQL言語リファレンス』を参照してください。
このパッケージまたはプロシージャの作成方法が原因で、セキュア・アプリケーション・ロールを使用可能または使用禁止にする際にログイン・トリガーを使用できません。かわりに、ユーザーがセキュリティ・アプリケーション・ロールで付与された権限を使用する前のユーザー・ログイン時に、アプリケーションからパッケージを直接起動します。
たとえば、hr_adminロールを使用するユーザーを、午前8時から午後5時の間にオンサイトになっている(つまり、特定の端末を使用している)従業員に制限する場合を想定します。システム管理者またはセキュリティ管理者は、次の手順を実行します。(最初の行のCREATE OR REPLACEの前にカーソルを置くことで、このテキストをコピーして貼り付けることができます。)
次のようにプロシージャを作成します。
CREATE OR REPLACE PROCEDURE hr_admin_role_check
AUTHID CURRENT_USER
AS
BEGIN
IF (SYS_CONTEXT ('userenv','ip_address')
BETWEEN '192.0.2.10' and '192.0.2.20'
AND
TO_CHAR (SYSDATE, 'HH24') BETWEEN 8 AND 17)
THEN
EXECUTE IMMEDIATE 'SET ROLE hr_admin';
END IF;
END;
/
この例の説明は、次のとおりです。
AUTHID CURRENT_USERは、実行者権限を使用できるように、AUTHIDプロパティをCURRENT_USERに設定します。
IF (SYS_CONTEXT ('userenv','ip_address')は、ユーザー・セッション情報を取得するSYS_CONTEXT SQLファンクションを使用してユーザーを検証します。
BETWEEN ... TO_CHARは、アクセス権を付与または拒否するテストを作成します。テストはオンサイト(つまり、特定の端末を使用中)で午前8:00から午後5:00までの時間に作業するユーザーについて、アクセスを制限します。ユーザーがこのチェックを通過すると、hr_adminロールが付与されます。
THEN... EXECUTEは、EXECUTE IMMEDIATEコマンドを使用してSET ROLE文を発行し、そのユーザーにロールを付与します。
ロールが割り当てられたユーザーに対して、hr_admin_role_checkプロシージャのEXECUTE権限を付与します。
例:
GRANT EXECUTE ON hr_admin_role_check TO psmith;
セキュア・アプリケーション・ロールをテストするには、そのユーザーでSQL*Plusにログインし、ロールを使用可能にして、ロールで付与される権限が必要なアクションを実行してみます。
例:
CONNECT PSMITH Enter password: password EXECUTE sec_admin.hr_admin_role_check; -- Actions requiring privileges granted by the role
ユーザーの権限が、現在のデータベース・ロールに関連した権限のみであることを確認します。
この項の内容は、次のとおりです。
1人のユーザーが、多数のアプリケーションおよび多数の対応付けられたロールを使用できます。ただし、このユーザーの権限は、現在のデータベース・ロールに関連した権限のみであることを確認する必要があります。次の使用例を考えてみます。
(「受注」というアプリケーションの)ORDERロールには、INVENTORY表に対するUPDATE権限が含まれています。
(「在庫」というアプリケーションの)INVENTORYロールには、INVENTORY表に対するSELECT権限が含まれています。
何人かの受注入力担当には、ORDERロールとINVENTORYロールの両方が付与されています。
このシナリオでは、両方のロールを付与された受注入力担当がINVENTORYアプリケーションの実行時にORDERロールの権限を使用してINVENTORY表を更新できます。問題は、INVENTORY表の更新は、INVENTORYアプリケーションにとって承認された操作ではないということです。ORDERアプリケーションにとって承認された操作です。この問題を防ぐには、SET ROLE文を次のセクションの説明のとおりに使用します。
各アプリケーションの開始時にSET ROLE文を使用して、各アプリケーションに対応付けられているロールを自動的に使用可能にし、他のすべてのロールを使用禁止にします。この方法によって、各アプリケーションでは、必要な場合にのみ、ユーザーの特定の権限を動的に使用可能にします。
SET ROLE文は、権限の管理を簡素化します。ユーザーがどのような情報にアクセスできるかと、その情報にいつアクセスできるかを制御します。また、このSET ROLE文によって、ユーザーは、明確に定義された権限ドメイン内で操作を続行できます。あるユーザーがロールからのみ権限を取得している場合、そのユーザーはこれらの権限を組み合せて不正な操作を実行することはできません。
スキーマとは、データベース・オブジェクトを含めることができるセキュリティ・ドメインです。各ユーザーまたはロールに付与された権限によって、これらのデータベース・オブジェクトへのアクセスが制御されます。
この項の内容は、次のとおりです。
ほとんどのスキーマはユーザー名と考えることができます。つまり、ユーザーがデータベースに接続してそのデータベース・オブジェクトへのアクセスを可能にするアカウントです。ただし、一意スキーマではデータベースへの接続は許可されませんが、関連する一連のオブジェクトを格納するために使用されます。この種のスキーマは通常ユーザーとして作成されますが、CREATE SESSIONシステム権限は(明示的にもロールを介しても)付与されません。しかし、CREATE SCHEMAを使用して、1つのトランザクション内に複数の表とビューを作成する場合は、一意スキーマにCREATE SESSIONおよびRESOURCE権限を一時的に付与する必要があります。
たとえば、所定のスキーマが特定のアプリケーションのスキーマ・オブジェクトを所有する場合があります。アプリケーション・ユーザーに権限がある場合、そのユーザーは、一般的なデータベース・ユーザー名を使用してデータベースに接続し、アプリケーションとそれに対応するオブジェクトを使用できます。ただし、ユーザーはアプリケーションに設定されたスキーマを使用して、データベースに接続することはできません。この構成は、対応付けられたオブジェクトへのスキーマを介したアクセスを防ぎ、スキーマ・オブジェクトの保護を強化します。この場合、アプリケーションではALTER SESSION SET CURRENT_SCHEMA文を発行して、ユーザーを適切なアプリケーション・スキーマに接続できます。
多くのアプリケーションでは、ユーザーはデータベースで自分自身のアカウントつまりスキーマを必要としません。これらのユーザーに必要なのは、アプリケーション・スキーマへのアクセスのみです。たとえば、ユーザーJohn、FiruzehおよびJaneはすべて給与アプリケーションのユーザーで、financeデータベースのpayrollスキーマにアクセスする必要があるとします。この場合、データベースに自分自身のオブジェクトを作成する必要があるユーザーはいません。これらのユーザーに必要なのは、payrollオブジェクトへのアクセスのみです。この問題に対処するために、Oracle Advanced Securityではエンタープライズ・ユーザー(スキーマに依存しないユーザー)が提供されています。
エンタープライズ・ユーザー、つまりディレクトリ・サービスで管理されるユーザーは、共有データベース・スキーマを使用するため、データベース・ユーザーとして作成する必要はありません。管理コストを削減するために、管理者はディレクトリに1つのエンタープライズ・ユーザーを1回作成し、他の多数のユーザーもアクセスできる共有スキーマで、そのユーザーを指し示すことができます。
エンタープライズ・ユーザー管理の詳細は、『Oracle Databaseエンタープライズ・ユーザー・セキュリティ管理者ガイド』を参照してください。
アプリケーション設計の一部として、そのアプリケーションで作業するユーザーのタイプ、およびユーザーが担当のタスクを達成するために必要なアクセスのレベルを決定する必要があります。これらのユーザーを複数のロール・グループに分類し、各ロールに付与する権限を決定する必要があります。
この項の内容は、次のとおりです。
通常、エンド・ユーザーにはオブジェクト権限が付与されています。オブジェクト権限によって、ユーザーは、特定の表、ビュー、順序、プロシージャ、ファンクションまたはパッケージに対して特定のアクションを実行できます。
表5-2に、オブジェクトの各タイプで使用できるオブジェクト権限の概要を示します。
表5-2 権限とスキーマ・オブジェクトとの関連
| オブジェクト権限 | 表への適用 | ビューへの適用 | 順序への適用 | プロシージャへの適用脚注 1 |
|---|---|---|---|---|
|
|
あり |
なし |
あり |
なし |
|
|
あり |
あり |
なし |
なし |
|
|
なし |
なし |
なし |
あり |
|
|
あり脚注 2 |
なし |
なし |
なし |
|
|
あり |
あり |
なし |
なし |
|
|
あり脚注 2 |
なし |
なし |
なし |
|
|
あり |
あり脚注 3 |
あり |
なし |
|
|
あり |
あり |
なし |
なし |
脚注1スタンドアロンのストアド・プロシージャ、ファンクションおよびパブリック・パッケージ構成
脚注2ロールに付与できない権限
脚注3スナップショットにも付与可能
スキーマ・オブジェクトの監査方法の詳細は、「スキーマ・オブジェクトの監査」も参照してください。
アプリケーションの実装時およびテスト時には、必要な各ロールを作成する必要があります。各ロールの使用例をテストし、データベースへのアクセス権がアプリケーション・ユーザーに正しく付与されることを確認します。テスト終了後は、アプリケーションの管理者と共同で各ユーザーに適切なロールが割り当てられていることを確認します。
表5-3に、表5-2で示したオブジェクト権限によって許可されるSQL文を示します。
表5-3 データベース・オブジェクト権限によって許可されるSQL文
| オブジェクト権限 | 許可されるSQL文 |
|---|---|
|
|
|
|
|
|
|
パブリック・パッケージ変数への参照 |
|
|
|
|
|
|
|
|
オブジェクト(表のみ)に対する |
|
|
順序を使用するSQL文 |
オブジェクト権限の詳細は、「権限とロールの概要」を参照してください。SQL文の監査方法の詳細は、「SQL文の監査」も参照してください。
データベース管理者は、この項の次の手順に従ってアプリケーションのセキュリティを管理できます。
サーバーが不正なパケット、順序に誤りがあるパケット、プライベートまたは未使用のリモート・プロシージャ・コールを受信した場合、Oracle Call Interface(OCI)やTwo-Task Common(TTC)などのネットワーキング通信ユーティリティでは、スタック・トレースおよびヒープ・ダンプを格納する大規模なディスク・ファイルを生成できます。通常、このディスク・ファイルは、非常に大規模になる可能性があります。侵入者は、サーバーに不正なパケットを繰り返し送信し、ディスクあふれやサービスの拒否を発生させることで、システムを使用できないようにする可能性があります。認証されていないクライアントが、この種の攻撃を仕掛ける可能性もあります。
これらの攻撃は、SEC_PROTOCOL_ERROR_TRACE_ACTION初期化パラメータを次の値のいずれかに設定することで防止できます。
None: サーバーが不正なパケットを無視し、トレース・ファイルまたはログ・メッセージを生成しないように構成します。サーバーの可用性が不正なパケットの受信を認識することよりも圧倒的に重要な場合は、この設定を使用します。
例:
SEC_PROTOCOL_ERROR_TRACE_ACTION = None
Trace(デフォルト設定): トレース・ファイルを作成します。これは、ネットワーク・クライアントが不具合の結果として不正なパケットを送信している場合など、デバッグを目的とする場合に便利です。
例:
SEC_PROTOCOL_ERROR_TRACE_ACTION = Trace
Log: サーバー・トレース・ファイルに1行の短いメッセージを書き込みます。この選択肢では、一定レベルの監査とシステムの可用性とのバランスがとれます。
例:
SEC_PROTOCOL_ERROR_TRACE_ACTION = Log
Alert: データベース管理者または監視コンソールにアラート・メッセージを送信します。
例:
SEC_PROTOCOL_ERROR_TRACE_ACTION = Alert
Oracle Databaseは、クライアントまたはサーバー・プロトコルからエラーを検出した後も実行を継続する必要があります。ただし、これによって、ディスクあふれまたはサービス拒否攻撃を発生させる可能性のある不正なパケットを、サーバーがさらに受信する可能性があります。
サーバーが悪意のあるクライアントから不正なパケットを受信しているときに、サーバー・プロセスの実行を詳細に制御するには、SEC_PROTOCOL_ERROR_FURTHER_ACTION初期化パラメータを次の値のいずれかに設定します。
Continue(デフォルト設定): サーバーの実行を継続します。ただし、サーバーがさらに攻撃を受ける可能性があることに注意してください。
例:
SEC_PROTOCOL_ERROR_FURTHER_ACTION = Continue
Delay,m: クライアントをm秒間遅延させます(サーバーが次のリクエストを同じクライアント接続から受け付けるまで)。この設定により悪意のあるクライアントはサーバー・リソースを過剰使用できなくなります。正当なクライアントのパフォーマンスも低下しますが、引き続き機能します。
例:
SEC_PROTOCOL_ERROR_FURTHER_ACTION = Delay,3
Drop,n: クライアント接続は、n個の不正パケットを受信した後に強制的に終了します。この設定を使用すると、トランザクションの損失など、クライアントを犠牲にしてサーバー自体を保護できます。ただし、クライアントは再度接続して、同じ操作を再試行できます。
例:
SEC_PROTOCOL_ERROR_FURTHER_ACTION = Drop,10
Oracle Databaseでは、サーバー・プロセスが最初に起動し、次にクライアントがこのサーバー・プロセスで認証を受けます。侵入者は、最初にサーバー・プロセスを起動し、様々なユーザー名とパスワードを使用して認証リクエストを無制限に発行し、データベースへのアクセスを試みます。
アプリケーション接続に対するログイン失敗回数を制限するには、SEC_MAX_FAILED_LOGIN_ATTEMPTS初期化パラメータを設定して、接続に対する認証試行回数を制限します。指定した認証試行回数を失敗すると、データベース・プロセスは接続を切断します。デフォルトでは、SEC_MAX_FAILED_LOGIN_ATTEMPTSは10に設定されます。
SEC_MAX_FAILED_LOGIN_ATTEMPTS初期化パラメータは、潜在的侵入者がアプリケーションを攻撃できないようにするためのものであることに注意してください。正当なユーザーには適用しません。sqlnet.ora INBOUND_CONNECT_TIMEOUTパラメータとFAILED_LOGIN_ATTEMPTS初期化パラメータもログイン失敗を制限しますが、違いはこの2つのパラメータが正当なユーザー・アカウントにのみ適用するということです。
たとえば、最大試行回数を5に制限するには、initsid.ora初期化パラメータ・ファイルで、次のようにSEC_MAX_FAILED_LOGIN_ATTEMPTSを設定します。
SEC_MAX_FAILED_LOGIN_ATTEMPTS = 5
クライアント接続(Oracle Call Interfaceクライアントを含む)が認証されてから、詳細な製品バージョン情報にアクセスできるようにする必要があります。侵入者は、データベース・バージョンを使用して、データベース・ソフトウェアに存在するセキュリティの脆弱性に関する情報を検出する可能性があります。
認証されていないクライアントに対してデータベース・バージョン・バナーの表示を制限するには、initsid.ora初期化パラメータ・ファイルでSEC_RETURN_SERVER_RELEASE_BANNER初期化パラメータをTRUEまたはFALSEのいずれかに設定します。デフォルトでは、SEC_RETURN_SERVER_RELEASE_BANNERはFALSEに設定されます。
たとえば、TRUEに設定すると、Oracle Databaseに正確なデータベース・バージョンが表示されます。
Oracle Database 11g Enterprise Edition Release 11.2.0.0 - Production
今後、たとえばOracle Database 11.2.0.2をインストールした場合は、次のバナーが表示されます。
Oracle Database 11g Enterprise Edition Release 11.2.0.2 - Production
ただし、同じリリースでこのパラメータをFALSEに設定すると、このバナーは制限されて、リリース11.2で開始する次の固定テキストで表示されます。
Oracle Database 11g Release 11.2.0.0.0 - Production
不正なアクセスおよびユーザー・アクション監査をユーザーに警告するには、バナーを作成して構成する必要があります。この通知は、クライアント・アプリケーションがデータベースにログインすると使用可能になります。
これらのバナーを構成して表示するには、データベース・サーバー側で次のsqlnet.oraパラメータを設定して、バナー情報が含まれるテキスト・ファイルを指し示します。
SEC_USER_UNAUTHORIZED_ACCESS_BANNER。次に例を示します。
SEC_USER_UNAUTHORIZED_ACCESS_BANNER = /opt/Oracle/11g/dbs/unauthaccess.txt
SEC_USER_AUDIT_ACTION_BANNER。次に例を示します。
SEC_USER_AUDIT_ACTION_BANNER = /opt/Oracle/11g/dbs/auditactions.txt
デフォルトでは、これらのパラメータは設定されません。さらに、バナー・テキストに使用される文字数には512バイトの制限があることを注意してください。
これらのパラメータを設定した後に、Oracle Call Interfaceアプリケーションでは、適切なOCI APIを使用してこれらのバナーを取得し、エンド・ユーザーに表示する必要があります。