Web內(nèi)容安全策略淺析

前端 | Web內(nèi)容安全策略淺析.png

前言

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ù)防主要分為兩大部分:

  1. 防止攻擊者提交惡意代碼

  2. 瀏覽器執(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)

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市形导,隨后出現(xiàn)的幾起案子环疼,更是在濱河造成了極大的恐慌鄙币,老刑警劉巖成黄,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疤孕,死亡現(xiàn)場離奇詭異围橡,居然都是意外死亡勿决,警方通過查閱死者的電腦和手機(jī)恋脚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門扬霜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粘招,“玉大人处嫌,你說我怎么就攤上這事栅贴。” “怎么了熏迹?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵檐薯,是天一觀的道長。 經(jīng)常有香客問我注暗,道長坛缕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任友存,我火速辦了婚禮祷膳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘屡立。我一直安慰自己直晨,他們只是感情好搀军,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著勇皇,像睡著了一般罩句。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上敛摘,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天门烂,我揣著相機(jī)與錄音,去河邊找鬼兄淫。 笑死屯远,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捕虽。 我是一名探鬼主播慨丐,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼泄私!你這毒婦竟也來了房揭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤晌端,失蹤者是張志新(化名)和其女友劉穎捅暴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咧纠,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡蓬痒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了惧盹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乳幸。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖钧椰,靈堂內(nèi)的尸體忽然破棺而出粹断,到底是詐尸還是另有隱情,我是刑警寧澤嫡霞,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布瓶埋,位于F島的核電站,受9級特大地震影響诊沪,放射性物質(zhì)發(fā)生泄漏养筒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一端姚、第九天 我趴在偏房一處隱蔽的房頂上張望晕粪。 院中可真熱鬧,春花似錦渐裸、人聲如沸巫湘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尚氛。三九已至诀诊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阅嘶,已是汗流浹背属瓣。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留讯柔,地道東北人抡蛙。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像磷杏,于是被迫代替她去往敵國和親溜畅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容