Oracle APEX 24.2では可能な限り、CSSによるスタイルの記述やJavaScriptのコードを外部ファイルに移し、それができない場合は、Oracle APEXが生成する<style>や<script>タグにnonce属性を付与します。Oracle APEXの標準機能を使って埋め込んだコードについてはnonceを指定することで実行が許可されますが、Cotent-Security-Policyヘッダーへのunsafe-XXXの指定がすべて不要になる、といったものではありません。APEXのアプリケーションに組み込んだ3rdパーティのJavaScriptライブラリなど、個別の対応が必要なケースがあります。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:These changes enhance security by reducing risks associated with inline code.
- 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.
以下より、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コマンドで実行した例です。
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をクリックしてポリシー違反をすべて削除します。
実行されているアプリケーション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なので、ポリシー違反はレポートされますが、アプリケーションの動作には影響ありません。
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が、デバッグ・モードではないときと異なります。
コンソールに以下のようなメッセージが表示されています。追加すべきハッシュ値がメッセージに含まれているので、これを取り出します。
[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の違反が残っています。
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
実装例を紹介します。ホーム・ページに非表示のページ・アイテムP1_DIALOG_URLを作成します。
ページ・アイテムP1_DIALOG_URLに計算を作成します。実行のポイントにヘッダーの前を選択し、計算のPL/SQLファンクション本体として、以下のコードを記述します。今回のテストで使用しているAPEXアプリケーションのページ番号3は、表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, {}
);
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のアプリケーション作成の参考になれば幸いです。
完