Content Security Policy (CSP) 內容安全政策
Content Security Policy是寫給瀏覽器看的
他寫在從伺服端回應給使用者瀏覽器端網頁的HTTP Header裡
主要用來限制網頁中對外部的請求來源(例如:css,js(ajax,ws),webfont,img,video,iframe等等)
還有一部份是禁止HTML行內的JS或CSS運作
以及限制<form>
表單的指向網址
過濾XSS語法是伺服器端語言的工作(例如PHP)
如果不幸過濾失敗的話CSP的功能可以阻止惡意語法在瀏覽器端運作
算是多一道擋XSS的防線
(要注意是規則要寫對才能起作用,寫法限制太寬鬆還是會阻止不了)
另外也要注意CSP白名單列入外部JS library CDN可能造成的風險
除了要用SRI檢查內容完整性避免被竄改之外
還要注意這些library的安全性問題
因為這些CDN含有大量開源的library,而且新舊版同時存在,若語法有安全問題則可能會被XSS語法引用來攻擊
這篇只是挑重點寫的筆記,建議有看到任何不懂的東西就去google更詳細的資料來讀,
還有至少要把MDN的資料讀過
然後CSP也一直有在更新增加新語法,也建議常去關注新版本動態
Content-Security-Policy - HTTP | MDN
更進階的話就是去讀CSP可能會被繞過的手法,來減少自己寫出的CSP語法產生漏洞的可能性(像是上面的例子)
Security
, HTTP Header
, Content Security Policy
, CSP
content-security-policy: default-src 'none';
content-security-policy: default-src 'none'; script-src 'self' https://ajax.googleapis.com;
建議可以先用瀏覽器的開發工具(F12)去看看Facebook和Twitter的content-security-policy
寫法
1. Chrome瀏覽器開Twitter
2. F12 --> Network --> F5重新載入網頁 -->
3. 拉到最上面選取第一個載入的檔案(主網頁檔) -->
4. Headers --> Response Headers --> content-security-policy
有實測一下default-src 'none';
無法阻止Tampermomkey這類瀏覽器插件的腳本
(以前運作方式不同似乎可以用CSP擋,但是現在實測過是無法阻擋了)
也有一些瀏覽器插件可以直接停用CSP
所以CSP的功能應該沒辦法拿來阻擋想用腳本修改網頁的人
*根據MDN的資料說CSP HTTP Header語法如果重複出現,那麼效果會疊加並且採取最嚴格的設定(不過各家瀏覽器或是伺服器有沒有正確支援還是建議實測過比較安全)
HTTP Header是以換行當成區格,
要注意所有CSP內容要輸出成同一行字串
content-security-policy: default-src 'none'; img-src 'self' data:;
<IfModule mod_headers.c>
Header set Content-Security-Policy " \
default-src 'none'; \
img-src 'self' data:; \
"
</IfModule>
如果子目錄要移除上層目錄的CSP規則可以用Header unset
移除
<IfModule mod_headers.c>
Header unset Content-Security-Policy
</IfModule>
<?php
header("Content-Security-Policy: default-src 'none'; img-src 'self' data:;");
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<p>test</p>
<img src="example.jpg">
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' data:;">
<title>test</title>
</head>
<body>
<p>test</p>
<img src="example.jpg">
</body>
</html>
這些指令經常變動,建議常去MDN看一下目前變動的狀態
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
default-src
- 預設值,以下種類未設定時會採用default-src
的設定值
'none'
或'self'
default-src
有差異的指令不能使用這個預設值(詳細請看MDN)img-src
- <img src="...">
media-src
- <video>
<audio>
<source>
<track>
style-src
- <link rel="stylesheet" href="style.css">
script-src
- <script>
font-src
- @font-face
frame-src
- <iframe>
object-src
- <object>
<embed>
<applet>
connect-src
- XMLHttpRequest()
WebSocket()
EventSource()
sendBeacon()
fetch()
sandbox
- 指定<iframe>
sandbox的屬性
base-uri
- <base>
child-src
- <iframe>
Worker()
frame-src
和worker-src
來個別定義form-action
- 限制<form action="...">
action所指向的目標
frame-ancestors
- 讓瀏覽器阻止其他網站嵌入這個網頁(防止網頁被拿去釣魚或欺騙點擊操作)
<frame>
, <iframe>
, <object>
, <embed>
, or <applet>
plugin-types
- 限制<embed>
, <object>
or <applet>
的MIME類型
referrer
report-uri
- 當用戶端違反CSP時,回傳給server的接收網址(HTTP POST json格式)
report-to
(但是大多瀏覽器還不支援)upgrade-insecure-requests;
- 強制將網頁內容中所有http://
請求升級成https://
https://
不會降級回http://
,而是請求失敗http://
轉跳到https://
,若要轉跳要用Strict-Transport-Security
Strict-Transport-Security
是獨立的HTTP header,不在CSP範圍內Strict-Transport-Security
是瀏覽器層級的轉跳301 redirect
重新定向語法require-sri-for
- 強制外部引入的script
或style
必須使用SRI驗證內容完整性
Content-Security-Policy: require-sri-for script;
Content-Security-Policy: require-sri-for style;
Content-Security-Policy: require-sri-for script style;
block-all-mixed-content;
- 禁止https/http混合內容manifest-src
- icon圖標 <link rel="manifest" href="mainfest.json">
navigate-to
report-to
- 用來取代並棄用舊的report-uri
,但是還沒被所有主流瀏覽器支援worker-src
- Worker()
trusted-types
el.innerHTML = "<img src="text.jpg>"
輸入HTML標籤,要改用const img = document.createElement('img')
取代,利用img.src = 'xyz.jpg'
參數的方式輸入值,以避免外來的HTML標籤輸入值造成XSS攻擊Don't
el.innerHTML = '<img src=xyz.jpg>';
Do
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
CSP以白名單的形式運作
建議以 default-src 'none';
或是 default-src 'self';
當成起頭再來設定細部選項,以提高安全性
若沒設定到時會自動採用default-src的設定值,避免遺漏
'none'
- 全部不允許'self'
- 允許同網域指定網域
< host-source > - 指定允許的網域,以空格分開
http://
,https://
前綴的網址,可加*
萬用字元example.com
*.example.com
https://example.com
https://*.example.com
wss://example.com
- 測試過wss://
前綴不能省略一定要加wss://*.example.com
http:
https:
data:
mediastream:
blob:
filesystem:
*
- 允許所有URL,除了data:
blob:
filesystem:
*
這種過於寬鬆的規則'unsafe-inline'
- 允許html行內css或js
'unsafe-eval'
- 允許eval()
eval()
是將字串重新轉回成可執行的程式碼,本身就是高風險的語法,建議不要開啟權限'nonce-<base64-value>'
'<hash-algorithm>-<base64-value>'
最後兩個驗證碼比對用法請參考: CSP: script-src - HTTP | MDN
常用的外部網站檔案用英文關鍵字丟Google都會有人寫過,不會寫的話可以用Google找比較快(通常寫法也會比自己寫的完整)
<IfModule mod_headers.c>
Header set Content-Security-Policy " \
default-src 'self'; \
script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com; \
img-src 'self' data: https://www.google-analytics.com; \
"
</IfModule>
<IfModule mod_headers.c>
Header set Content-Security-Policy " \
default-src 'self'; \
script-src 'self' https://ajax.googleapis.com; \
"
</IfModule>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<IfModule mod_headers.c>
Header set Content-Security-Policy " \
default-src 'self'; \
frame-src 'self' https://www.youtube.com; \
"
</IfModule>
<iframe width="560" height="315" src="https://www.youtube.com/embed/vtwiz6P1rdw" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
如果你的網頁有要嵌入OBS中,記得要加data:
才能修改自訂CSS
<IfModule mod_headers.c>
Header set Content-Security-Policy " \
default-src 'self'; \
style-src 'self' 'unsafe-inline' data:; \
"
</IfModule>
Referrer-Policy - HTTP | MDN (使用細節請看MDN內文)
<a>
, <area>
, <img>
, or <link>
)都會送出Referrer(來源網址)
no-referrer
可能會影響自己網站本身的流量分析和廣告運作(如果有放的話),所以還是要看狀況調整成就適合自己網站使用的參數no-referrer
不能阻止Javascript的AJAX請求送出referrer
# =========== Header ===========
<IfModule mod_headers.c>
# 唯有當符合同源政策下,才能被嵌入到 frame 中。(防止網站被外部釣魚網站嵌入)
Header always append X-Frame-Options SAMEORIGIN
# 拒絕 MIME 類型錯誤的回應 (js,css)
Header set X-Content-Type-Options "nosniff"
# 將所有Cookie設為HttpOnly
Header always edit Set-Cookie (.*) "$1; HTTPOnly; Secure"
#隱藏PHP版本
Header always unset X-Powered-By
</IfModule>
# ==============================
# ========= 禁止訪問檔案 =========
# 這些檔案不應該放在外部網路能讀取到的地方
# 如果你還是上傳了這些檔案請把他給擋掉外部讀取
# 不然這會很危險
# .git可以把你所有程式碼還原
# composer.json, package.json可以知道你用了哪些package和版本
# 如果你用了舊版本沒更新,可能就會被找到漏洞
RedirectMatch 404 /\.git
<Files composer.json>
order allow,deny
deny from all
</Files>
<Files composer.lock>
order allow,deny
deny from all
</Files>
<Files .gitignore>
order allow,deny
deny from all
</Files>
<Files .htacces>
order allow,deny
deny from all
</Files>
<Files .htaccess.sample>
order allow,deny
deny from all
</Files>
<Files .php_cs>
order allow,deny
deny from all
</Files>
<Files .travis.yml>
order allow,deny
deny from all
</Files>
<Files CHANGELOG.md>
order allow,deny
deny from all
</Files>
<Files CONTRIBUTING.md>
order allow,deny
deny from all
</Files>
<Files CONTRIBUTOR_LICENSE_AGREEMENT.html>
order allow,deny
deny from all
</Files>
<Files COPYING.txt>
order allow,deny
deny from all
</Files>
<Files Gruntfile.js>
order allow,deny
deny from all
</Files>
<Files LICENSE.txt>
order allow,deny
deny from all
</Files>
<Files LICENSE_AFL.txt>
order allow,deny
deny from all
</Files>
<Files nginx.conf.sample>
order allow,deny
deny from all
</Files>
<Files package.json>
order allow,deny
deny from all
</Files>
<Files php.ini.sample>
order allow,deny
deny from all
</Files>
<Files README.md>
order allow,deny
deny from all
</Files>
# ===============================
# ========隱藏目錄下所有檔案========
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
# ===============================
這個功能用法在Bootstrap的官網就能看到實例
利用integrity
中的雜湊碼來檢查檔案是否被偷改過
相同的檔案雜湊碼每次算出來都會一樣
被偷改過的檔案雜湊碼則會不同(即使只改一個字雜湊碼也會完全不一樣)
雜湊碼不同時則檔案不會載入運行(更嚴謹的講法是會載入但不會運行,因為要有完整檔案才能給瀏覽器算雜湊碼)
2021/11/3補充SRI的幾個坑(用Chrome 95實測過):
@import
,import的那個外部檔案不在驗證範圍內)integrity=""
中的雜湊碼格式如果填錯時會讓瀏覽器跳過驗證步驟,然後直接運行(例如雜湊碼前的sha384-
忘記填時就不會驗證,會變成直接運行),chrome瀏覽器開發工具的console也不會顯示任何錯誤訊息
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js" integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js" integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm" crossorigin="anonymous"></script>
真的建議認真評估一下是否為了省一點小流量而去使用這些外部JS library CDN
不然的話還是把檔案放自己伺服器比較安全
因為很難保證這些CDN永遠不會出問題,或是有心搞事
而且這些JS CDN含有大量開源library
另外這些library的新舊版也同時存在
根本不可能去檢查完這些library都沒問題
還有這些CDN高機率有在做流量分析
等於是你網站的流量分析被白白撿走(免費CDN的代價?)
就算他們沒有植入額外的JS流量分析code
他們還是能用使用者ip做最基本的流量分析
真的還是要用的話建議避開這些頁面:
(如果你的網站有在使用Google Analytics或是其他外部流量分析工具也是建議避開這些頁面,以確保網站的使用者隱私,還有外部廣告也不應該放在這些頁面)
例子:
windows環境下可以直接用git附送的bash來輸入以下指令
cat dist/main.js | openssl dgst -sha256 -binary | openssl base64 -A
cat dist/main.js | openssl dgst -sha384 -binary | openssl base64 -A
cat dist/main.js | openssl dgst -sha512 -binary | openssl base64 -A
這篇是舉例用,建議實機上線不要這樣寫
應該把雜湊結果存起來直接寫在網頁裡
而不是每次連入都算一次,因為這樣會很浪費伺服器CPU運算
<?php
header("Content-type:text/html;charset=utf-8");
/*
openssl指令:
(windows環境下可以直接用git附送的bash來輸入以下指令)
cat dist/main.js | openssl dgst -sha256 -binary | openssl base64 -A
cat dist/main.js | openssl dgst -sha384 -binary | openssl base64 -A
cat dist/main.js | openssl dgst -sha512 -binary | openssl base64 -A
*/
/*
列出php支援的算法
https://www.php.net/manual/zh/function.openssl-get-md-methods.php
*/
//print_r(openssl_get_md_methods(true));
$file = 'dist/main.js';
$file_contents = file_get_contents($file);
//留一個你要用的算法就好
$sha256 = 'sha256-' . base64_encode( openssl_digest($file_contents , 'sha256', TRUE) );
$sha384 = 'sha384-' . base64_encode( openssl_digest($file_contents , 'sha384', TRUE) );
$sha512 = 'sha512-' . base64_encode( openssl_digest($file_contents , 'sha512', TRUE) );
?>
<html>
<head>
<title>test</title>
</head>
<body>
<!--留一個你要用的算法就好-->
<script src="<?php echo $file; ?>" integrity="<?php echo $sha256; ?>" crossorigin="anonymous"></script>
<script src="<?php echo $file; ?>" integrity="<?php echo $sha384; ?>" crossorigin="anonymous"></script>
<script src="<?php echo $file; ?>" integrity="<?php echo $sha512; ?>" crossorigin="anonymous"></script>
</body>
</html>
白話講就是在A網頁點開B網頁連結的分頁
這時如果B網頁如果帶有惡意JS語法,那他可以把A網頁導到其他頁面
假設像是A網頁如果是fb,如果被導到假的fb登入頁面就可能會被騙走帳號密碼
這個連結寫法可以防止這個問題產生(細節原理請自行google noopener
)
<a rel="noopener noreferrer" target="_blank" href="http://example.com/">連結</a>