12 アプリケーション開発者のセキュリティの管理

アプリケーション開発者のセキュリティ・ポリシーでは、パスワード管理や外部プロシージャおよびアプリケーション権限の保護といった領域を網羅する必要があります。

12.1 アプリケーション・セキュリティ・ポリシーについて

アプリケーション・セキュリティ・ポリシーは、アプリケーション・セキュリティの要件およびデータベース・オブジェクトへのユーザー・アクセスを規制するルールのリストです。

安全なデータベース・アプリケーションを作成する最初のステップは、アプリケーション・セキュリティ・ポリシーの作成です。セキュリティ・ポリシーは、データベース・アプリケーションごとに立案する必要があります。たとえば、各データベース・アプリケーションには、アプリケーションの実行時に異なるレベルのセキュリティを提供する1つ以上のデータベース・ロールが必要です。データベース・ロールは、他のロールに付与するか、または特定のユーザーに直接付与できます。

(SQL*PlusやSQL Developerなどのツールを使用して)SQL文を制限なしで処理できるアプリケーションの場合でも、機密扱いのスキーマ・オブジェクトや重要なスキーマ・オブジェクトへの不当なアクセスを防ぐために、セキュリティ・ポリシーが必要です。特に、使用するアプリケーションでパスワードが安全に処理されることを確認する必要があります。

12.2 アプリケーション・ベースのセキュリティの使用に関する考慮事項

アプリケーション・セキュリティの実装では、アプリケーション・ユーザーとデータベース・ユーザーについて、アプリケーション内とデータベース内のどちらでセキュリティを強制するのかを検討する必要があります。

12.2.1 アプリケーション・ユーザーはデータベース・ユーザーでもあるか

できるかぎり、アプリケーション・ユーザーがデータベース・ユーザーであるアプリケーションを作成し、データベースに備わるセキュリティ・メカニズムを使用できるようにします。

多くの市販のパッケージ・アプリケーションでは、アプリケーション・ユーザーは、データベース・ユーザーではありません。これらのアプリケーションでは、複数のユーザーがユーザー自身をアプリケーションに対して認証し、次に、アプリケーションが単一の高い権限を持つユーザーとしてデータベースに接続します。これを、One Big Application Userモデルと呼びます。

この方法で作成されたアプリケーションは、通常、データベースに本来備わっているセキュリティ機能の多くを使用できません。これは、ユーザーの識別情報がデータベースで認識されないためです。ただし、クライアント識別子を使用して、いくつかのタイプの追跡を実行できます。たとえば、Oracle Call InterfaceのメソッドOCIAttrSetOCI_ATTR_CLIENT_IDENTIFIER属性を使用して、クライアント接続の監査と監視を有効にできます。クライアント識別子を使用すると、個々のWebユーザーの監査証跡データの収集、Webユーザーによるデータ・アクセスを制限するルールの適用、および各Webユーザーが使用しているアプリケーションの監視および追跡を行うことができます。

表12-1は、One Big Application Userモデルが様々なOracle Databaseセキュリティ機能にどのように影響するかを説明しています。

表12-1 One Big Application Userモデルの影響を受ける機能

Oracle Database機能 One Big Application Userモデルの制限

監査

セキュリティの基本原則の1つは、監査によるアカウンタビリティです。One Big Application Userがデータベース内のすべての操作を実行する場合、データベース監査では、個々のユーザーが実行したアクションに対する責任をそのユーザー自身に持たせることができません。アプリケーションでは、独自の監査メカニズムを実装して、ユーザーのアクションを個別に捕捉する必要があります。

Oracle厳密認証

データベースに対するクライアントの認証が、個々のユーザーではなくアプリケーションの場合、認証の厳密な形式(たとえば、SSLやトークンなどでのクライアント認証)は使用できません。

ロール

ロールは、データベース・ユーザーに割り当てられます。エンタープライズ・ロールは、データベース内で作成されていない場合でも、エンタープライズ・ユーザーに割り当てられ、データベースに認識されます。アプリケーション・ユーザーがデータベース・ユーザーではない場合、ロールの有用性は低くなります。この場合、アプリケーションでは独自のメカニズムを作成して、様々なアプリケーション・ユーザーがアプリケーション内のデータへアクセスするのに必要とする権限を識別する必要があります。

エンタープライズ・ユーザー管理

エンタープライズ・ユーザー管理機能は、Oracle Internet DirectoryなどのLDAPベースのディレクトリにユーザー情報を安全に格納して認可を管理することで、OracleデータベースがOracle Identity Managementインフラストラクチャを使用できるようにします。エンタープライズ・ユーザーは、データベース内に作成する必要はありませんが、データベースに認識される必要があります。One Big Application Userモデルでは、Oracle Identity Managementを利用できません。

12.2.2 アプリケーション内またはデータベース内でのセキュリティ規定

アプリケーションでできるだけ多くのデータベース・セキュリティ・メカニズムが使用されるようにすることをお薦めします。

ユーザーがデータベース・ユーザーでもあるアプリケーションは、アプリケーションの中にセキュリティを組み込むか、きめ細かい権限、仮想プライベート・データベース(アプリケーション・コンテキストによるファイングレイン・アクセス・コントロール)、ロール、ストアド・プロシージャ、および監査(ファイングレイン監査を含む)などのデータベースに本来備わっているセキュリティ・メカニズムを使用できます。

セキュリティがアプリケーション内ではなくデータベース自体で規定されている場合は、セキュリティを回避することはできません。アプリケーション・ベースのセキュリティの主なデメリットは、ユーザーがアプリケーションを回避してデータにアクセスすると、セキュリティが回避されることです。たとえば、データベースへのSQL*Plusアクセス権を持つユーザーは、人事管理アプリケーションを介さずに問合せを実行できます。したがって、このユーザーは、アプリケーション内のセキュリティ対策のすべてをバイパスします。

One Big Application Userモデルを使用するアプリケーションでは、データベースのセキュリティ・メカニズムを使用せずに、アプリケーション内にセキュリティ規定を作成する必要があります。ユーザーを認識するのは、データベースではなくアプリケーションであるため、アプリケーション自体が各ユーザーに対してセキュリティ対策を規定する必要があります。

このアプローチでは、データにアクセスする各アプリケーションでのセキュリティの再実装が必要になります。組織では複数のアプリケーションに同じセキュリティ・ポリシーを実装する必要があるため、セキュリティが高コストになり、新規アプリケーションごとに高コストな再実装が必要になります。

12.3 アプリケーション開発者用のDB_DEVELOPER_ROLEロールの使用

DB_DEVELOPER_ROLEロールは、アプリケーション開発者に必要なシステム権限、オブジェクト権限、事前定義ロール、PL/SQLパッケージ権限およびトレース権限の大部分を提供します。

アプリケーション開発者は、アプリケーションを設計、開発およびデプロイするために、多数のこれらの権限を必要とします。これらの権限を個別に付与したり、ユーザーにDBAロールを付与するのではなく、アプリケーション開発者にDB_DEVELOPER_ROLEロールを付与することをお薦めします。アプリケーション・ユーザーにDB_DEVELOPER_ROLEロールを付与すると、最小権限の原則に準拠するだけでなく、開発環境のセキュリティが強化されるため、アプリケーション・ユーザーのロールの付与および取消しの管理が容易になります。DB_DEVELOPER_ROLEロールは、CDBルートまたはPDBで使用できます。

