哈囉大家好,這裡是又踩進Wildfly大坑裡面的小編過路君子
Wicket 9.x API真的很過分,就放兩個函式在那裡,然後底下全白,誰知道在做什麼的。
那因為本篇文章高度相關 CSP,還不太了解的人可以參考由其他筆者所撰寫的這篇:Content Security Policy (CSP) — 幫你網站列白名單吧@Hannah Lin。
或是直接生咬 W3C 所提供的完整原文文檔:Content Security Policy Level 3@W3C,這兩篇也是小編所參考的文獻資料。
小編這邊要特別提醒:不是使用了 CSP 就可以高枕無憂,CSP 只是我們眾多安全防線中的第一道而已,就算開啟,我們伺服器後端也要做校驗檔案之類的動作,相關的資訊可以參考這篇:前端防禦從入門到棄坑——CSP變遷@程序猿哪些事。
那至於 Wicket 的 API 也有小小的提及,但是前提是建立在他們認為我們已經具有 CSP 的基本知識了,所以如果完全沒有 CSP 知識點進去會完全看不懂。
關於如何設定 CSP 和 Content-Security-Policy-Report-Only 可以參考這篇:org.apache.wicket.csp.ContentSecurityPolicySettings。
關於 Wicket 有什麼 CSP 可以設定,可以參考這篇:org.apache.wicket.csp.CSPDirective。
小編就是靠上面三篇文檔和硬咬 Wicket 9.x API 才終於搞懂的,不得不說,Wicket 這方面的資料網路上超級缺乏,沒什麼有用的資料可以參考。
其實小編有看到更為簡單粗暴的解法,這個小編下面會提到,簡單來說就是直接關閉 CSP,那樣是可以跑啦,但是一點都不優雅,小編不喜歡,就是喜歡搞自己。
所以才跑去讀了好幾遍 CSP 後好不容易搞懂,然後又要了解 Wicket 如何魔改 CSP,又要如何更訂 Wicket 原始預設值,最後才誕生出這篇筆記的。
這篇文章的主要目的是要記錄 Wicket CSP 該如何設定,那事實上 Wicket 有提供我們在這高強度的 CSP 下依舊可以使用外部 css 和 javascript 的(運用隨機 nonce)。
有興趣知道如何新增 css 或是 javascript 到我們當前頁面(直接使用 HTML 標籤,這兩者都會被 Wicket 預設的 CSP 擋下),可以參考這篇:【Wicket】[nonce]如何導入css以及javascript@小編過路君子 。
那在開始之前照慣例附上目錄結構:
(所有圖片點擊都可以放大、變高清)
當我們今天部屬好 Wicket 時,很高興地開始撰寫我們的網頁,那既然是網頁,想必 CSS 和 JavaScript 是不可少的吧!
所以我們就很高興的像平常一樣使用了 <link href="..." /> 或 <script scr="...">,結果竟然被 CSP 擋下來了......
然後仔細看了一下檔頭:
好傢伙,竟然 Wicket 預設連同源的 CSS 檔案也會拒絕掉。
不僅如此連 inline 和 <style type="text/css"></style> 也擋掉了,總之只要是 css 一律無效。
那各位可能會看到網路上其他人的解法是使用 <wicket:head></wicket:head> 來解決行內 css 或是 js,但對於外部檔案一樣無解。
那接下來我們就要開始對 CSP 動刀了,接下來的所有程式都是寫在 webRoute.java 內,index.html 以及 index.java 等等檔案都不需要做修正。
第一步當然就是要來取得我們整個網站的 CSP 設定了,Wicket 的 CSP 是以一個網站作為單位的,全站統一用一個 CSP。
那為了達成這個任務,我們首先要到我們的 WebApplication 下,以小編的例子來說是 webRoute.java 這個檔案,使用以下函式:
public class webRoute extends WebApplication { @Override public void init() { getCspSettings(); } }
就這樣,但如果各位有偷偷看過 Wicket 9.x API 的話,應該不難發現我們後面還要接一個函式,根據 API 的解釋:
沒了。
真的是有夠搞笑,至少這樣寫一下吧!
blocking() | reporting() |
設定 Content-Security-Policy | 設定 Content-Security-Policy-Report-Only |
那 Wicket 預設不啟用 Content-Security-Policy-Report-Only,如果各位之後有自行啟用的話......記得要設定 report-to,要不然網頁會一直送出警告,就算沒有錯誤也一樣,很討厭。
其中 Wicket 對於這兩項的預設值如下,其中 nonce 後面接的數值每次頁面載入都會重新產生,所以這邊僅寫 nonce:
Content-Security-Policy(CSP) | Content-Security-Policy-Report-Only | |
default-src | none | - |
script-src | nonce strict-dynamic |
- |
style-src | nonce | - |
img-src | self | - |
connect-src | self | - |
font-src | self | - |
anifest-src | self | - |
child-src | self | - |
frame-src | self | - |
base-uri | self | - |
report-to | - | - |
表註:可能會有人發現,若真的有設定 report-to,實際去抓取檔頭的話會看到 Wicket 用的是 report-uri,那因為 report-uri 即將被淘汰,所以小編評估之後還是決定寫 report-to。
所以如果我們今天要設定 Content-Security-Policy 要這樣寫:
public class webRoute extends WebApplication { @Override public void init() { getCspSettings().blocking(); } }
反之,如果只是要設定 Content-Security-Policy-Report-Only 要這樣寫:
public class webRoute extends WebApplication { @Override public void init() { getCspSettings().reporting(); } }
那如果兩個都要設定就都寫,簡單吧!
接下來就是重頭戲了,我們要開始添加、移除或是關閉 CSP 規則了。
那 Wicket 官方也很好心的提供了四個快速建構式給我們,分別是:
strict() | disabled() | clear() | unsafeInline() | |
default-src | none | - | - | none |
script-src | nonce strict-dynamic |
- | - | self unsafe-eval unsafe-inline |
style-src | nonce | - | - | self unsafe-inline |
img-src | self | - | - | self |
connect-src | self | - | - | self |
font-src | self | - | - | self |
anifest-src | self | - | - | self |
child-src | self | - | - | self |
frame-src | self | - | - | self |
base-uri | self | - | - | self |
report-to | - | - | - | - |
其中的 strict() 設定有沒有眼熟呢?對,我們的 Wicket 預設值就是 strict()。
這也是為什麼現在看到網路上的解法有些人就叫你直接下:
public class webRoute extends WebApplication { @Override public void init() { getCspSettings().blocking().disabled(); } }
然後我們照著下,唉!可以跑了,我的資源正常載入了!
看了上表之後我們就可以理解了,因為我們直接把 CSP 關掉了嘛!俗話說的好,解決不了問題就解決提出問題的人。
所以其實下 getCspSettings().blocking().clear(); 也有同樣的效果。
那關於設定 SCP 的方式有以下四種:
add() | remove() | reportBackAt() | setAddLegacyHeaders() |
添加 SCP 指定項狀態 | 完全移除 SCP 指定項設定 | 設定 report-to 的網址路徑 | 是否啟用 Legacy 來相容其他瀏覽器,像是 IE |
那可以設定的 SCP 總共有以下 16 種。
這邊要注意的是:要先 import org.apache.wicket.csp.CSPDirective; 才能使用喔。
Wicket CSP 屬性 | 對應的 CSP 值 |
BASE_URI | base-uri |
CHILD_SRC | child-src |
CONNECT_SRC | connect-src |
DEFAULT_SRC | default-src |
FONT_SRC | font-src |
FRAME_ANCESTORS | frame-ancestors |
FRAME_SRC | frame-src |
IMG_SRC | img-src |
MANIFEST_SRC | manifest-src |
MEDIA_SRC | media-src |
OBJECT_SRC | object-src |
REPORT_URI | report-uri |
SANDBOX | sandbox |
SCRIPT_SRC | script-src |
STYLE_SRC | style-src |
WORKER_SRC | worker-src |
那對每個設定可以添加的值有以下七種。
這邊也要注意的是:要先 import org.apache.wicket.csp.CSPDirectiveSrcValue; 才能使用喔。
Wicket CSP 值 | 對應的 CSP 值 |
NONCE | nonce-xxx |
NONE | none |
SELF | self |
STRICT_DYNAMIC | strict-dynamic |
UNSAFE_EVAL | unsafe-eval |
UNSAFE_INLINE | unsafe-inline |
WILDCARD | * |
表註:nonce後面所接的值 Wicket 會自動幫我們產生,我要只要填入 NONCE 即可。
那我們一個一個來看,先從最簡單的 setAddLegacyHeaders() 來看好了,我們先看看關於此函式的介紹:
由上圖我們可以知道,此函數接受一個 boolean 值,而且預設為關閉。
所以如果我們今天要打開此項設定,應該要這樣下:
@Override public void init() { getCspSettings().blocking() .setAddLegacyHeaders(true); }
當我們重新載入頁面之後就可以看到我們的檔頭裡多了 X-Content-Security-Policy 的屬性。
而且相關的設定跟我們目前的 Content-Security-Policy 一模一樣喔。
第二個小編緊接著要來介紹的是 reportBackAt(),一樣先來看看有關此函式的說明:
由上圖的函式說明我們可以得知,此函數接受一個字串,而這個字串就是要瀏覽器將使用者違反相關的 CSP 資訊傳送到我們所指定的地方。
所以如果小編想要瀏覽器將相關資訊傳送到 http://192.168.88.128:8080/CSPwarning,那我們應該要這樣下指令:
public class webRoute extends WebApplication { @Override public void init() { getCspSettings().blocking() .reportBackAt("http://192.168.88.128:8080/CSPwarning"); } }
然後我們再次重新載入網頁。
瞧~現在我們的檔頭的 Content-Security-Policy 這欄是不是出現了剛剛一直都沒有看到的 report-uri 呢?
那各位啟用前要對於處理這些訊息做一些規範,包括短時間內大量的 CSP 警告接收,如果對方真的有心,那這也是一種 DDoS 攻擊。
那接下來要來介紹的就是 remove() 囉,照慣例我們先來一起看 API 對於此函式給出的說明吧~
根據上圖我們可以得知,只要將我們想要移除項目填入,就可以成功將其移除掉囉。
import org.apache.wicket.csp.CSPDirective; public class webRoute extends WebApplication { @Override public void init() { getCspSettings().blocking() .remove(CSPDirective.STYLE_SRC) //移除 style-src .remove(CSPDirective.SCRIPT_SRC); //移除 script-src } }
這邊小編示範移除原預設 CSP 裡的 style-src 和 script-src 屬性,若要移除其他屬性可以參照上表,換成要移除的屬性即可。
那根據 Wicket 9.x API 上所寫的,小編沒有找到只移除某一屬性某個值的函式,所以目前要刪除屬性的某一值的話......只能全部移除再添加回來其他不須刪除的。
最後一個函數!沒錯,終於來到了最後一個函式 add() 了。
就像剛剛一樣,來吧。
那不同於 remove() 是移除整個屬性,每次 add() 都只會增加某一屬性的指定值。
import org.apache.wicket.csp.CSPDirective; import org.apache.wicket.csp.CSPDirectiveSrcValue; public class webRoute extends WebApplication { @Override public void init() { getCspSettings().blocking() /* 在 font-src 裡添加 nonce-xxx */ .add(CSPDirective.FONT_SRC, CSPDirectiveSrcValue.NONCE) /* 在 style-uri 裡添加 self */ .add(CSPDirective.REPORT_URI, CSPDirectiveSrcValue.SELF); } }
所以我們在使用的時候就要告訴 Wicket 我們要設定什麼屬性的什麼值對不對~
那這就是 Wicket 的 CSP 全操作了,其中小編有省略掉解釋 CSP 屬性所代表的意義,像是 STYLE_SRC 是用來限定可執行 Css 來源或是 SCRIPT_SRC 是用來限定可執行 JavaScript 來源等等。
那其實 Wicket 已經有內建導入外部 Script 或是 Css 的方法了,各位想到了嗎?那小編這邊也不賣關子了,簡單來說就是使用 nonce 啦!
相關的資訊可以參考小編的這篇文章:【Wicket】[nonce]如何導入css以及javascript@小編過路君子。
後記
其實當下小編一得知是因為 CSP 才會造成資源無法加載,就跑去找其他人是怎麼解決這個問題,發現絕大部份就是教你直接關閉 CSP,然後原發問者就很高興的在下面回覆:「它有用,開始運作了!」小編看到真的是額頭三條線冒出來......相當的不以為意,小編就不信邪小編不能在不更動 Wicket 預設 CSP 的狀況下使用小編想要的資源。
然後花了兩天去研讀 CSP,然後又花了一天去了解要怎麼使用 Wicket 來全面控制 CSP,最後又花了一天來寫這篇文;那你問小編最後有沒有解決問題,當然有!就是使用 nonce,而且除了同源之外還可以加載外部的資源檔案喔!
下圖的 nonce 是 Wicket 自己產生的,所以每次載入頁面所顯示的值都不一樣,詳細的步驟小編寫在了此文章末的那篇文章了。