前言
Web安全問題一直是前端領(lǐng)域一個繞不開的話題捷绑,但很多前端人員對Web的相關(guān)安全策略都只停留在面試過程中,本文主要是對上線過程中遇到的安全問題踩坑進(jìn)行了一個總結(jié)劫谅,旨在對Web安全相關(guān)問題能有一個更為立體切身的體會见坑,也希望能給大家提供一些踩坑時候的參考。
背景
XSS vs CSRF
XSS
XSS是Cross-site scripting的縮寫捏检,為了和Cascading Style Sheets進(jìn)行區(qū)分荞驴,因而將其簡寫為XSS:
Cross-site scripting (XSS) is a security exploit which allows an attacker to inject into a website malicious client-side code. This code is executed by the victims and lets the attackers bypass access controls and impersonate users.
從MDN給出的定義可以看出,XSS是通過inject(注入)
有害代碼來實現(xiàn)攻擊方案的贯城,也就是說XSS的攻擊方案是注入熊楼,這里一般是通過注入腳本,由于瀏覽器中dom能犯、bom的為js提供了接口鲫骗,因而js可以操作html和css犬耻,其也可以插入html片段等。
CSRF
很多人分不清CSRF执泰,其實他們還是有很大區(qū)別的枕磁,只不過通常會使用XSS拿到一些權(quán)限后才進(jìn)行CSRF。CSRF是Cross-Site Request Forgery的簡寫:
CSRF (Cross-Site Request Forgery) is an attack that impersonates a trusted user and sends a website unwanted commands.
從MDN定義可以看出來术吝,CSRF是通過偽造來進(jìn)行攻擊计济,也就是其本質(zhì)目的需要用一切手段來偽裝自己獲取本不屬于它的權(quán)限資源
區(qū)別
名稱 | 目的 | 備注 |
---|---|---|
XSS | 篡改內(nèi)容 | 不關(guān)心權(quán)限 |
CSRF | 獲取資源 | 只關(guān)心權(quán)限 |
XSS分類
名稱 | 存放 | 侵入方式 | 場景 |
---|---|---|---|
存儲型(持久型) | 后端數(shù)據(jù)庫 | HTML | 帶有用戶保存數(shù)據(jù)的網(wǎng)站,如:論壇發(fā)帖顿苇、商品評論峭咒、用戶私信 |
反射型(非持久型) | URL | HTML | 網(wǎng)站搜索、跳轉(zhuǎn) |
DOM 型 | 后端數(shù)據(jù)庫/前端存儲/URL | js | 前端js執(zhí)行纪岁,如寫了eval等 |
XSS防御方案
XSS預(yù)防主要分為兩大部分:
防止攻擊者提交惡意代碼
瀏覽器執(zhí)行惡意代碼
從這兩大部分可以整理出不同的防范思路:
方法 | 步驟 | 類型 |
---|---|---|
利用模板引擎 | 1 | 存儲型凑队、反射型 |
限制及轉(zhuǎn)義輸入 | 1 | 存儲型、反射型 |
限制js執(zhí)行方法 | 2 | DOM型 |
Content Security Policy | 1幔翰、2 | 存儲型漩氨、反射型、DOM型 |
從上述列表可以看出遗增,Content Security Policy對XSS的防范是比較好的叫惊,那么接下來就到了本文的重點內(nèi)容,在下一part會重點介紹CSP相關(guān)的內(nèi)容
CSP
內(nèi)容安全策略(Content Security Policy)是現(xiàn)代瀏覽器安全機(jī)制中的一項重要內(nèi)容做修,從圖上可以看出除了老IE僅支持了csp的sandbox外霍狰,其他主流瀏覽器都已支持了csp,并且w3c也對整體做了統(tǒng)一的要求饰及,具體可以參看w3c的官方文檔Content Security Policy Level 2
簡介
Content-Security-Policy 是現(xiàn)代瀏覽器用來增強(qiáng)document安全性的一個響應(yīng)頭字段蔗坯,其用來限制諸如js、css以及其他瀏覽器所需資源的加載燎含。也就是說宾濒,csp的本質(zhì)是一個限制資源加載的策略,其通過解析csp的指令及值進(jìn)行具體資源的限制屏箍,具體的實現(xiàn)可以看最后一part源碼中chromium的相關(guān)實現(xiàn)绘梦。另外除了在header添加csp外,在html的dom中也可以通過meta標(biāo)簽來進(jìn)行限制赴魁,只是當(dāng)二者所限制的資源指令及值都一樣的時候卸奉,header中的優(yōu)先級較高。
指令
指令 | 版本 | 注釋 |
---|---|---|
default-src | 1 | 大部分資源未定義時會讀取這個配置颖御,少數(shù)不會择卦,比如:frame-ancestors,優(yōu)先級低 |
script-src | 1 | 定義有效的js資源 |
style-src | 1 | 定義有效的css資源 |
img-src | 1 | 定義有效的圖片資源 |
connect-src | 1 | 加載XMLHttpRequest郎嫁、WebSocket秉继、fetch、<a ping> 以及 EventSource資源泽铛,如果不被允許則返回400狀態(tài)碼 |
font-src | 1 | 定義有效的字體資源尚辑,通過@font-face加載 |
object-src | 1 | 定義有效的插件資源,比如:<object> 盔腔,<embed> 或者<applet>
|
media-src | 1 | 定義有效的音頻及視頻資源杠茬,比如:<audio> ,<video> 等元素 |
frame-src | 1 | 定義有效的frame加載資源弛随,在csp2中瓢喉,frame-src被廢棄,使用child-src舀透;在csp3中栓票,又被啟用,與child-src同時存在愕够,如果child-src不存在走贪,frame-src也會起作用 |
sandbox | 1 | 允許iframe的sandbox屬性,sandbox會采用同源策略惑芭,阻止彈窗坠狡、插件及腳本執(zhí)行,可以通過不設(shè)置sandbox屬性遂跟,而是通過allow-scripts逃沿、allow-popups、allow-modals幻锁、allow-orientation-lock凯亮、allow-pointer-lock、allow-presentation越败、allow-popups-to-escape-sandbox触幼、allow-top-navigation來透給sandbox字段限制 |
report-uri | 1 | 向這個uri發(fā)送失敗報告,也可以使用Content-Security-Policy-Report-Only來作為http header進(jìn)行發(fā)送但不阻塞網(wǎng)絡(luò)資源的解析究飞,在csp3中report-uri被廢棄置谦,改用report-to指令 |
child-src | 2 | 定義web workers以及包含嵌套上下文的資源加載,比如<frame> 以及<iframe>
|
form-action | 2 | 定義有效的html標(biāo)簽<form> 的action資源加載 |
frame-ancestors | 2 | 定義有效內(nèi)嵌標(biāo)簽諸如<frame> 亿傅、<iframe> 媒峡、<object> 、<embd> 葵擎、<applet> 資源加載谅阿,當(dāng)值為'none'時,大致可以和X-Frame-Options: DENY相當(dāng) |
plugin-types | 2 | 定義通過<object> 及<embd> 的MIME資源類型加載,對于<applet> 則必須明確MIME為application/x-java-applet |
base-uri | 2 | 定義通過<base> 的html標(biāo)簽中的src屬性引用的url資源加載 |
report-to | 3 | 定義Report-To的http響應(yīng)頭字段 |
worker-src | 3 | 限制通過Worker签餐、SharedWorker以及ServiceWorker的url資源加載 |
manifest-src | 3 | 限制manifests的url資源加載 |
prefetch-src | 3 | 定義預(yù)渲染及預(yù)加載請求的資源加載寓涨,比如通過<link> 標(biāo)簽的rel="prefetch"或rel="prerender"屬性 |
navigate-to | 3 | 限制document的任何方式跳轉(zhuǎn)url,比如通過link跳轉(zhuǎn)氯檐,或者window.location被執(zhí)行戒良,如果form-action被設(shè)置,則本規(guī)則會被替換冠摄,即對于form而言糯崎,form-action優(yōu)先級更高 |
值
值 | 版本 | 描述 | 樣例 |
---|---|---|---|
* | 1 | 未知,允許除了data河泳、blob沃呢、filesystem、schemes之外的任何url資源加載 | img-src * |
'none' | 1 | 不允許加載任何的資源 | object-src 'none' |
'self' | 1 | 只允許同源資源加載 | script-src 'self' |
'data:' | 1 | 允許data格式的資源加載拆挥,比如:Base64圖片編碼 | img-src 'self' data: |
xx.xxx.com | 1 | 允許明確域名的資源加載 | img-src domain.example.com |
*.xxx.com | 1 | 允許加載任何例如:example.com子域下的資源 | img-src *.example.com |
https://xxx.com | 1 | 僅允許https協(xié)議下域名的資源加載 | img-src https://cdn.com |
https: | 1 | 允許加載https下任何域名的資源 | img-src https: |
'unsafe-inline' | 1 | 允許使用內(nèi)聯(lián)元素加載資源薄霜,諸如:級聯(lián)樣式、句柄方法竿刁、script內(nèi)容標(biāo)簽以及javascript:URIs等 | script-src 'unsafe-inline' |
'unsafe-eval' | 1 | 允許不安全的動態(tài)js代碼執(zhí)行 | script-src 'unsafe-eval' |
'sha256-' | 2 | 允許內(nèi)聯(lián)script及css匹配hash值后的執(zhí)行 | script-src 'sha256-xyz...' |
'nonce-' | 2 | 允許使用包含nonce屬性的內(nèi)聯(lián)script及css執(zhí)行黄锤,nonce應(yīng)該是一個安全的任意值,并且不能被重復(fù)使用 | script-src 'nonce-r@nd0m' |
'strict-dynamic' | 3 | 允許加載非解析型腳本資源食拜,比如:document.createElement('script') | script-src 'strict-dynamic' |
'unsafe-hashes' | 3 | 允許使用事件句柄的方式進(jìn)行資源加載鸵熟,但是不允許使用內(nèi)聯(lián)腳本及javascript:執(zhí)行的方式 | script-src 'unsafe-hashes' 'sha256-abc...' |
案例
通過上一part的介紹,我們大致了解了CSP相關(guān)一些用法负甸,下面具體看一下在前端業(yè)務(wù)中的具體實踐
nginx
server {
listen 9080;
server_name localhost;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; frame-ancestors 'self'; object-src 'none'";
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
}
我們先來看一下nginx中的所有都嚴(yán)格控制的情況流强,我們再console中的v8實例下,使用創(chuàng)建一個標(biāo)簽來測試一下
這時我們發(fā)現(xiàn)呻待,瀏覽器中報了
Refused to apply xxx because it violates the following Content Security Policy directive...
的錯誤打月;同時,我們打開任意一個network中的請求連接蚕捉,我們發(fā)現(xiàn)奏篙,在響應(yīng)頭中,出現(xiàn)了
如上圖所示的Content-Security-Policy: xxx
的字段
接著迫淹,我們根據(jù)返回的錯誤秘通,進(jìn)行適度的csp限制放開
server {
listen 9080;
server_name localhost;
add_header Content-Security-Policy "default-src *; script-src * 'unsafe-inline' 'unsafe-eval'; img-src * data:;";
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
}
修改過后,發(fā)現(xiàn)之前的報錯消失了敛熬,測試成功
html
最后肺稀,我們來看一下html中meta標(biāo)簽的一個使用情況
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Security-Policy" content="default-src *; script-src 'unsafe-inline' 'unsafe-eval'; img-src *;">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without
JavaScript enabled. Please enable it to continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
在不使用nginx配置csp的情況下,在html中配置meta也是可以起到同樣的作用应民,但是當(dāng)二者同時存在且字段一致時话原,會優(yōu)先解析header中的響應(yīng)
源碼
這一part我們通過chromium源碼來看一下chrome中是如何實現(xiàn)CSP的
content_security_policy_parsers
bool IsCSPDirectiveNameCharacter(UChar c) {
return IsASCIIAlpanumeric(c) || c == '-';
}
bool IsCSPDirectiveValueCharacter(UChar c) {
return IsASCIISpace(c) || (IsASCIIPrintable(c) && c != ',' && c != ';');
}
在network中通過解析key-value值對Content-Security-Policy中的指令及值進(jìn)行獲取
resource_fetcher
bool ResourceFetcher::ResourceNeedsLoad(Resource* resource, const FetchParameters& params, RevalidationPolicy policy) {
if(archive_)
return false;
if(resource->GetType() == ResourceType::kFont && !params.IsLinkPreload())
return false;
if(resource->GetType() == ResourceType::kImage && (ShouldDeferImageLoad(resource->Url()) || params.GetImageRequestBehavior() == FetchParameters::kDeferImageLoad)) {
return false;
}
return policy != RevalidationPolicy::kUse || resource->StillNeedsLoad();
}
對是否進(jìn)行資源加載進(jìn)行判斷
http_equiv
void HttpEquiv::ProcessHttpEquivContentSecurityPolicy(LocalDOMWindow* window, const AtomicString& equiv, const AtomicString& content) {
if(!window || !window->GetFrame())
return;
if(window->GetFrame()->GetSettings()->GetBypassCSP())
return;
if(EqualIgnoringASCIICase(equiv, "content-security-policy")) {
Vector<network::mojom::blink::ContentSecurityPolicyPtr> parsed = ParseContentSecurityPolicies(content, network::mojom::blink::ContentSecurityPolicyType::kEnforce, network::mojom::blink::ContentSecurityPolicySource::kMeta, *(window->GetSecurityOrigin()));
window->GetContentSecurityPolicy()->AddPolicies(mojo::Clone(parsed));
window->GetPolicyContainer()->AddContentSecurityPolicies(std::move(parsed));
} else if (EqualIgnoringASCIICase(equiv, "content-security-policy-report-only")) {
window->GetContentSecurityPolicy()->ReportReportOnlyInMeta(content);
} else {
NOTREACHED();
}
}
我們看到在Document中執(zhí)行相關(guān)csp的一些邏輯
worker_content_settings_client
bool WorkerContentSettingsClient::AllowScriptFromSource(bool enabled_per_settings, const blink::WebURL& script_url) {
bool allow = enabled_per_settings;
if(allow && content_setting_rules_) {
GURL top_frame_origin_url = top_frame_origin_.GetURL();
for(const auto& rule: content_setting_rules_->script_rules) {
if(rule.primary_pattern.Matches(top_frame_origin_url) && rule.secondary_pattern.Matches(script_url)) {
allow = rule.GetContentSetting() != CONTENT_SETTING_BLOCK;
break;
}
}
}
if(!allow) {
EnsureContentSettingsManager();
content_settings_manager_->OnContentBlocked(render_frame_id_, ContentSettingsType::JAVASCRIPT);
return false;
}
return true;
}
最后夕吻,我們可以簡單看一下是否允許加載content的一些邏輯,以script加載為例
總結(jié)
不論是否是it行業(yè)繁仁,只要是工程師涉馅,都需要對工程項目中的安全問題進(jìn)行相關(guān)的重視,對于前端工程來說改备,常見的XSS控漠、CSRF等相關(guān)知識也需要我們在實際工程項目中進(jìn)行原理探索,以及對常見的解決方案能夠做到了熟于心悬钳,這樣在架構(gòu)設(shè)計及工程實踐過程中才能做到應(yīng)用工程的穩(wěn)定可持續(xù),最后附上一張chrome中的xss信息的ER圖偶翅,從中體會一下chrome對于安全問題的架構(gòu)設(shè)計及信息鏈路把控的優(yōu)秀與嚴(yán)謹(jǐn)
參考
- Cross-site scripting
- 前端安全系列(一):如何防止XSS攻擊默勾?
- 前端安全系列(二):如何防止CSRF攻擊?
- xss攻擊和csrf攻擊的定義及區(qū)別
- 【第849期】如何讓前端更安全聚谁?——XSS攻擊和防御詳解
- 預(yù)測最近面試會考 Cookie 的 SameSite 屬性
- Content-Security-Policy
- 內(nèi)容安全策略(CSP)詳解
- CHROME擴(kuò)展筆記之拒絕unsafe-eval求值
- CSP內(nèi)容安全策略
- Module ngx_http_headers_module
- Content Security Policy Reference
- Content Security Policy Level 2
- WebKit技術(shù)內(nèi)幕
- 前端的CSP & CSP如何落地母剥,了解一下