DB_DEVELOPER_ROLEによって付与されるシステム権限、オブジェクト権限およびロールの完全なリストを生成するには、次の文を実行します。ラップされたset serveroutput on formatコマンドを含め、インデントが正しく表示されるようにします。

ノート:

出力は、使用しているOracle Databaseのバージョンまたはパッチ・リリースによって異なることに注意してください。
set serveroutput on format wrapped;
DECLARE
    procedure printRolePrivileges(
      p_role             in varchar2,
      p_spaces_to_indent in number) IS
      v_child_roles   DBMS_SQL.VARCHAR2_TABLE;
      v_system_privs  DBMS_SQL.VARCHAR2_TABLE;
      v_table_privs   DBMS_SQL.VARCHAR2_TABLE;
      v_indent_spaces varchar2(2048);
    BEGIN
      -- Indentation for nested privileges via granted roles.
      for space in 1..p_spaces_to_indent LOOP
        v_indent_spaces := v_indent_spaces || ' ';
      end LOOP;
      -- Get the system privileges granted to p_role
      select PRIVILEGE bulk collect into v_system_privs
      from DBA_SYS_PRIVS
      where GRANTEE = p_role
      order by PRIVILEGE;

      -- Print the system privileges granted to p_role
      for privind in 1..v_system_privs.COUNT LOOP
        DBMS_OUTPUT.PUT_LINE(
          v_indent_spaces || 'System priv: ' || v_system_privs(privind));
      END LOOP;

      -- Get the object privileges granted to p_role
      select PRIVILEGE || ' ' || OWNER || '.' || TABLE_NAME
        bulk collect into v_table_privs
      from DBA_TAB_PRIVS
      where GRANTEE = p_role
      order by TABLE_NAME asc;

      -- Print the object privileges granted to p_role
      for tabprivind in 1..v_table_privs.COUNT LOOP
        DBMS_OUTPUT.PUT_LINE(
          v_indent_spaces || 'Object priv: ' || v_table_privs(tabprivind));
      END LOOP;

      -- get all roles granted to p_role
      select GRANTED_ROLE bulk collect into v_child_roles
      from DBA_ROLE_PRIVS
      where GRANTEE = p_role
      order by GRANTED_ROLE asc;

      -- Print all roles granted to p_role and handle child roles
recursively.
      for roleind in 1..v_child_roles.COUNT LOOP
        -- Print child role
        DBMS_OUTPUT.PUT_LINE(
         v_indent_spaces || 'Role priv: ' || v_child_roles(roleind));

        -- Print privileges for the child role recursively. Pass 2 additional
        -- spaces to illustrate these privileges belong to a child role.
        printRolePrivileges(v_child_roles(roleind), p_spaces_to_indent + 2);
      END LOOP;

      EXCEPTION
        when OTHERS then
          DBMS_OUTPUT.PUT_LINE('Got exception: ' || SQLERRM );

    END printRolePrivileges;

BEGIN
    printRolePrivileges('DB_DEVELOPER_ROLE', 0);
END;
/

次のような出力が表示されます。

System priv: CREATE ANALYTIC VIEW
System priv: CREATE ATTRIBUTE DIMENSION
System priv: CREATE CUBE
System priv: CREATE CUBE BUILD PROCESS
System priv: CREATE CUBE DIMENSION
System priv: CREATE DIMENSION
System priv: CREATE DOMAIN
System priv: CREATE HIERARCHY
System priv: CREATE JOB
System priv: CREATE MATERIALIZED VIEW
System priv: CREATE MINING MODEL
System priv: CREATE MLE
System priv: CREATE PROCEDURE
System priv: CREATE SEQUENCE
System priv: CREATE SESSION
System priv: CREATE SYNONYM
System priv: CREATE TABLE
System priv: CREATE TRIGGER
System priv: CREATE TYPE
System priv: CREATE VIEW
System priv: DEBUG CONNECT SESSION
System priv: EXECUTE DYNAMIC MLE
System priv: FORCE TRANSACTION
System priv: ON COMMIT REFRESH
Object priv: SELECT SYS.DBA_PENDING_TRANSACTIONS
Object priv: EXECUTE SYS.JAVASCRIPT
Object priv: READ SYS.V_$PARAMETER
Object priv: READ SYS.V_$STATNAME
Role priv: CTXAPP
  System priv: CREATE SEQUENCE
  Object priv: EXECUTE CTXSYS.CTX_ANL
  Object priv: EXECUTE CTXSYS.CTX_DDL
  Object priv: EXECUTE CTXSYS.CTX_ENTITY
  Object priv: EXECUTE CTXSYS.CTX_OUTPUT
  Object priv: EXECUTE CTXSYS.CTX_THES
  Object priv: EXECUTE CTXSYS.CTX_ULEXER
  Object priv: INSERT CTXSYS.DR$DICTIONARY
  Object priv: DELETE CTXSYS.DR$DICTIONARY
  Object priv: SELECT CTXSYS.DR$DICTIONARY
  Object priv: UPDATE CTXSYS.DR$DICTIONARY
  Object priv: INSERT CTXSYS.DR$THS
  Object priv: INSERT CTXSYS.DR$THS_BT
  Object priv: INSERT CTXSYS.DR$THS_FPHRASE
  Object priv: UPDATE CTXSYS.DR$THS_PHRASE
  Object priv: INSERT CTXSYS.DR$THS_PHRASE
  Object priv: EXECUTE CTXSYS.DRIENTL
  Object priv: EXECUTE CTXSYS.DRITHSL
Role priv: SODA_APP
  Object priv: EXECUTE XDB.DBMS_SODA_ADMIN
  Object priv: EXECUTE XDB.DBMS_SODA_USER_ADMIN
  Object priv: READ XDB.JSON$USER_COLLECTION_METADATA

12.4 アプリケーション設計におけるパスワードの保護

Oracleでは、パスワード・サービスの安全な呼出し(スクリプトを使用するなど)のための戦略や、これらの戦略をその他の機密データに適用するための戦略を用意しています。

12.4.1 アプリケーションでのパスワードの保護に関する一般的なガイドライン

アプリケーションでのパスワードの保護に関するガイドラインでは、プラットフォーム固有のセキュリティ脅威などの領域を扱います。

12.4.1.1 プラットフォーム固有のセキュリティへの脅威

次の潜在的なセキュリティへの脅威は、わかりにくい可能性があるので注意してください。

これらのセキュリティへの脅威は次のとおりです。

  • UNIXおよびLinuxプラットフォームでは、同じホスト・コンピュータ上のすべてのオペレーティング・システム・ユーザーがコマンド・パラメータを表示できます。結果として、コマンドラインに入力したパスワードは他のユーザーに公開される可能性があります。ただし、UNIXおよびLinux以外のプラットフォームがこの脅威を回避できるとは考えないでください。

  • HP Tru64およびIBM AIXなど一部のUNIXプラットフォームでは、すべてのオペレーティング・システム・ユーザーがあらゆるプロセスの環境変数を表示できます。ただし、UNIXおよびLinux以外のプラットフォームがこの脅威を回避できるとは考えないでください。

  • Microsoft Windowsでは、コマンド・リコール機能(上矢印)に、コマンド起動間のユーザー入力が記憶されます。たとえば、SQL*PlusのCONNECT SYSTEM/password表記法を使用し、終了してから上矢印を押してCONNECTコマンドを繰り返した場合、コマンド・リコール機能により接続文字列が明らかになり、パスワードが表示されます。また、Microsoft Windows以外のプラットフォームがこの脅威を回避できるとは考えないでください。

12.4.1.2 パスワード入力を処理するアプリケーションの設計のガイドライン

Oracleには、パスワード入力を扱うアプリケーションの設計に関するガイドラインが用意されています。

  • パスワードの入力を対話形式で求めるアプリケーションを設計します。コマンドライン・ユーティリティの場合、コマンド・プロンプトでパスワードを公開することをユーザーに強制しないでください。

    アプリケーションの設計に使用するプログラミング言語(Javaなど)のAPIについて、ユーザーからのパスワードを処理する最適な方法を確認します。

  • コード・インジェクション攻撃からデータベースを保護します。コード・インジェクション攻撃の標的は主に、データベースにSQLを送信するクライアント・アプリケーション・ツール(SQL*Plus、Oracle Call Interface (OCI)、JDBCアプリケーションなど)です。これらのツールを使用して作成されたデータベース・ドライバも含まれます。SQLインジェクション攻撃では、PL/SQLアプリケーションで想定されていない方法でSQL文が動作します。インジェクション攻撃は、データベースに文が送信される前に行われます。たとえば、侵入者はWHERE句をTRUEに設定してパスワード認証を回避できます。

    SQLインジェクション攻撃の問題に対処するには、バインド変数引数を使用するか妥当性チェックを作成します。バインド変数を使用できない場合は、DBMS_ASSERT PL/SQLパッケージを使用して入力値のプロパティを検証することを検討してください。また、PUBLICなどのロールに対する権限付与も検討してください。

    インジェクションはデータベースに文が送信される前に行われる可能性があるため、クライアント・アプリケーションのユーザーがSQLインジェクションをPL/SQLに結び付けることができるとは限らないことに注意してください。

  • 可能な場合は、認証を遅延するアプリケーションを設計します。たとえば:

    • ログインに証明書を使用します。

    • オペレーティング・システムで提供される機能を使用してユーザーを認証します。たとえば、Microsoft Windows上で動作するアプリケーションはドメイン認証を使用できます。

  • パスワードをマスクまたは暗号化します。パスワードを格納する必要がある場合、パスワードをマスクまたは暗号化します。たとえば、ログ・ファイルでパスワードをマスクし、リカバリ・ファイルでパスワードを暗号化できます。

  • 各接続を認証します。たとえば、スキーマAがデータベース1に存在する場合、データベース2のスキーマAが同一のユーザーであるとは考えないでください。同様に、ローカル・オペレーティング・システム・ユーザーpsmithは、必ずしもリモート・ユーザーpsmithと同じユーザーではありません。

  • ファイルまたはリポジトリにクリアテキスト・パスワードを格納しないでください。パスワードをファイルに格納すると、侵入者がパスワードにアクセスする危険性が高まります。

  • 1つのメイン・パスワードを使用します。次に例を示します。

    • 1人のデータベース・ユーザーに、他のデータベース・ユーザーとなるためのプロキシ認証を付与できます。この場合、必要となるデータベース・パスワードは1つのみです。

    • Oracle Database Enterprise User Security Wallet Managerを使用して、メイン・パスワードで開くことができるパスワード・ウォレットを作成できます。ウォレットにはその他のパスワードが含められます。

      ノート:

      エンタープライズ・ユーザー・セキュリティ(EUS)は、Oracle Database 23cで非推奨になりました。

      集中管理ユーザー(CMU)の使用に移行することをお薦めします。この機能を使用すると、データベースに対するエンタープライズ・ユーザー認証および認可のためにディレクトリ・サービスを介在させることなく、Microsoft Active Directoryに直接接続できます。Oracle Databaseがクラウドにある場合は、クラウド・アイデンティティ・プロバイダとの新しい統合のいずれかに移行することも選択できます。

12.4.1.3 パスワードの形式と動作の構成のガイドライン

Oracle Databaseには、パスワードの形式と動作の構成に関するガイドラインが用意されています。

  • パスワードの存続期間を制限します。パスワード存続期間を設定して、その期間が過ぎるとパスワードが期限切れになり、変更しないとユーザーがアカウントにログインできなくなるようにすることができます。PASSWORD_REUSE_TIMEPASSWORD_REUSE_MAXおよび PASSWORD_VERIFY_FUNCTIONパラメータを設定することによって、これらの要素を制御します。

  • ユーザーが古いパスワードを再利用する機能を制限します。新しい一意のパスワードの作成をユーザーに強制することで、侵入者によるパスワードの推測を大幅に抑制できます。

  • 強力かつ安全なパスワードを作成するようユーザーに要求します。パスワードの複雑度検証を使用して、サイトのパスワード要件をカスタマイズできます。これにより、強力なパスワードを作成するためのOracleのガイドラインへの準拠がユーザーに強制されます。

  • パスワードで大/小文字を区別できるようにします。デフォルトでは、新しいパスワードでは大文字と小文字が区別されます。

12.4.1.4 SQLスクリプトにおけるパスワードの処理のガイドライン

Oracleには、SQLスクリプトにおけるパスワードの取扱いに関するガイドラインが用意されています。

  • プログラムまたはスクリプトのコマンドラインにパスワードを指定して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コマンドと結合する方法は、パスワードおよびその他の機密入力データを非表示にする便利なテクニックです。

    • ACCEPT password CHAR PROMPTは、ACCEPT passwordプロンプトにHIDEオプションを含めます。これによって入力パスワードのエコーが回避されます。

    次の例では位置パラメータを使用しており、ユーザーがコマンドライン上でパスワードを渡すことでスクリプトを起動できるため、セキュリティ上のリスクをもたらします。ユーザーがパスワードを入力せず、入力を求めるプロンプトが表示される場合、ユーザーが入力した内容がすべて画面およびスプール・ファイル(スプールが有効な場合)にエコーされるため危険です。

    CONNECT &1/&2
    
  • バッチ・スクリプトのログイン時間を制御します。パスワードを必要とするバッチ・スクリプトでは、実行されることになっている時間内のみにバッチ・スクリプトがログインできるようにアカウントを構成します。たとえば、毎日午後8時から1時間実行するバッチ・スクリプトを想定します。この時間のみスクリプトがログインできるようにアカウントを設定します。侵入者がアクセスしようとした場合、安全性の低いアカウントが利用される可能性は低くなります。

  • パスワードの入力を求めるDML文またはDDL SQL文を使用する場合は注意してください。この場合、機密情報がネットワークを介してクリアテキストで渡されます。Oracle厳密認証を使用して、この問題に対処できます。

    次のパスワード変更例は、パスワードが公開されないため安全です。

    password psmith
    Changing password for psmith
    New password: password
    Retype new password: password
    

    この例は、パスワードがコマンドラインおよびネットワーク上の両方に公開されるため、セキュリティ上のリスクをもたらします。

    ALTER USER psmith IDENTIFIED BY password 

12.4.2 外部パスワード・ストアを使用したパスワードの保護

データベースに接続するためのパスワード資格証明は、クライアント側のOracleウォレットを使用して格納できます。

Oracleウォレットは、ユーザーのログインに必要な認証および署名用証明書を格納する安全性の高いソフトウェア・コンテナです。

12.4.3 ORAPWDユーティリティを使用したパスワードの保護

SYSDBAまたはSYSOPERユーザーはパスワード・ファイルを使用して、ネットワーク経由でアプリケーションに接続できます。

  • パスワード・ファイルを作成するには、ORAPWDユーティリティを使用します。

12.4.4 例: パスワードを読み取るためのJavaコード

パスワードの読取りに使用できるJavaパッケージを作成できます。

例12-1に、パスワードの読取りに使用できるJavaパッケージの作成方法を示します。

例12-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();
    }
  }
}

12.5 外部プロシージャの保護

外部プロシージャはデータベースとは別に.dllまたは.soファイルに保存され、資格証明認証で保護できます。

12.5.1 外部プロシージャの保護について

安全上の理由のため、Oracle外部プロシージャは、データベースと物理的に異なるプロセスで実行されます。

ほとんどの場合、このプロセスを構成して、Oracleソフトウェア・アカウント以外のユーザーとして実行します。アプリケーションがこの外部プロシージャを呼び出す場合(.dllまたは.soファイルのライブラリにアクセスする必要がある場合など)、Oracle Databaseがextprocと呼ばれるオペレーティング・システム・プロセスを作成します。デフォルトでは、extprocプロセスは、サーバー・プロセスで直接通信します。つまり、資格証明を使用しない場合、Oracle Databaseは、デフォルトのOracle Databaseサーバー構成でextprocプロセスを作成し、oracleソフトウェア・アカウントとしてextprocを実行します。また、Oracle Databaseリスナーを介して通信できます。

12.5.2 資格証明の認証に対するextprocの構成に関する一般プロセス

セキュリティを強化するために、extprocプロセスを構成して、資格証明を介して認証できます。

一般プロセスは次のとおりです。

  1. 資格証明を作成し、それを使用するようにデータベースを構成します(つまり、外部プロシージャの認証を構成します)。

    資格証明は、暗号化されたコンテナにあります。パブリック・シノニムとプライベート・シノニムの両方でこの資格証明を参照できます。

  2. 専用サーバーまたは共有サーバー・プロセスで実行しているデータベースへの最初の接続を行います。

  3. アプリケーションは、外部プロシージャへのコールを行います。

    これが最初のコールの場合、Oracle Databaseはextprocプロセスを作成します。extprocの資格証明を使用する場合、Oracleリスナーを使用してextprocプロセスを起動できません。

  4. extprocプロセスは、偽装(つまり、指定された資格証明のかわりに実行)して、必要な.dll.so.slまたは.aファイルをロードし、SQLおよびC間のデータを送信します。

12.5.3 extprocプロセス認証および偽装の予期される動作

extprocプロセスには、認証および偽装に対する一連の動作が含まれています。

表12-2では、可能性のある認証および偽装シナリオに基づくextprocプロセスの予期される動作について説明します。

この表では、資格証明が明示的に指定されず、ENFORCE_CREDENTIAL環境変数がTRUEに設定されている場合、GLOBAL_EXTPROC_CREDENTIALは、デフォルトの資格証明の予約された資格証明名です。そのため、ENFORCE_CREDENTIALTRUEに設定されている場合、この名前で資格証明を作成することをお薦めします。

表12-2 extprocプロセス認証および偽装設定の予期される動作

ENFORCE_CREDENTIAL環境変数設定 資格証明を使用したPL/SQLライブラリか GLOBAL_EXTPROC_CREDENTIAL資格証明の有無 予期される動作

FALSE

いいえ

いいえ

OracleリスナーまたはOracleサーバー・プロセスの所有者のオペレーティング・システム権限で認証される12cより前のリリースの認証を使用します。

FALSE

いいえ

はい

Oracle Databaseインスタンス全体の指定されたGLOBAL_EXTPROC_CREDENTIALを使用して認証および偽装します。

GLOBAL_EXTPROC_CREDENTIAL資格証明のみを使用している場合、このグローバル資格証明のEXECUTE権限が暗黙的にすべてのユーザーに自動的に付与されます。

FALSE

はい

いいえ

PL/SQLライブラリで定義された資格証明を使用して認証および偽装します

FALSE

はい

はい

認証および偽装します。

PL/SQLライブラリおよびGLOBAL_EXTPROC_CREDENTIAL設定の両方で資格証明が定義された場合、PL/SQLライブラリの資格証明が優先されます。

TRUE

いいえ

いいえ

エラーORA-28575: 外部プロシージャ・エージェントへのRPC接続をオープンできませんを戻します

TRUE

いいえ

はい

Oracleシステム全体の指定されたGLOBAL_EXTPROC_CREDENTIALを使用して認証および偽装します(脚注1)

TRUE

はい

いいえ

PL/SQLライブラリで定義された資格証明を使用して認証および偽装します

TRUE

はい

はい

認証および偽装します(脚注2)

12.5.4 外部プロシージャの認証の構成

extprocプロセスの資格証明を構成するには、DBMS_CREDENTIAL PL/SQLパッケージを使用します。

  1. CREATE CREDENTIALまたはCREATE ANY CREDENTIAL権限が付与されたユーザーとしてPDBにログインします。

    また、CREATE LIBRARYまたはCREATE ANY LIBRARY権限および外部コールを含むライブラリのEXECUTEオブジェクト権限があることも確認します。

    sqlplus psmith@hpdb
    Enter password: password
    Connected.
    

    CDB内の使用可能なPDBを確認するには、CDBルート・コンテナにログインし、DBA_PDBSデータ・ディクショナリ・ビューのPDB_NAME列を問い合せます。現在のコンテナを確認するには、show con_nameコマンドを実行します。

  2. DBMS_CREDENTIAL PL/SQLパッケージを使用して、新しい資格証明を作成します。

    たとえば:

    BEGIN
      DBMS_CREDENTIAL.CREATE_CREDENTIAL (
        credential_name   => 'smith_credential', 
        user_name         => 'tjones',
        password          => 'password')
    END;
    /

    この例では、次のようになります。

    • credential_name: 資格証明の名前を入力します。オプションで、スキーマの名前(たとえば、psmith.smith_credentialなど)で接頭辞を付けます。ENFORCE_CREDENTIAL環境変数がTRUEに設定されている場合、credential_name GLOBAL_EXTPROC_CREDENTIALを使用して資格証明を作成する必要があります。

    • user_name: ユーザーとして実行するために使用する有効なオペレーティング・システム・ユーザー名を入力します。

    • password: user_nameユーザーに対するパスワードを入力します。

  3. 資格証明とPL/SQLライブラリを関連付けます。

    たとえば:

    CREATE OR REPLACE LIBRARY ps_lib  
     AS 'smith_lib.so' IN DLL_LOC
     CREDENTIAL smith_credential;
    

    この例では、DLL_LOCは、$ORACLE_HOME/binディレクトリを指すディレクトリ・オブジェクトです。DLLへの絶対パスの使用はお薦めしません。

    PL/SQLライブラリがextprocプロセスを介して外部プロシージャ・コールによってロードされる場合、extprocでは、定義されたsmith_credential資格証明のかわりに認証および偽装できます。

  4. 外部プロシージャのコール方法や渡す引数をPL/SQLに対して通知するPL/SQLプロシージャまたは関数を作成して、外部プロシージャを登録します。

    たとえば、Cで記述された外部プロシージャを登録する関数を作成するには、CREATE FUNCTION文のAS LANGUAGE CLIBRARYおよびNAME句のみ次のように使用します。

    CREATE OR REPLACE FUNCTION getInt (x VARCHAR2, y BINARY_INTEGER)
    RETURN BINARY_INTEGER
    AS LANGUAGE C
    LIBRARY ps_lib
    NAME "get_int_vals"
    PARAMETERS (x STRING, y int);

12.5.5 レガシー・アプリケーションの外部プロシージャ

セキュリティを最大にするために、ENFORCE_CREDENTIAL環境変数をTRUEに設定します。

ただし、下位互換性に対応する必要がある場合、ENFORCE_CREDENTIALFALSEに設定します。FALSEによって、extprocプロセスは、指定された資格証明のかわりにユーザー定義コールアウト関数を認証、偽装および実行できます。

  • 資格証明がPL/SQLライブラリで定義されます。

  • 資格証明は定義されていませんが、 GLOBAL_EXTPROC_CREDENTIAL資格証明が存在します。

これらの資格証明定義が設定されていない場合、ENFORCE_CREDENTIALパラメータをFALSEに設定すると、OracleリスナーまたはOracleサーバー・プロセスの所有者のオペレーティング・システム権限で認証されるextprocプロセスが設定されます。

extprocプロセス上で実行されるレガシー・アプリケーションでは、レガシー・アプリケーション・コードを変更して、すべての別名ライブラリと資格証明を関連付けることをお薦めします。これを実行できない場合、Oracle DatabaseはGLOBAL_EXTPROC_CREDENTIAL資格証明を使用して、認証の処理方法を決定します。GLOBAL_EXTPROC_CREDENTIAL資格証明が定義されていない場合、extprocプロセスは、OracleリスナーまたはOracleサーバー・プロセスの所有者のオペレーティング・システム権限で認証されます。

12.6 LOBロケータの署名を使用したLOBの保護

LOBロケータの署名を再生成することで、ラージ・オブジェクト(LOB)を保護できます。

12.6.1 LOBロケータの署名を使用したLOBの保護について

LOBロケータ(ラージ・オブジェクト(LOB)値の実際の場所へのポインタ)には署名を割り当てることができます。この署名はLOBの保護に使用できます。

LOBを作成するときに、Oracle DatabaseによってLOBロケータに自動的に署名が割り当てられます。Oracle Databaseは、クライアントからロケータを受信したときに署名が一致することを検証し、ロケータが改ざんされていないことを確認します。署名ベース・セキュリティは、永続LOBロケータと一時LOBロケータの両方に使用できます。これは、索引構成表(IOT)ロケータから取得される分散CLOB、BLOBおよびNBLOBにも使用されます。

Oracle Real Applications Clusters (Oracle RAC)環境では、すべてのインスタンスが同じ署名キーを共有し、そのキーはデータベース内に永続化されます。各プラガブル・データベース(PDB)には、それ固有の署名キーがあります。LOBロケータが改ざんされている場合、署名検証によってLOBが拒否され、「ORA-64219:無効なLOBロケータが見つかりました」エラーが発生します。

スタンドアロン・データベースまたはPDBからクライアントに送信されるLOBロケータのLOB署名の生成に使用されたLOB署名キーを暗号化、キー更新および削除できます。署名キーを暗号化する予定の場合、キーが存在するデータベース(またはPDB)にオープンTDEキーストアが必要です。

LOB署名機能を有効にするには、LOB_SIGNATURE_ENABLE初期化パラメータをTRUEに設定する必要があります。デフォルトでは、LOB_SIGNATURE_ENABLEは、Oracle Databaseリリース19cではFALSEに設定されています。

12.6.2 LOBロケータの署名キーの暗号化の管理

ALTER DATABASE DICTIONARY SQL文を使用して、LOBロケータの署名キーを暗号化できます。

  1. ALTER DATABASE DICTIONARY権限を持つユーザーとして、データベース・インスタンスにログインします。
  2. 必要に応じて、LOB_SIGNATURE_ENABLE初期化パラメータをTRUEに設定して、LOB署名キー機能を有効にします。
    ALTER SYSTEM SET LOB_SIGNATURE_ENABLE = TRUE;
    または、データベースの再起動の前にinit.ora初期化ファイルのLOB_SIGNATURE_ENABLEパラメータを設定できます。これにより、すべてのPDBのLOB署名キー機能が有効になります。
  3. 署名キーを暗号化する予定の場合、データベースまたはPDBにオープンTDEキーストアがあることを確認します。
    TDEキーストアを作成するには、SYSKM管理権限が必要です。
    たとえば、ソフトウェアTDEキーストアを作成して開くには、次のようにします。
    ADMINISTER KEY MANAGEMENT CREATE KEYSTORE '/etc/ORACLE/WALLETS/orcl' IDENTIFIED BY password;
    ADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN IDENTIFIED BY password;
  4. ALTER DATABASE DICTIONARY文を実行して、LOB署名キー構成を設定します。
    • 不明瞭化せずにLOBロケータの署名キーを暗号化するには、次の文を実行します。
      ALTER DATABASE DICTIONARY ENCRYPT CREDENTIALS;
    • クライアントに送信されるLOBロケータのLOBロケータの署名キーを再生成するには、次の文を使用します。データベースが制限モードの場合、Oracle Databaseでは、再生成された署名キーを暗号化するために新しいLOB署名キーが再生成されます。データベースが非制限モードの場合、新しい署名キーは再生成されませんが、そのかわりにOracle Databaseでは新しい暗号化キーを使用して既存のLOB署名キーを暗号化します。データベース管理者またはPDB管理者は、できればデータベースの停止時に定期的にこの文を制限モードで実行することをお薦めします。
      ALTER DATABASE DICTIONARY REKEY CREDENTIALS;
    • 暗号化されたLOBロケータの署名キーを削除して、暗号化された形式のかわりに、不明瞭化された形式の新しいLOBの署名キーを再生成するには、次の文を実行します。
      ALTER DATABASE DICTIONARY DELETE CREDENTIALS;

12.7 アプリケーション権限の管理

ほとんどのデータベース・アプリケーションでは、異なるスキーマ・オブジェクトごとに異なる権限が関与します。

各アプリケーションに必要な権限の追跡は、複雑な場合があります。また、アプリケーションを実行するユーザーの認可には、多くのGRANT操作が関与する場合があります。アプリケーションの権限管理を簡素化するために、アプリケーションごとに作成した1つのロールに、1人のユーザーがそのアプリケーションを実行するために必要なすべての権限を付与します。実際には、1つのアプリケーションに複数のロールがある可能性があり、各ロールには、アプリケーションの実行中に使用できる機能の多少を決める権限の特定サブセットが付与されます。たとえば、すべての管理アシスタントが休暇アプリケーションを使用して、部門のメンバーが取得した休暇を記録するとします。このアプリケーションを最適に管理するには、次の操作を行う必要があります。
  1. VACATIONロールを作成します。
  2. 休暇アプリケーションに必要なすべての権限をVACATIONロールに付与します。
    有用なデータ・ディクショナリ・ビューは、ROLE_TAB_PRIVSROLE_SYS_PRIVSおよびDBA_ROLE_PRIVSです。
  3. VACATIONロールをすべての管理アシスタントに付与します。より効率的な方法は、管理アシスタントが持つ権限を定義したロールを作成し、VACATIONロールをそのロールに付与します。

12.8 アプリケーション権限の管理にロールを使用する利点

複数のアプリケーション権限を1つのロールにグループ化すると、権限の管理に役立ちます。

次の管理オプションを考えてみます。

  • アプリケーションを実行するユーザーに、多数の個別の権限ではなく、ロールを付与できます。したがって、従業員が業務を変更するときは、多くの権限ではなく、1つのロールのみを付与または取り消す必要があります。

  • アプリケーションに対応付けられている権限の変更は、そのアプリケーションのすべてのユーザーが保持する権限ではなく、ロールに付与されている権限のみを修正することで実行できます。

  • 特定のアプリケーションの実行に必要な権限は、ROLE_TAB_PRIVSROLE_SYS_PRIVSの各データ・ディクショナリ・ビューを問い合せることで判断できます。

  • どのユーザーに、どのアプリケーションの権限があるかは、DBA_ROLE_PRIVSデータ・ディクショナリ・ビューを問い合せることで判断できます。

12.9 アプリケーションへのアクセスを制御するセキュア・アプリケーション・ロールの作成

セキュア・アプリケーション・ロールは関連するPL/SQLパッケージまたはプロシージャでのみ使用可能にできます。

12.9.1 ステップ1: セキュア・アプリケーション・ロールの作成

IDENTIFIED USING句を持つCREATE ROLE文で、セキュア・アプリケーション・ロールを作成します。

この文を実行するには、CREATE ROLEシステム権限が必要です。

たとえば、sec_mgr.hr_adminパッケージに対応付けられるhr_adminというセキュア・アプリケーション・ロールを作成するには、次の手順を実行します。

  1. 次のようにセキュア・アプリケーション・ロールを作成します。
    CREATE ROLE hr_admin IDENTIFIED USING sec_mgr.hr_admin_role_check;
    

    この例から次のようなことがわかります。

    • 作成されるhr_adminは、セキュア・アプリケーション・ロールです。

    • ロールを使用可能にできるのは、PL/SQLプロシージャsec_mgr.hr_admin_role_check内で定義されたモジュールのみです。この段階では、このプロシージャが存在する必要はありません。

  2. セキュア・アプリケーション・ロールに対して、このロールに通常対応付ける権限を付与します。

    たとえば、HR.EMPLOYEES表に対するSELECTINSERTUPDATEおよびDELETE権限をhr_adminロールに付与するには、次の文を入力します。

    GRANT SELECT, INSERT, UPDATE, DELETE ON HR.EMPLOYEES TO hr_admin;
    

    このロールをユーザーに直接付与しないでください。ユーザーがセキュリティ・ポリシーを通過した場合は、PL/SQLプロシージャまたはパッケージによってこのロールが自動的に付与されます。

12.9.2 ステップ2: アプリケーションに対するアクセス・ポリシーを定義するPL/SQLパッケージの作成

アプリケーションに対するアクセス・ポリシーを定義するPL/SQLパッケージを作成できます。

12.9.2.1 アプリケーションに対するアクセス・ポリシーを定義するPL/SQLパッケージの作成について

セキュア・アプリケーション・ロールを有効または無効にするには、PL/SQLパッケージ内にロールのセキュリティ・ポリシーを作成する必要があります。

個別のプロシージャを作成してこれを実行することもできますが、パッケージを使用すると、一連のプロシージャをグループ化できます。これにより、一緒に使用するポリシーのグループを作成して、アプリケーションを保護するための強固なセキュリティ戦略を提示できます。セキュリティ・ポリシーに失敗したユーザー(潜在的な侵入者)については、監査チェックをパッケージに追加して、その失敗を記録できます。通常、このパッケージは、セキュリティ管理者のスキーマに作成します。

このパッケージまたはプロシージャは、次の内容を実行する必要があります。

  • 実行者権限を使用してロールを有効にする必要があります。実行者権限を使用してパッケージを作成するには、AUTHIDプロパティをCURRENT_USERに設定する必要があります。定義者権限を使用してパッケージを作成することはできません。

  • ユーザーを検証するためのセキュリティ・チェックを1つ以上組み込む必要があります。ユーザーを検証する方法の1つは、SYS_CONTEXT SQLファンクションを使用することです。ユーザーに関するセッション情報を検索するために、アプリケーション・コンテキストでSYS_CONTEXTを使用できます。

  • ユーザーがセキュリティ・チェックを通過するときに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を起動できます。

このパッケージまたはプロシージャの作成方法が原因で、セキュア・アプリケーション・ロールを使用可能または使用禁止にする際にログイン・トリガーを使用できません。かわりに、ユーザーがセキュリティ・アプリケーション・ロールで付与された権限を使用する前のユーザー・ログイン時に、アプリケーションからパッケージを直接起動します。

12.9.2.2 アプリケーションに対するアクセス・ポリシーを定義するPL/SQLパッケージまたはプロシージャの作成

作成するPL/SQLパッケージまたはプロシージャでは、アクセス・ポリシーを定義するために実行者権限を使用する必要があります。

たとえば、hr_adminロールを使用する全員を、オンサイト(特定の端末を使用)で午前8時から午後5時まで勤務する従業員に限定するとします。システム管理者またはセキュリティ管理者として、アプリケーションに対してアクセス・ポリシーを定義するプロシージャを作成できます。

  1. 次のようにプロシージャを作成します。
    CREATE OR REPLACE PROCEDURE hr_admin_role_check
     AUTHID CURRENT_USER 
     AS 
     BEGIN 
      IF (SYS_CONTEXT ('userenv','ip_address') 
        IN ('192.0.2.10' , '192.0.2.11')
         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文を発行し、そのユーザーにロールを付与します。

  2. ロールが割り当てられたユーザーに対して、hr_admin_role_checkプロシージャのEXECUTE権限を付与します。

    たとえば:

    GRANT EXECUTE ON hr_admin_role_check TO psmith;
12.9.2.3 セキュア・アプリケーション・ロールのテスト

セキュア・アプリケーション・ロールを付与されたユーザーとして、そのロールで付与される権限を要するアクションを実行します。

セキュア・アプリケーション・ロールを付与されたユーザーとしてログインすると、そのロールが有効になります。

  1. そのロールを付与されたユーザーとして、そのアプリケーション・ロールが作成されたPDBにログインします。

    たとえば:

    CONNECT PSMITH@pdb_name
    Enter password: password
    
  2. セキュア・アプリケーション・ロールで付与される権限を要求するアクションを実行します。

    たとえば、そのロールがsec_admin.hr_admin_role_checkプロシージャに対してEXECUTE権限を付与するとします。

    EXECUTE sec_admin.hr_admin_role_check;
    

12.10 権限とユーザーのデータベース・ロールとの関連付け

ユーザーの権限が、現在のデータベース・ロールに関連した権限のみであることを確認します。

12.10.1 ユーザーの権限が現在のデータベース・ロールのみである理由

1人のユーザーが、多数のアプリケーションおよび多数の対応付けられたロールを使用できます。

ただし、このユーザーの権限は、現在のデータベース・ロールに関連した権限のみであることを確認する必要があります。

次の使用例を考えてみます。

  • (「受注」というアプリケーションの)ORDERロールには、INVENTORY表に対するUPDATE権限が含まれています。

  • (「在庫」というアプリケーションの)INVENTORYロールには、INVENTORY表に対するSELECT権限が含まれています。

  • 何人かの受注入力担当には、ORDERロールとINVENTORYロールの両方が付与されています。

このシナリオでは、両方のロールを付与された受注入力担当がINVENTORYアプリケーションの実行時にORDERロールの権限を使用してINVENTORY表を更新できます。問題は、INVENTORY表の更新は、INVENTORYアプリケーションにとって承認された操作ではないということです。ORDERアプリケーションにとって承認された操作です。この問題を防ぐには、SET ROLE文を次のセクションの説明のとおりに使用します。

12.10.2 ロールを自動的に使用可能または使用禁止にするSET ROLE文の使用

各アプリケーションの開始時にSET ROLE文を使用して、各アプリケーションに対応付けられているロールを自動的に使用可能にし、他のすべてのロールを使用禁止にします。

この方法によって、各アプリケーションでは、必要な場合にのみ、ユーザーの特定の権限を動的に使用可能にします。SET ROLE文は、権限の管理を簡素化します。ユーザーがどのような情報にアクセスできるかと、その情報にいつアクセスできるかを制御します。また、このSET ROLE文によって、ユーザーは、明確に定義された権限ドメイン内で操作を続行できます。あるユーザーがロールからのみ権限を取得している場合、そのユーザーはこれらの権限を組み合せて不正な操作を実行することはできません。

12.11 スキーマを使用したデータベース・オブジェクトの保護

スキーマとは、データベース・オブジェクトを含めることができるセキュリティ・ドメインです。ユーザーおよびロールに付与された権限によって、これらのデータベース・オブジェクトへのアクセスが制御されます。

12.11.1 一意スキーマでのデータベース・オブジェクトの保護

ほとんどのスキーマはユーザー名と考えることができます。つまり、ユーザーがデータベースに接続してそのデータベース・オブジェクトへのアクセスを可能にするアカウントです。

ただし、一意スキーマではデータベースへの接続は許可されませんが、関連する一連のオブジェクトを格納するために使用されます。この種のスキーマは通常ユーザーとして作成されますが、CREATE SESSIONシステム権限は(明示的にもロールを介しても)付与されません。

  • オブジェクトを保護するには、CREATE SCHEMAを使用して、1つのトランザクション内に複数の表とビューを作成する場合は、一意スキーマにCREATE SESSIONおよびRESOURCE権限を一時的に付与します。

たとえば、所定のスキーマが特定のアプリケーションのスキーマ・オブジェクトを所有する場合があります。アプリケーション・ユーザーに権限がある場合、そのユーザーは、一般的なデータベース・ユーザー名を使用してデータベースに接続し、アプリケーションとそれに対応するオブジェクトを使用できます。ただし、ユーザーはアプリケーションに設定されたスキーマを使用して、データベースに接続することはできません。この構成は、対応付けられたオブジェクトへのスキーマを介したアクセスを防ぎ、スキーマ・オブジェクトの保護を強化します。この場合、アプリケーションではALTER SESSION SET CURRENT_SCHEMA文を発行して、ユーザーを適切なアプリケーション・スキーマに接続できます。

12.11.2 共有スキーマでのデータベース・オブジェクトの保護

多くのアプリケーションでは、ユーザーがアクセスする必要があるのはアプリケーション・スキーマのみであるため、データベースで自分自身のアカウントつまりスキーマを必要としません。

たとえば、ユーザーJohn、FiruzehおよびJaneはすべて給与アプリケーションのユーザーで、financeデータベースのpayrollスキーマにアクセスする必要があるとします。この場合、データベースに自分自身のオブジェクトを作成する必要があるユーザーはいません。これらのユーザーに必要なのは、payrollオブジェクトへのアクセスのみです。この問題に対処するために、Oracle Databaseではエンタープライズ・ユーザー(スキーマに依存しないユーザー)が提供されています。

エンタープライズ・ユーザー、つまりディレクトリ・サービスで管理されるユーザーは、共有データベース・スキーマを使用するため、データベース・ユーザーとして作成する必要はありません。管理コストを削減するために、管理者はディレクトリに1つのエンタープライズ・ユーザーを1回作成し、他の多数のユーザーもアクセスできる共有スキーマで、そのユーザーを指し示すことができます。

ノート:

エンタープライズ・ユーザー・セキュリティ(EUS)は、Oracle Database 23cで非推奨になりました。

集中管理ユーザー(CMU)の使用に移行することをお薦めします。この機能を使用すると、データベースに対するエンタープライズ・ユーザー認証および認可のためにディレクトリ・サービスを介在させることなく、Microsoft Active Directoryに直接接続できます。Oracle Databaseがクラウドにある場合は、クラウド・アイデンティティ・プロバイダとの新しい統合のいずれかに移行することも選択できます。

12.12 アプリケーションでのオブジェクト権限

アプリケーションの設計時には、ユーザーのタイプとユーザーに必要なレベル・アクセスについて検討する必要があります。

12.12.1 アプリケーション開発者に必要なオブジェクト権限に関する知識

オブジェクト権限によって、エンド・ユーザーは、表、ビュー、順序、プロシージャ、ファンクション、パッケージなどのオブジェクトに対してアクションを実行できます。

表12-3に、オブジェクトの各タイプで使用できるオブジェクト権限の概要を示します。

表12-3 権限とスキーマ・オブジェクトとの関連

オブジェクト権限 表への適用 ビューへの適用 順序への適用 スタンドアロンのストアド・プロシージャ、ファンクションまたはパブリック・パッケージ構成への適用

ALTER

はい

いいえ

はい

いいえ

DELETE

はい

はい

いいえ

いいえ

EXECUTE

いいえ

いいえ

いいえ

はい

INDEX

はい(ロールに付与できない権限)

いいえ

いいえ

いいえ

INSERT

はい

はい

いいえ

いいえ

REFERENCES

はい(ロールに付与できない権限)

いいえ

いいえ

いいえ

SELECT

はい

はい(スナップショットにも付与可能)

はい

いいえ

UPDATE

はい

はい

いいえ

いいえ

12.12.2 オブジェクト権限によって許可されるSQL文

アプリケーションの実装時およびテスト時には、必要な各ロールを作成する必要があります。

各ロールの使用例をテストし、データベースへのアクセス権がアプリケーション・ユーザーに正しく付与されることを確認します。テスト終了後は、アプリケーションの管理者と共同で各ユーザーに適切なロールが割り当てられていることを確認します。

表12-4に、表12-3で示したオブジェクト権限によって許可されるSQL文を示します。

表12-4 データベース・オブジェクト権限によって許可されるSQL文

オブジェクト権限 許可されるSQL文

ALTER

ALTERオブジェクト(表または順序)

CREATE TRIGGER ONオブジェクト(表のみ)

DELETE

DELETE FROMオブジェクト(表、ビューまたはシノニム)

EXECUTE

EXECUTEオブジェクト(プロシージャまたはファンクション)

パブリック・パッケージ変数への参照

INDEX

CREATE INDEX ONオブジェクト(表、ビューまたはシノニム)

INSERT

INSERT INTOオブジェクト(表、ビューまたはシノニム)

REFERENCES

オブジェクト(表のみ)に対するFOREIGN KEY整合性制約を定義するCREATEまたはALTER TABLE

SELECT

SELECT...FROMオブジェクト(表、ビュー、シノニムまたはスナップショット)

順序を使用するSQL文

12.13 データベース通信のセキュリティを強化するためのパラメータ

プロトコル・エラーによる不正パケットの処理や認証エラーの上限の構成など、パラメータを使用してセキュリティを管理できます。

12.13.1 プロトコル・エラーによってデータベースで受信した不正パケット

SEC_PROTOCOL_ERROR_TRACE_ACTION初期化パラメータで、プロトコル・エラーが発生したときにトレース・ファイルをどのように管理するかを制御します。

サーバーが不正なパケット、順序に誤りがあるパケット、プライベートまたは未使用のリモート・プロシージャ・コールを受信した場合、Oracle Call Interface(OCI)やTwo-Task Common(TTC)などのネットワーキング通信ユーティリティでは、スタック・トレースおよびヒープ・ダンプを格納する大規模なディスク・ファイルを生成できます。

通常、このディスク・ファイルは、非常に大規模になる可能性があります。侵入者は、サーバーに不正なパケットを繰り返し送信し、ディスクあふれやサービス拒否(DOS)攻撃を発生させることで、システムを使用できないようにする可能性があります。認証されていないクライアントが、この種の攻撃を仕掛ける可能性もあります。

これらの攻撃は、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

12.13.2 不正パケット受信後のサーバー実行の制御

SEC_PROTOCOL_ERROR_FURTHER_ACTION初期化パラメータで、サーバーで不正パケットを受信した後のサーバー実行を制御します。

Oracle Databaseは、クライアントまたはサーバー・プロトコルからエラーを検出した後も実行を継続する必要があります。ただし、これによって、ディスクあふれまたはサービス拒否攻撃を発生させる可能性のある不正なパケットを、サーバーがさらに受信する可能性があります。
  • サーバーが悪意のあるクライアントから不正なパケットを受信しているときに、サーバー・プロセスの実行を詳細に制御するには、SEC_PROTOCOL_ERROR_FURTHER_ACTION初期化パラメータを次の値のいずれかに設定します。

    • Continue: サーバーの実行を続行します。ただし、サーバーがさらに攻撃を受ける可能性があることに注意してください。

      たとえば:

      SEC_PROTOCOL_ERROR_FURTHER_ACTION = Continue
      
    • (Delay,m): クライアントをm秒間遅延させます(サーバーが次のリクエストを同じクライアント接続から受け付けるまで)。この設定により悪意のあるクライアントはサーバー・リソースを過剰使用できなくなります。正当なクライアントのパフォーマンスも低下しますが、引き続き機能します。この設定を入力する場合は、カッコで囲みます。

      たとえば:

      SEC_PROTOCOL_ERROR_FURTHER_ACTION = (Delay,3)

      ALTER SYSTEMまたはALTER SESSION SQL文を使用してSEC_PROTOCOL_ERROR_FURTHER_ACTIONを設定している場合は、Delay設定を一重引用符または二重引用符で囲む必要があります。

      ALTER SYSTEM SEC_PROTOCOL_ERROR_FURTHER_ACTION = '(Delay,3)';
    • (Drop,n): クライアント接続は、n個の不正パケットを受信した後に強制的に終了します。この設定を使用すると、トランザクションの損失など、クライアントを犠牲にしてサーバー自体を保護できます。ただし、クライアントは再度接続して、同じ操作を再試行できます。この設定はカッコで囲みます。SEC_PROTOCOL_ERROR_FURTHER_ACTIONのデフォルト値は、(Drop,3)です。

      たとえば:

      SEC_PROTOCOL_ERROR_FURTHER_ACTION = (Drop,10)

      Delay設定と同様に、ALTER SYSTEMまたはALTER SESSIONを使用してこの設定を変更している場合は、Drop設定を一重引用符または二重引用符で囲む必要があります。

12.13.3 認証の最大試行回数の構成

SEC_MAX_FAILED_LOGIN_ATTEMPTS初期化パラメータに設定された認証試行回数を超過すると、確立できなかった接続がデータベースで削除されます。

接続作成の一環として、リスナーはサーバー・プロセスを開始し、そのプロセスをクライアントに付加します。この物理的な接続を使用して、クライアントは接続を認証できます。サーバー・プロセスが開始された後、このサーバー・プロセスに対してクライアントが認証がされます。侵入者は、サーバー・プロセスを起動し、様々なユーザー名とパスワードを使用して認証リクエストを無制限に発行し、データベースへのアクセスを試みます。

アプリケーション接続に対するログイン失敗回数を制限するには、SEC_MAX_FAILED_LOGIN_ATTEMPTS初期化パラメータを設定して、接続に対する認証試行回数を制限します。認証の試行が指定した回数失敗すると、データベース・プロセスは接続を切断し、サーバー・プロセスが終了します。デフォルトでは、SEC_MAX_FAILED_LOGIN_ATTEMPTS3に設定されます。

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

12.13.4 データベース・バージョン・バナーの表示構成

SEC_RETURN_SERVER_RELEASE_BANNER初期化パラメータを使用して、認証中の詳細な製品情報の表示を抑制できます。

クライアント接続(Oracle Call Interfaceクライアントを含む)が認証されてから、詳細な製品バージョン情報にアクセスできるようにする必要があります。侵入者は、データベース・バージョンを使用して、データベース・ソフトウェアに存在するセキュリティの脆弱性に関する情報を検出する可能性があります。

  • 認証されていないクライアントに対してデータベース・バージョン・バナーの表示を制限するには、initsid.ora初期化パラメータ・ファイルでSEC_RETURN_SERVER_RELEASE_BANNER初期化パラメータをTRUEまたはFALSEのいずれかに設定します。

    デフォルトでは、SEC_RETURN_SERVER_RELEASE_BANNERFALSEに設定されます。

たとえば、TRUEに設定すると、Oracle Databaseに正確なデータベース・バージョンが表示されます。たとえば、リリース19.1.0.0の場合は次のようになります。

Oracle Database 19c Enterprise Edition Release 19.1.0.0 - Production

リリース番号にポイント・リリース表記法(Oracle Databaseリリース19.1.0.1など)が使用されている場合、バナーには次のように表示されます。

Oracle Database 19c Enterprise Edition Release 19.1.0.1 - Production

ただし、同じリリースでこのパラメータをNOに設定すると、このバナーは制限されて、リリース19.1で始まる次の固定テキスト(19.1.0.1のかわりに19.1.0.0.0)が表示されます。

Oracle Database 19c Release 19.1.0.0.0 - Production

12.13.5 不正なアクセスおよびユーザー・アクションの監査に関するバナーの構成

SEC_USER_UNAUTHORIZED_ACCESS_BANNERおよびSEC_USER_AUDIT_ACTION_BANNER初期化パラメータで、不正アクセスやユーザーの監査に関するバナーの表示を制御します。

不正なアクセスおよびユーザー・アクション監査をユーザーに警告するには、バナーを作成して構成する必要があります。この通知は、クライアント・アプリケーションがデータベースにログインすると使用可能になります。

  • これらのバナーを構成して表示するには、データベース・サーバー側で次のsqlnet.oraパラメータを設定して、バナー情報が含まれるテキスト・ファイルを指し示します。

    • SEC_USER_UNAUTHORIZED_ACCESS_BANNER。たとえば:

      SEC_USER_UNAUTHORIZED_ACCESS_BANNER = /opt/Oracle/12c/dbs/unauthaccess.txt
      
    • SEC_USER_AUDIT_ACTION_BANNER。たとえば:

      SEC_USER_AUDIT_ACTION_BANNER = /opt/Oracle/12c/dbs/auditactions.txt
      

デフォルトでは、これらのパラメータは設定されません。さらに、バナー・テキストに使用される文字数には512バイトの制限があることを注意してください。

これらのパラメータを設定したら、これらのバナーを取得してエンドユーザーに表示するように、Oracle Call Interfaceアプリケーションで適切なOCI APIを使用する必要があります。