Web 應(yīng)用中存在很多安全風(fēng)險,這些風(fēng)險會被黑客利用,輕則篡改網(wǎng)頁內(nèi)容,重則竊取網(wǎng)站內(nèi)部數(shù)據(jù),更為嚴(yán)重的則是在網(wǎng)頁中植入惡意代碼,使得用戶受到侵害。常見的安全漏洞如下:
而框架本身針對 Web 端常見的安全風(fēng)險內(nèi)置了豐富的解決方案:
在框架中內(nèi)置了安全插件 egg-security, 提供了默認(rèn)的安全實踐。
注意:除非清楚的確認(rèn)后果,否則不建議擅自關(guān)閉安全插件提供的功能。
框架的安全插件是默認(rèn)開啟的,如果我們想關(guān)閉其中一些安全防范,直接設(shè)置該項的 enable 屬性為 false 即可。例如關(guān)閉 xframe 防范:
exports.security = { |
match 和 ignore 使用方法和格式與中間件通用配置一致。
如果只想開啟針對某一路徑,則配置 match 選項,例如只針對 /example 開啟 CSP:
exports.security = { |
如果需要針對某一路徑忽略某安全選項,則配置 ignore 選項,例如針對 /example 關(guān)閉 xframe,以便合作商戶能夠嵌入我們的頁面:
exports.security = { |
如果要針對內(nèi)部 ip 關(guān)閉部分安全防范:
exports.security = { |
下面我們會針對具體的場景,來講解如何使用框架提供的安全方案進(jìn)行 Web 安全防范。
XSS(cross-site scripting跨域腳本攻擊)攻擊是最常見的 Web 攻擊,其重點(diǎn)是『跨域』和『客戶端執(zhí)行』。
XSS 攻擊一般分為兩類:
反射型的 XSS 攻擊,主要是由于服務(wù)端接收到客戶端的不安全輸入,在客戶端觸發(fā)執(zhí)行從而發(fā)起 Web 攻擊。比如:
在某購物網(wǎng)站搜索物品,搜索結(jié)果會顯示搜索的關(guān)鍵詞。搜索關(guān)鍵詞填入<script>alert('handsome boy')</script>, 點(diǎn)擊搜索。頁面沒有對關(guān)鍵詞進(jìn)行過濾,這段代碼就會直接在頁面上執(zhí)行,彈出 alert。
框架提供了 helper.escape() 方法對字符串進(jìn)行 XSS 過濾。
const str = '><script>alert("abc") </script><'; |
當(dāng)網(wǎng)站需要直接輸出用戶輸入的結(jié)果時,請務(wù)必使用 helper.escape() 包裹起來,如在 egg-view-nunjucks 里面就覆蓋掉了內(nèi)置的 escape。
另外一種情況,網(wǎng)站輸出的內(nèi)容會提供給 JavaScript 來使用。這個時候需要使用 helper.sjs() 來進(jìn)行過濾。
helper.sjs() 用于在 JavaScript(包括 onload 等 event)中輸出變量,會對變量中字符進(jìn)行 JavaScript ENCODE, 將所有非白名單字符轉(zhuǎn)義為 \x 形式,防止 XSS 攻擊,也確保在 js 中輸出的正確性。使用實例:
const foo = '"hello"'; |
還有一種情況,有時候我們需要在 JavaScript 中輸出 json ,若未做轉(zhuǎn)義,易被利用為 XSS 漏洞??蚣芴峁┝?nbsp;helper.sjson() 宏做 json encode,會遍歷 json 中的 key ,將 value 的值中,所有非白名單字符轉(zhuǎn)義為 \x 形式,防止 XSS 攻擊。同時保持 json 結(jié)構(gòu)不變。 若存在模板中輸出一個 JSON 字符串給 JavaScript 使用的場景,請使用 helper.sjson(變量名) 進(jìn)行轉(zhuǎn)義。
處理過程較復(fù)雜,性能損耗較大,請僅在必要時使用。
實例:
<script> |
基于存儲的 XSS 攻擊,是通過提交帶有惡意腳本的內(nèi)容存儲在服務(wù)器上,當(dāng)其他人看到這些內(nèi)容時發(fā)起 Web 攻擊。一般提交的內(nèi)容都是通過一些富文本編輯器編輯的,很容易插入危險代碼。
框架提供了 helper.shtml() 方法對字符串進(jìn)行 XSS 過濾。
注意,將富文本(包含 HTML 代碼的文本)當(dāng)成變量直接在模版里面輸出時,需要用到 shtml 來處理。 使用 shtml 可以輸出 HTML 的 tag,同時執(zhí)行 XSS 的過濾動作,過濾掉非法的腳本。
由于是一個非常復(fù)雜的安全處理過程,對服務(wù)器處理性能一定影響,如果不是輸出 HTML,請勿使用。
簡單示例:
// js |
|
shtml 在 xss 模塊基礎(chǔ)上增加了針對域名的過濾。
例如只支持 a 標(biāo)簽,且除了 title 其他屬性都過濾掉: whiteList: {a: ['title']}
options:
注意,shtml 使用了嚴(yán)格的白名單機(jī)制,除了過濾掉 XSS 風(fēng)險的字符串外, 在默認(rèn)規(guī)則外的 tag 和 attr 都會被過濾掉。
例如 HTML 標(biāo)簽就不在白名單中,
const html = '<html></html>'; |
常見的 data-xx 屬性由于不在白名單中,所以都會被過濾。
所以,一定要注意 shtml 的適用場景,一般是針對來自用戶的富文本輸入,切忌濫用,功能既受到限制,又會影響服務(wù)端性能。 此類場景一般是論壇、評論系統(tǒng)等,即便是論壇等如果不支持 HTML 內(nèi)容輸入,也不要使用此 Helper,直接使用 escape 即可。
JSONP 的 callback 參數(shù)非常危險,他有兩種風(fēng)險可能導(dǎo)致 XSS
1、callback 參數(shù)意外截斷js代碼,特殊字符單引號雙引號,換行符均存在風(fēng)險。
2、callback 參數(shù)惡意添加標(biāo)簽(如 <script> ),造成 XSS 漏洞。
參考 JSONP 安全攻防
框架內(nèi)部使用 jsonp-body 來對 JSONP 請求進(jìn)行安全防范。
防御內(nèi)容:
可定義配置:
瀏覽器自身具有一定針對各種攻擊的防范能力,他們一般是通過開啟 Web 安全頭生效的??蚣軆?nèi)置了一些常見的 Web 安全頭的支持。
W3C 的 Content Security Policy,簡稱 CSP,主要是用來定義頁面可以加載哪些資源,減少 XSS 的發(fā)生。
框架內(nèi)支持 CSP 的配置,不過是默認(rèn)關(guān)閉的,開啟后可以有效的防止 XSS 攻擊的發(fā)生。要配置 CSP , 需要對 CSP 的 policy 策略有了解,具體細(xì)節(jié)可以參考 CSP 是什么。
默認(rèn)開啟,禁用 IE 下下載框Open按鈕,防止 IE 下下載文件默認(rèn)被打開 XSS。
禁用 IE8 自動嗅探 mime 功能例如 text/plain 卻當(dāng)成 text/html 渲染,特別當(dāng)本站點(diǎn) serve 的內(nèi)容未必可信的時候。
IE 提供的一些 XSS 檢測與防范,默認(rèn)開啟
CSRF(Cross-site request forgery跨站請求偽造,也被稱為 One Click Attack 或者 Session Riding,通??s寫為 CSRF 或者 XSRF,是一種對網(wǎng)站的惡意利用。 CSRF 攻擊會對網(wǎng)站發(fā)起惡意偽造的請求,嚴(yán)重影響網(wǎng)站的安全。因此框架內(nèi)置了 CSRF 防范方案。
通常來說,對于 CSRF 攻擊有一些通用的防范方案,簡單的介紹幾種常用的防范方案:
框架結(jié)合了上述幾種防范方式,提供了一個可配置的 CSRF 防范策略。
在同步渲染頁面時,在表單請求中增加一個 name 為 _csrf 的 url query,值為 ctx.csrf,這樣用戶在提交這個表單的時候會將 CSRF token 提交上來:
<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data"> |
傳遞 CSRF token 的字段可以在配置中改變:
// config/config.default.js |
為了防范 BREACH 攻擊,通過同步方式渲染到頁面上的 CSRF token 在每次請求時都會變化,egg-view-nunjucks 等 View 插件會自動對 Form 進(jìn)行注入,對應(yīng)用開發(fā)者無感知。
在 CSRF 默認(rèn)配置下,token 會被設(shè)置在 Cookie 中,在 AJAX 請求的時候,可以從 Cookie 中取到 token,放置到 query、body 或者 header 中發(fā)送給服務(wù)端。
In jQuery:
var csrftoken = Cookies.get('csrfToken'); |
通過 header 傳遞 CSRF token 的字段也可以在配置中改變:
// config/config.default.js |
默認(rèn)配置下,框架會將 CSRF token 存在 Cookie 中,以方便 AJAX 請求獲取到。但是所有的子域名都可以設(shè)置 Cookie,因此當(dāng)我們的應(yīng)用處于無法保證所有的子域名都受控的情況下,存放在 Cookie 中可能有被 CSRF 攻擊的風(fēng)險??蚣芴峁┝艘粋€配置項,可以將 token 存放到 Session 中。
// config/config.default.js |
注意:該選項已廢棄,攻擊者可以通過 flash + 307 來攻破,請不要在生產(chǎn)環(huán)境打開改選項!
在 SOP 的安全策略保護(hù)下,基本上所有的現(xiàn)代瀏覽器都不允許跨域發(fā)起 content-type 為 JSON 的請求,因此我們可以直接放過類型的 JSON 格式的請求。
// config/config.default.js |
當(dāng) CSRF token 存儲在 Cookie 中時,一旦在同一個瀏覽器上發(fā)生用戶切換,新登陸的用戶將會依舊使用舊的 token(之前用戶使用的),這會帶來一定的安全風(fēng)險,因此在每次用戶登陸的時候都必須刷新 CSRF token。
// login controller |
XST 的全稱是 Cross-Site Tracing,客戶端發(fā) TRACE 請求至服務(wù)器,如果服務(wù)器按照標(biāo)準(zhǔn)實現(xiàn)了 TRACE 響應(yīng),則在 response body 里會返回此次請求的完整頭信息。通過這種方式,客戶端可以獲取某些敏感的頭字段,例如 httpOnly 的 Cookie。
下面我們基于 Koa 來實現(xiàn)一個簡單的支持 TRACE 方法的服務(wù)器:
var koa = require('koa'); |
啟動服務(wù)后,先發(fā)個 GET 請求 curl -i http://127.0.0.1:7001,得到如下響應(yīng):
HTTP/1.1 200 OK |
服務(wù)器設(shè)置了一個 httpOnly 的 Cookie 為 1,在瀏覽器環(huán)境中,是無法通過腳本獲取它的。
接著我們發(fā) TRACE 請求到服務(wù)器curl -X TRACE -b a=1 -i http://127.0.0.1:7001,并帶上 Cookie,得到如下響應(yīng):
HTTP/1.1 200 OK |
在響應(yīng)體里可以看到完整的頭信息,這樣我們就繞過了 httpOnly 的限制,拿到了cookie=1,造成了很大的風(fēng)險。
http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
http://deadliestwebattacks.com/2010/05/18/cross-site-tracing-xst-the-misunderstood-vulnerability/
框架已經(jīng)禁止了 trace,track,options 三種危險類型請求。
釣魚有多種方式,這里介紹 url 釣魚、圖片釣魚和 iframe 釣魚。
服務(wù)端未對傳入的跳轉(zhuǎn) url 變量進(jìn)行檢查和控制,可能導(dǎo)致可惡意構(gòu)造任意一個惡意地址,誘導(dǎo)用戶跳轉(zhuǎn)到惡意網(wǎng)站。 由于是從可信的站點(diǎn)跳轉(zhuǎn)出去的,用戶會比較信任,所以跳轉(zhuǎn)漏洞一般用于釣魚攻擊,通過轉(zhuǎn)到惡意網(wǎng)站欺騙用戶輸入用戶名和密碼盜取用戶信息,或欺騙用戶進(jìn)行金錢交易; 也可能引發(fā)的 XSS 漏洞(主要是跳轉(zhuǎn)常常使用 302 跳轉(zhuǎn),即設(shè)置 HTTP 響應(yīng)頭,Locatioin: url,如果 url 包含了 CRLF,則可能隔斷了 HTTP 響應(yīng)頭,使得后面部分落到了 HTTP body,從而導(dǎo)致 XSS 漏洞)。
框架提供了安全跳轉(zhuǎn)的方法,可以通過配置白名單避免這種風(fēng)險。
安全方案覆蓋了默認(rèn)的ctx.redirect方法,所有的跳轉(zhuǎn)均會經(jīng)過安全域名的判斷。
用戶如果使用ctx.redirect方法,需要在應(yīng)用的配置文件中做如下配置:
// config/config.default.js |
若用戶沒有配置 domainWhiteList 或者 domainWhiteList數(shù)組內(nèi)為空,則默認(rèn)會對所有跳轉(zhuǎn)請求放行,即等同于ctx.unsafeRedirect(url)
如果可以允許用戶向網(wǎng)頁里插入未經(jīng)驗證的外鏈圖片,這有可能出現(xiàn)釣魚風(fēng)險。
比如常見的 401釣魚, 攻擊者在訪問頁面時,頁面彈出驗證頁面讓用戶輸入帳號及密碼,當(dāng)用戶輸入之后,帳號及密碼就存儲到了黑客的服務(wù)器中。 通常這種情況會出現(xiàn)在<img src=$url />中,系統(tǒng)不對$url是否在域名白名單內(nèi)進(jìn)行校驗。
攻擊者可以在自己的服務(wù)器中構(gòu)造以下代碼:
401.php:作用為彈出 401 窗口,并且記錄用戶信息。
<?php |
之后攻擊者生成一個圖片鏈接<img src="http://xxx.xxx.xxx/fishing/401.php?a.jpg//" rel="external nofollow" />。
當(dāng)用戶訪問時,會彈出信息讓用戶點(diǎn)擊,用戶輸入的用戶名及密碼會被黑客的服務(wù)器偷偷記錄。
框架提供了 .surl() 宏做 url 過濾。
用于在 html 標(biāo)簽中中要解析 url 的地方(比如 <a href=""/><img src=""/>),其他地方不允許使用。
對模板中要輸出的變量,加 helper.surl($value)。
注意:在需要解析 url 的地方,surl 外面一定要加上雙引號,否則就會導(dǎo)致XSS漏洞。
不使用 surl
<a href="$value" /> |
output:
<a rel="external nofollow" target="_blank" /> |
使用 surl
<a href="helper.surl($value)" /> |
output:
<a rel="external nofollow" target="_blank" /> |
iframe 釣魚,通過內(nèi)嵌 iframe 到被攻擊的網(wǎng)頁中,攻擊者可以引導(dǎo)用戶去點(diǎn)擊 iframe 指向的危險網(wǎng)站,甚至遮蓋,影響網(wǎng)站的正常功能,劫持用戶的點(diǎn)擊操作。
框架提供了 X-Frame-Options 這個安全頭來防止 iframe 釣魚。默認(rèn)值為 SAMEORIGIN,只允許同域把本頁面當(dāng)作 iframe 嵌入。
當(dāng)需要嵌入一些可信的第三方網(wǎng)頁時,可以關(guān)閉這個配置。
Http Parameter Pollution(HPP),即 HTTP 參數(shù)污染攻擊。在HTTP協(xié)議中是允許同樣名稱的參數(shù)出現(xiàn)多次,而由于應(yīng)用的實現(xiàn)不規(guī)范,攻擊者通過傳播參數(shù)的時候傳輸 key 相同而 value 不同的參數(shù),從而達(dá)到繞過某些防護(hù)的后果。
HPP 可能導(dǎo)致的安全威脅有:
框架本身會在客戶端傳輸 key 相同而 value 不同的參數(shù)時,強(qiáng)制使用第一個參數(shù),因此不會導(dǎo)致 hpp 攻擊。
HTTP 是網(wǎng)絡(luò)應(yīng)用廣泛使用的協(xié)議,負(fù)責(zé) Web 內(nèi)容的請求和獲取。然而,內(nèi)容請求和獲取時會經(jīng)過許多中間人,主要是網(wǎng)絡(luò)環(huán)節(jié),充當(dāng)內(nèi)容入口的瀏覽器、路由器廠商、WIFI提供商、通信運(yùn)營商,如果使用了代理、翻墻軟件則會引入更多中間人。由于 HTTP 請求的路徑、參數(shù)默認(rèn)情況下均是明文的,因此這些中間人可以對 HTTP 請求進(jìn)行監(jiān)控、劫持、阻擋。
在沒有 HTTPS 時,運(yùn)營商可在用戶發(fā)起請求時直接跳轉(zhuǎn)到某個廣告,或者直接改變搜索結(jié)果插入自家的廣告。如果劫持代碼出現(xiàn)了 BUG ,則直接讓用戶無法使用,出現(xiàn)白屏。
數(shù)據(jù)泄露、請求劫持、內(nèi)容篡改等等問題,核心原因就在于 HTTP 是全裸式的明文請求,域名、路徑和參數(shù)都被中間人們看得一清二楚。HTTPS 做的就是給請求加密,讓其對用戶更加安全。對于自身而言除了保障用戶利益外,還可避免本屬于自己的流量被挾持,以保護(hù)自身利益。
盡管 HTTPS 并非絕對安全,掌握根證書的機(jī)構(gòu)、掌握加密算法的組織同樣可以進(jìn)行中間人形式的攻擊。不過HTTPS是現(xiàn)行架構(gòu)下最安全的解決方案,并且它大幅增加了中間人攻擊的成本。
因此,請各位使用 Egg 框架開發(fā)網(wǎng)站的開發(fā)者,務(wù)必推動自己的網(wǎng)站升級到 HTTPS。
對于 HTTPS 來講,還有一點(diǎn)要注意的是 HTTP 嚴(yán)格傳輸安全(HSTS),如果不使用 HSTS,當(dāng)用戶在瀏覽器中輸入網(wǎng)址時沒有加 HTTPS,瀏覽器會默認(rèn)使用 HTTP 訪問
框架默認(rèn)關(guān)閉了 hsts Strict-Transport-Security。使得 HTTPS 站點(diǎn)不跳轉(zhuǎn)到 HTTP,如果站點(diǎn)支持 HTTPS,請一定要開啟。
如果我們的Web 站點(diǎn)是 http 站點(diǎn),需要關(guān)閉這個頭。配置如下:
通過 Server-Side Request Forgery(SSRF) 攻擊,攻擊者可以發(fā)起網(wǎng)絡(luò)請求訪問或者操作內(nèi)部網(wǎng)絡(luò)的資源。
一般來說,SSRF 安全漏洞常見于開發(fā)者在服務(wù)端直接請求客戶端傳遞進(jìn)來的 URL 資源,一旦攻擊者傳入一些內(nèi)部的 URL 即可發(fā)起 SSRF 攻擊。
通常我們會基于內(nèi)網(wǎng) IP 黑名單的形式來防范 SSRF 攻擊,通過對解析域名后得到的 IP 做過濾,禁止訪問內(nèi)部 IP 地址來達(dá)到防范 SSRF 攻擊的目的。
框架在 ctx, app 和 agent 上都提供了 safeCurl 方法,在發(fā)起網(wǎng)絡(luò)請求的同時會對指定的內(nèi)網(wǎng) IP 地址過濾,除此之外,該方法和框架提供的 curl 方法一致。
直接調(diào)用 safeCurl 方法其實并沒有任何作用,還需要配合安全配置項。
// config/config.default.js |
是否為安全域名。安全域名在配置中配置,見 ctx.redirect 部分。
這個函數(shù)提供了模板預(yù)處理-自動插入 CSRF key 的能力,可以自動在所有的 form 標(biāo)簽中插入 CSRF 隱藏域,用戶就不需要手動寫了。
這個函數(shù)提供了模板預(yù)處理-自動插入 nonce 的能力,如果網(wǎng)站開啟了 CSP 安全頭,并且想使用 CSP 2.0 nonce 特性,可以使用這個函數(shù)。參考 CSP 是什么。
這個函數(shù)會掃描模板中的 script 標(biāo)簽,并自動加上 nonce 頭。
對于沒有開啟 HTTPS 的網(wǎng)站,這個函數(shù)可以有限的防止運(yùn)營商劫持。
更多建議: