2025年1月20日月曜日

Oracle APEX 24.2で強化されたContent-Security-Policyの対応について

Oracle APEX 24.2の新機能の紹介ページでは下から4番目の扱いですが、24.2ではContent-Security-Policy(CSP)に関する機能強化が行われています。Oracle APEX 24.2のRelease Notesでは以下のように説明されています。

2.23 Improved Content-Security-Policy Support

The APEX runtime engine adheres more strictly to Content-Security-Policy (CSP) standards by minimizing the use of unsafe-inline directives:
  • Inline JavaScript: Inline event handlers are moved to external JavaScript files wherever possible. When not feasible, they are securely emitted using a unique identifier called a nonce.
  • Inline CSS: Inline styles are now either moved to external CSS files, replaced with placeholder strings, or securely emitted using a nonce.
  • Style Tags: <style> tags are securely emitted using a nonce.
  • Script Tags: <script> tags are securely emitted using a nonce.
These changes enhance security by reducing risks associated with inline code.
Oracle APEX 24.2では可能な限り、CSSによるスタイルの記述やJavaScriptのコードを外部ファイルに移し、それができない場合は、Oracle APEXが生成する<style><script>タグにnonce属性を付与します。Oracle APEXの標準機能を使って埋め込んだコードについてはnonceを指定することで実行が許可されますが、Cotent-Security-Policyヘッダーへのunsafe-XXXの指定がすべて不要になる、といったものではありません。APEXのアプリケーションに組み込んだ3rdパーティのJavaScriptライブラリなど、個別の対応が必要なケースがあります。

以下より、Oracle APEX 24.2でのContent-Security-Policyの設定方法について、APEXのアプリケーションを例にとって説明します。セキュリティ・ポリシーについての説明ではCSPと略しますが、HTTPヘッダーを指す場合はContent-Security-Policyと略さずに表記します。

APEX 24.2で空のAPEXアプリケーションし、ホーム・ページにCSPの機能強化を確認するための処理を記述します。

ページ・プロパティJavaScriptファンクションおよびグローバル変数の宣言に以下を記述します。

var checkCSPnonce = 'inline javascript - global def';

ページ・ロード時に実行に以下を記述します。

checkCSPnonce = 'inline javascript - page load';

CSSインラインに以下を記述します。

.checkCSPnonce {
    width: 40;
    height: 80;
}

ページのロードで実行する動的アクションを作成し、TRUEアクションとしてJavaScriptコードの実行を作成します。設定コードとして以下を記述します。

checkCSPnonce = 'inline javascript - dynamic action';


これらのJavaScriptのコードやCSSのスタイルは、生成されるページに埋め込まれます。

APEX 24.1では、ファンクションおよびグローバル変数の宣言は、以下のようにページに埋め込まれます。

<script type="text/javascript">
var checkCSPnonce = 'inline javascript - global def';
</script>

APEX 24.2ではnonce属性が付加されます。nonce属性の値は、ページが生成される毎に異なります。

<script nonce="ilP0-7uxgcoLp7o5bVwn1w">
var checkCSPnonce = 'inline javascript - global def';
</script>


ページ・ロード時に実行動的アクションに記述したJavaScriptも同様に、script要素にnonce属性が付加されます。

CSSインラインは、APEX 24.1では以下のようにページに埋め込まれます。

<style type="text/css">
.checkCSPnonce {
    width: 40;
    height: 80;
}
</style>


APEX 24.2では、style要素にnonce属性が付加されます。

<style nonce="ilP0-7uxgcoLp7o5bVwn1w" >
.checkCSPnonce {
    width: 40;
    height: 80;
}
</style>

24.2では上記のようにscript要素とstyle要素にnonce属性が付加されます。このnonce属性を使ってアプリケーションを保護するために、それぞれのAPEXアプリケーションが送信するHTTPレスポンス・ヘッダーとしてContent-Security-Policyを明示的に設定します。

HTTPレスポンス・ヘッダーは、アプリケーション定義セキュリティブラウザ・セキュリティのセクションに含まれています。


CSPの設定にあたって、Content-Security-Policyヘッダーを設定する前に、Content-Security-Policy-Report-Onlyヘッダーを設定することが推奨されています。アプリケーションの動作に影響を与えることなく、CSPのポリシーが適切かどうかを確認できます。

CSPのポリシー違反のレポート先にできるサーバーがすでにある場合は別ですが、とりあえずCSPのポリシー違反を受信するサーバーが必要です。そのため、Oracle REST Data Servicesを使って簡単なサーバーを作成します。

ポリシー違反のレポートを保存する表としてEBAJ_CSP_REPORTSを作成します。以下のDDLを実行します。
create table ebaj_csp_reports (
    id         number generated by default on null as identity
               constraint ebaj_csp_reports_id_pk primary key,
    report     clob check (report is json),
    created    date default on null sysdate
);
以下は、SQLワークショップSQLコマンドで実行した例です。


SQLワークショップRESTfulサービスを開き、モジュールとしてCSPを作成します。ベース・パス/csp/とします。公開オンです。


作成したモジュールCSPに、URIテンプレートとしてreportを作成します。


URIテンプレートreportに、リソース・ハンドラを作成します。メソッドPOSTソース・タイプPL/SQL使用可能なMIMEタイプとして、application/csp-report,application/jsonを設定します。

ソースに以下を記述します。POSTリクエストで受信したJSONドキュメントを、表EBAJ_CSP_REPORTSの列REPORTに保存します。
begin
    insert into ebaj_csp_reports(report) values(:body);
end;
完全なURLは、Content-Security-Policy-Report-Onlyヘッダーのreport-uriに指定するURLです。後で設定するので、メモをしておきます。


以上でCSPのポリシー違反を受信するサーバーは完成です。

続いて、表EBAJ_CSP_REPORTSの内容を表示するAPEXアプリケーションを作成します。

アプリケーション作成ウィザードを起動します。アプリケーションの名前CSP Reportとします。


デフォルトで作成されているホーム・ページを削除し、代わりに対話モード・レポートのページを追加します。

ページの追加をクリックし、対話モード・レポートを選択します。ページ名Policy Violationsとし、SQL問合せ対話モード・レポートを選択します。

ソースとなるSELECT文として、以下を記述します。
SELECT
  jd.id,
  jt.document_uri,
  jt.referrer,
  jt.violated_directive,
  jt.effective_directive,
  jt.original_policy,
  jt.disposition,
  jt.blocked_uri,
  jt.line_number,
  jt.column_number,
  jt.source_file,
  jt.status_code,
  jt.script_sample,
  jd.created
FROM ebaj_csp_reports jd,
     JSON_TABLE(
       jd.report,
       '$."csp-report"'
       COLUMNS (
         document_uri VARCHAR2(200) PATH '$."document-uri"',
         referrer VARCHAR2(200) PATH '$."referrer"',
         violated_directive VARCHAR2(200) PATH '$."violated-directive"',
         effective_directive VARCHAR2(200) PATH '$."effective-directive"',
         original_policy VARCHAR2(500) PATH '$."original-policy"',
         disposition VARCHAR2(50) PATH '$."disposition"',
         blocked_uri VARCHAR2(200) PATH '$."blocked-uri"',
         line_number NUMBER PATH '$."line-number"',
         column_number NUMBER PATH '$."column-number"',
         source_file VARCHAR2(200) PATH '$."source-file"',
         status_code NUMBER PATH '$."status-code"',
         script_sample VARCHAR2(500) PATH '$."script-sample"'
       )
     ) jt;
以上で、ページの追加をクリックします。


アプリケーションの作成をクリックします。


以上でCSPのポリシー違反をレポートするアプリケーションができました。

この状態でもCSPのポリシー違反は確認できますが、CSPの設定を容易にするために、表EBAJ_CSP_REPORTS表を全削除するボタンRESETを作成します。

ブレッドクラムのリージョンCSP ReportにボタンRESETを作成します。スロットNextを選択し、リージョンの右端に配置します。動作アクションはデフォルトのページの送信です。


表EBAJ_CSP_REPORTSの内容を全削除するプロセスを作成します。

識別名前RESETとし、タイプコードを実行を選択します。ソースPL/SQLコードとして以下を記述します。

delete from ebaj_csp_reports;

サーバー側の条件ボタン押下時RESETを選択します。


以上でCSPのポリシー違反をレポート表示するアプリケーションは完成です。アプリケーションを実行すると以下のように表示されます。今の所、データが投入されていないため、対話モード・レポートには何も表示されません。


Content-Security-Policyヘッダーを設定するAPEXアプリケーションを作成します。

アプリケーション作成ウィザードを起動します。アプリケーションの名前CSP Testとします。


ファセット検索のページを追加します。

ファセット検索ソースとして、サンプル・データセットEMP/DEPTに含まれる表EMPを使用します。

ページ名EMPとし、表示形式レポートを選択します。としてEMPを選択し、フォームを含めるにチェックを入れます。

以上でページの追加をクリックします。


以上でアプリケーションを作成する準備ができました。

アプリケーションの作成をクリックします。


アプリケーションが作成されました。このアプリケーションにCSPのポリシーを設定します。


作成したアプリケーションにサインインし、ファセット検索のページが操作できることを確認します。


最初にContent-Security-Policy-Report-Onlyではなく、Content-Security-Policyとして以下を設定してみます。report-uriは先ほど作成したRESTfulサービスの完全なURLを指定します。許可するソースとして'self'以外にhttps://static.oracle.comを含めています。これは試験に使用しているAPEX環境にて、Oracle APEXの静的リソース・ファイルをCDNより参照しているために追加しています。ローカルのORDSから静的リソース・ファイルを参照している場合は不要です。

nonceをポリシーに含めていないと、どのようなポリシー違反が検出されるか、まずは確認してみます。
Content-Security-Policy: default-src 'self' https://static.oracle.com; report-uri http://localhost:8181/ords/apexdev/csp/report;

ファセット検索のページは以下のように表示されます。ファセットについては全く表示されず、ほとんど利用できない状態です。本番環境へのCSPの設定は注意して行う必要があります。


アプリケーションCSP Reportより、報告されたポリシー違反を確認します。RESTfulサービスが適切に作成できていて、report-uriへのURLの設定に間違いがなければ、ポリシー違反の一覧が表示されます。

これまでの作業に間違いがないことが確認できたので、ボタンResetをクリックしてポリシー違反をすべて削除します。


Content-Security-Policyヘッダーを変更します。

実行されているアプリケーションCSP Testの開発者ツールバーは、CSPのポリシーによりインラインのJavaScriptの実行が禁止されているため、操作できません。開発者ツールバーからアプリケーション・ビルダーを開くことはできないため、アプリケーション・ビルダーが開いているウィンドウを見つけて、そのウィンドウで作業を行います。


HTTPレスポンス・ヘッダーの設定を以下に変更します。置換文字列#APEX_CSP_NONCE#は、'nonce-[noncevalue]'に置き換えられます。結果として、nonce属性のあるscript要素やstyle要素による処理が許可されます。
Content-Security-Policy-Report-Only: default-src 'self' https://static.oracle.com #APEX_CSP_NONCE#; report-uri http://localhost:8181/ords/apexdev/csp/report;

ファセット検索のページをリロードします。

今回設定したヘッダーはContent-Security-Policy-Report-Onlyなので、ポリシー違反はレポートされますが、アプリケーションの動作には影響ありません。


レポートされたポリシー違反を確認します。style-srcimg-srcに関するポリシー違反が検出されています。ポリシーとしてはdefault-srcを指定していますが、default-srcに含まれるscript-srcについてはポリシー違反がないことより、JavaScriptに関しては問題なさそうです。

JavaScriptにしてもCSSにしても、カスタムでコードを追加したのではなく(カスタムのコードは、コード側で対応すべき)、Oracle APEXのウィザードが作成したアプリケーションでのポリシー違反なので、CSPのポリシーとしてこれらの違反が検出されないように、例外を設定する必要があります。

現時点では全体で9件のポリシー違反が報告されています。


Oracle APEXでは、できるだけ安全にスタイルを設定する方法として、置換文字列APEX_CSP_DISPLAY_NONEを提供しています。テンプレートに記述した#APEX_CSP_DISPLAY_NONE#は、HTMLページではstyle=display:none;に置換されます。しかし、これはインラインのstyle指定であるため、CSPのポリシー違反が発生します。

#APEX_CSP_DISPLAY_NONE#として置換されるstyle属性を許可するハッシュ値が、置換文字列#APEX_CSP_HASHES#として提供されています。この置換文字列を使って、CSPの設定を以下に変更します。
Content-Security-Policy-Report-Only: default-src 'self' https://static.oracle.com #APEX_CSP_NONCE# ; style-src 'self' https://static.oracle.com #APEX_CSP_NONCE# 'unsafe-hashes' #APEX_CSP_HASHES#; report-uri http://localhost:8181/ords/apexdev/csp/report;
先ほどと同様の手順で、レポートされたCSPのポリシー違反を確認します。総数としては4件になりました。Oracle APEXでのスタイルに関するポリシー違反は検出されなくなりましたが、Oracle JETやjQueryのライブラリでポリシー違反が検出されています。

Oracle APEXではアプリケーションがデバッグ・モードかどうかで、ロードするJavaScriptやCSSのファイルが変わります。デバッグ・モードの場合は、ミニファイルされていないファイルがロードされるため、CSPのポリシー違反のSource File、Line NumberやColumn Numberが、デバッグ・モードではないときと異なります。


こちらのポリシー違反を解除するために、unsafe-hashesとして設定が必要なハッシュ値を見つけます。ポリシー違反が報告されているページでJavaScriptコンソールを開きます。


コンソールに以下のようなメッセージが表示されています。追加すべきハッシュ値がメッセージに含まれているので、これを取り出します。
[Report Only] Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self' https://static.oracle.com 'nonce--WnKzTcD7a2EokGyTHGy2Q' 'unsafe-hashes' 'sha256-0EZqoz+oBhx7gF4nvY2bSqoGyy4zLjNF+SDQXGp/ZrY='". Either the 'unsafe-inline' keyword, a hash ('sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='), or a nonce ('nonce-...') is required to enable inline execution.
今回は3つのハッシュ値が見つかりました。style-srcに見つけたハッシュ値を追加し、CSPのヘッダーを以下に変更します。
Content-Security-Policy-Report-Only: default-src 'self' https://static.oracle.com #APEX_CSP_NONCE# ; style-src 'self' https://static.oracle.com #APEX_CSP_NONCE# 'unsafe-hashes' #APEX_CSP_HASHES# 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' 'sha256-VxjHmcXP0U9Zgkr9GhUG4Vgds8G3sZ6YZkLx6BDRXHk=' 'sha256-ZdHxw9eWtnxUb3mk6tBS+gIiVUPE3pGM470keHPDFlE='; report-uri http://localhost:8181/ords/apexdev/csp/report;
ファセット検索のページを開き、ポリシー違反の確認します。img-srcの違反が残っています。


img-src
の違反はインラインのデータが指定されているためです。これをポリシー違反の検出対象から外すため、img-srcdata:を指定します。

CSPのポリシーとして以下を設定します。
Content-Security-Policy-Report-Only: default-src 'self' https://static.oracle.com #APEX_CSP_NONCE# ; style-src 'self' https://static.oracle.com #APEX_CSP_NONCE# 'unsafe-hashes' #APEX_CSP_HASHES# 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' 'sha256-VxjHmcXP0U9Zgkr9GhUG4Vgds8G3sZ6YZkLx6BDRXHk=' 'sha256-ZdHxw9eWtnxUb3mk6tBS+gIiVUPE3pGM470keHPDFlE='; img-src 'self' https://static.oracle.com data:; report-uri http://localhost:8181/ords/apexdev/csp/report; 
以上の設定で、ポリシー違反の検出がなくなりました。


CSPのポリシー違反が検出されなくはなりましたが、あくまで今回テストに使用したAPEXアプリケーションCSP Testに限定されます。それぞれのアプリケーション毎にテストを行い、設定を吟味する必要があるでしょう。

現時点ではContent-Security-Policyヘッダーを設定するのは危険で、Content-Security-Policy-Report-Onlyヘッダーを設定して、十分に検証する必要があるように思います。

他にいくつか、CSPに関するトピックがあります。

もし、すでに静的コンテンツにインラインで記述しているscript要素がある場合、そのscript要素はOracle APEXが生成したものではないため、nonce属性が自動では付加されません。

そのような場合、置換文字列#APEX_CSP_NONCE_VALUE#を使って、以下のようにnonce属性を付加します。

<div id="test"></div>
<script nonce="#APEX_CSP_NONCE_VALUE#">
    alert('javascript is called');
</script>

置換文字列#APEX_CSP_NONCE#'nonce-[noncevalue]'という形式の値に置換されますが、#APEX_CSP_NONCE_VALUE#[noncevalue]に置換されるため、nonce属性へ設定する適切な値になります。

また、リリース・ノートの3.6 Changes to Modal Dialog Page URLsに説明されていますが、ターゲットがモーダル・ダイアログのページの場合、APEX_PAGE.GET_URLが返すURLが変更されています。

CSP Testのアプリケーションで、p_pageとしてモーダル・ダイアログのページ番号3を指定してURLを取得します。
    l_target := apex_page.get_url(
        p_page => 3
    );
APEX 24.2以前では、この戻り値は以下のようにapex.theme42.dialogというJavaScriptのファンクションを呼び出すコードが生成されていました。

javascript:apex.theme42.dialog('\u002Fords\u002Fr\u002Fapexdev\u002Fcsp-tester\u002Femployee?session=124985650933552\u0026dialogCs=38JuwWFJlVGYG3HU7OB3eLS0morc5lKFUVXvpgHkuJCq7PDgwkaTcx1xdvB2Gj8vLFJE630MxoKk1xGKQjpv8A',{title:'Employee',w:'720',mxw:'960',modal:true,dialog:null,dlgCls:'t-Drawer-page--standard '+''},' js-dialog-class-t-Drawer--pullOutEnd','#R134762641471363919')

APEX 24.2からは、以下のようなAPEXアクションの呼び出しに変わります。

#action$a-dialog-open?url=%2Fords%2Fr%2Fapexdev%2Fcsp-test%2Femployee%3Fsession%3D471202422674%26dialogCs%3DJxE7CTsKDk8vxbCn-iAyaZIlifbK4MLkG-T-tJg_J3Ree4uqFAUeVKDXgaV0NrhmEaVv4J5q6nWo6PY5qVjQQA&appId=104&pageId=3&tmpl=DRAWER&title=Employee&h=auto&w=720&mxw=960&isModal=true&dlgCls=&pageCls=%20js-dialog-class-t-Drawer--pullOutEnd&trgEl=%23R12287797887271218

そのため、A要素やBUTTON要素のonclick属性の代わりに、A要素であればhref属性、BUTTON要素であれば、カスタム属性data-actionを使います。また、24.2以前では可能だった、evalでの評価、実行もできません。

実装例を紹介します。ホーム・ページに非表示のページ・アイテムP1_DIALOG_URLを作成します。


ページ・アイテムP1_DIALOG_URL計算を作成します。実行ポイントヘッダーの前を選択し、計算PL/SQLファンクション本体として、以下のコードを記述します。今回のテストで使用しているAPEXアプリケーションのページ番号は、表EMPの編集を行うダイアログのページなので、ダイアログが開くURLがP1_DIALOG_URLに設定されます。
begin
    return apex_page.get_url(
        p_page => 3
    );
end;

タイプ静的コンテンツのリージョンを作成します。以下のスクリプトをインラインで記述して、ページ・アイテムP1_DIALOG_URLの値をリージョンに表示します。script要素のnonce属性として置換文字列#APEX_CSP_NONCE_VALUE#が設定されているため、このインライン・スクリプトの実行は許可されます。
<div id="test"></div>
<script type="module" nonce="#APEX_CSP_NONCE_VALUE#">
    const el = document.getElementById("test");
    el.textContent = apex.item("P1_DIALOG_URL").getValue();
</script>

リージョンには以下のように表示されます。


このURLで指定されているダイアログを開く実装として、いくつかの方法があります。

1つ目:ボタンのカスタム属性として、data-actionを設定します。

data-action="&P1_DIALOG_URL."


2つ目:ボタンのアクションとしてURLにリダイレクトを選択し、ターゲット&P1_DIALOG_URL.を指定します。


3つ目:APEXのJavaScript APIの、apex.navigation.dialogを呼び出します。
apex.navigation.dialog(
    apex.items.P1_DIALOG_URL.value, {}
);

4つ目:APEXのJavaScript APIの、apex.navigation.redirectを呼び出します。
apex.navigation.redirect(
    apex.items.P1_DIALOG_URL.value
);

通常は宣言的な設定を優先し、JavaScript APIは宣言的な設定で実装できない場合に使用します。

追記ですが、Oracle APEXの管理サービスのインスタンス管理でも、HTTPレスポンス・ヘッダーを設定できます。インスタンス管理セキュリティHTTPプロトコルのセクションに含まれています。

この場合、管理ツールや開発ツールも含むすべてのアプリケーションに適用されるため、設定を間違うとすべてのツールが使えなくなります。間違って設定したHTTPレスポンス・ヘッダーの変更もできません。


設定をクリアするには、DBA権限を持つユーザーでデータベースに接続し、apex_instance_admin.set_parameterを呼び出します。
begin
    apex_instance_admin.set_parameter('HTTP_RESPONSE_HEADERS','');
end;
/
commit;
CSPはアプリケーション毎の調整が必要であるため、インスタンス全体にContent-Security-Policyヘッダーを設定するのは無理があります。

今回作成したContent-Security-Policyの違反をレポートするアプリのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/csp_report.zip

今回の記事は以上になります。

Oracle APEXのアプリケーション作成の参考になれば幸いです。