1.1 利用FormHash防CSRF和變淡自動提交
FormHash 指的是,通過在 Form 表單中構(gòu)造一個隱藏的 input 元素,如:
<input type="hidden" name="formhash" id="formhash" value="{FORMHASH}" />
讓第三方難以偽造這個 input 的 value,借此阻止網(wǎng)站外部隨意構(gòu)造表單提交,即防CSRF。適合的業(yè)務(wù)場景有注冊、登錄、下單、秒殺、抽獎、積分換代金券等等。
1.2 通過全局 Filter 檢測或過濾 XSS/SQLi/shell注入
最好是在框架層面增加全局 Filter,對 HTTP Request 中的 $_GET/$_POST/$_COOKIE 進(jìn)行字符串過濾,這種 Filter 將是強(qiáng)制性的。(出于防范CSRF(Cross-Site Request Forgery,跨站請求偽造)的考慮[注4],工程師盡量不要使用 $_REQUEST。)
對于 PHP 來說,還可以在開發(fā)環(huán)境引入 laruence 的檢測 xss/sql/shell 注入的 PHP 擴(kuò)展模塊。
其次,杜絕裸寫SQL。
1.2.1.大的原則是:不要相信客戶端提交的數(shù)據(jù)
要深刻理解XSS的原理,攻擊代碼不一定(非要)在 <script></script> 中。
所以,處理XSS注入的時(shí)候,不僅僅要轉(zhuǎn)義或刪除特殊的 HTML 標(biāo)記和符號,如尖括號<>,如script,如iframe等,還需要過濾 JavaScript 事件所涉及的大量屬性.
否則,就可能出現(xiàn)下面這兩種的XSS漏洞實(shí)例:
例1:
http://YourDomain.com/index?num=1%22+onmousemove%3Dalert%284%29+wb%3D%22
%22就是單引號的轉(zhuǎn)義,%3D是等號,%28是左括號,%29是右括號。鼠標(biāo)移動可觸發(fā)。
例2:
訪問 http://YourDomain.com/;
在頁眉的搜索輸入框中輸入關(guān)鍵詞:" onmousemove=alert(4) wb=" (注:包含雙引號);
在搜索結(jié)果頁面上,當(dāng)鼠標(biāo)移動時(shí),就會彈出XSS種下的JS彈出框
1.3.驗(yàn)證碼服務(wù)不簡單
圖片驗(yàn)證碼或者答題驗(yàn)證碼是為了盡可能狙擊注冊機(jī)、秒殺器等非人類行為,自然就成了攻防第一線的重要技術(shù)。
攻:提前收集驗(yàn)證碼。適用場景:秒殺。在秒殺開始前就收集好驗(yàn)證碼,從而提前準(zhǔn)備好表單。
防:1)不同業(yè)務(wù)的驗(yàn)證碼不得混用,登錄、注冊、抽獎、秒殺、下發(fā)短信驗(yàn)證等各是各的。2)像秒殺這種與時(shí)間有關(guān)的業(yè)務(wù)場景,驗(yàn)證碼就不允許提前生成。即使刻意構(gòu)造出某個秒殺活動的圖片驗(yàn)證碼URL訪問,也必須先判斷秒殺活動開始時(shí)間,阻止提前訪問。
攻:構(gòu)造表單時(shí)使用過期 cookie。
驗(yàn)證碼一般是瀏覽器提交 cookie 里的 verifysession(一個標(biāo)識本次驗(yàn)證會話的 Hash 串)和手工輸入的驗(yàn)證碼字符串 verifycode,服務(wù)器端按 F(verifysession)==verifycode 進(jìn)行校驗(yàn)。那么秒殺器突破時(shí),別用服務(wù)器之前響應(yīng)的 Hash 串,一直用自己手中掌握的 verifysession 和 verifycode 提交,就可以突破了。即使這一組 verifysession 和 verifycode 在服務(wù)器端驗(yàn)證后立即失效,也無所謂,多準(zhǔn)備幾組數(shù)據(jù),就拼誰的秒殺器并發(fā)提交快即可。
防:1)結(jié)合 FormHash 方法聯(lián)防。2)針對不同業(yè)務(wù)生成的驗(yàn)證碼的過期時(shí)間可以單獨(dú)設(shè)定??梢宰屇硞€業(yè)務(wù)的驗(yàn)證碼圖片過期時(shí)間很短。
攻:OCR識別圖片驗(yàn)證碼。
防:如果業(yè)務(wù)場景確實(shí)非常需要狙擊機(jī)器提交,那么上答題驗(yàn)證碼,但也因此有了“題庫有限”這個弱點(diǎn)。
攻:(既然題庫有限那么)通過刷秒殺頁面來收集題目。然后人工快速回答,或者 OCR 識別問題內(nèi)容,自動匹配答案。
防:FormHash 有一定作用,但對于“按鍵精靈”軟件錄制腳本(而不是機(jī)器構(gòu)造表單),確實(shí)難以防范,第三方只要比真正的用戶響應(yīng)快、提交快即可。
攻:答題時(shí),構(gòu)造表單使用過期題目。
原理類似于使用過期 cookie。如果本次秒殺商品對應(yīng)的答題不是一對一鎖定的(即換其他題提交也可以),那么也可以強(qiáng)制使用過期題目。
防:加Token。讓 Token 的生成與業(yè)務(wù)的具體場景有關(guān)。如一個 Token 由登錄用戶(以user id標(biāo)識)和當(dāng)前秒殺商品(以goods id標(biāo)識)等關(guān)鍵信息生成。也是 FormHash 思路的延續(xù)。或者當(dāng)前秒殺綁定題目,禁止使用其他題目答題。
攻:上面的措施都擋不住我。
防:攔人。對于黑名單用戶(手動加或自動識別),他能參與秒殺,但總是得到如下提示“系統(tǒng)繁忙:人太多了,休息一下,等等吧”。
其他電商的做法參考鄭昀的《對付秒殺器等惡意訪問行為的簡單梳理》 [注6] 。
1.4.敏感信息存入 cookies 須防篡改
電商應(yīng)用有時(shí)候不可避免地存儲了一些敏感數(shù)據(jù)到客戶端,當(dāng)然不希望被客戶端篡改。
所以可以在 set-cookie 時(shí)加上防篡改驗(yàn)證碼,或者不暴露原值直接對稱加密存儲。
如:user_name=alex|bj95ef23cc6daecc475de
防篡改驗(yàn)證碼的生成規(guī)則可以很簡單:md5(cookieValue+key)或sha1(cookieValue+key),key可以是服務(wù)器端掌握的一個固定字符串,也可以很復(fù)雜(如LTPA示例[注7])。
1.5.IP地址防偽造
有人會說可以通過 request.getHeader("X-FORWARDED-FOR") 獲得ip字符串,這個頭域簡稱 XFF頭,只有在通過了 HTTP 代理或者負(fù)載均衡服務(wù)器時(shí)才會添加該項(xiàng)。
但一定要知道 XFF 頭僅僅是 HTTP Headers 中的一分子,那自然是可以隨意增刪改的。很多投票系統(tǒng)都有此漏洞,它們簡單地取 XFF 頭中的ip地址,設(shè)置為客戶端來源地址,因此第三方可以偽造任何ip投票。
所以規(guī)則一:不要輕信 HTTP Headers 里的IP字段。
還與客戶端到服務(wù)器端之間的路徑有關(guān),所以工程師要關(guān)注生產(chǎn)環(huán)境里你的 WebServer 前面到底掛的是什么,Nginx 的 proxy_set_header 是怎么配置的。
幾種場景:1)F5->Nginx->WebServer;2)F5->WebServer;3)Client->CDN->Nginx。搞清楚場景和配置,拿到真正的 Remote Address,請參考鄭昀的《客戶端的IP地址偽造、CDN、反向代理、獲取的那些事兒》。
1.6.防 Session Hijacking
當(dāng)?shù)谌揭匀缦率址ǐ@得了你的瀏覽器標(biāo)識會話的字符串,那么他也許能以你的身份冒名操作:
從訪問記錄里獲得 URL 上攜帶的 Session Token;
利用 XSS 漏洞竊取 Cookie 里存儲的 Session Token。
所以,首先絕不在 URL 中傳遞 Session Token,除非走 HTTPS 通道。
其次,需要多管齊下,1)cookie 里的 Session Token,必須設(shè)定為 httponly,禁止 JavaScript 讀取,以絕后患;2)如果服務(wù)器端還需要 cookie 里存儲的用戶ID等信息,那么 cookie 鍵值需要加簽名防篡改;3)根據(jù)客戶端的IP地址、User-Agent、Session和其他信息生成一個 UA Token,存儲在 cookie 里;服務(wù)器端每次都會核對這個 UA Token 是否正確,如果不正確則退出登錄。這樣,即使第三方拿到了你的 Session Token,也無法處于登錄狀態(tài)。
1.7.用 Robots.txt 限制住搜索引擎
每一個對公網(wǎng)暴露的 Web 工程,上線之初理應(yīng)配有 Robots.txt ,不僅僅是為了SEO,而且要限制站點(diǎn)的某些目錄不允許抓取和收錄。
內(nèi)部站點(diǎn)的robots.txt 內(nèi)容必須(MUST)是:
User-agent: *
Disallow: /
不少瀏覽器都會向搜索引擎?zhèn)魉驮L問歷史,你以為的內(nèi)部隱秘訪問地址,可能早已被搜索引擎收錄了,所以對此絕不要掉以輕心。不要給入侵者借搜索“site:YourDomain.com”或“site:YourDomain.com URL:/YourPath” 遍歷內(nèi)部站點(diǎn)的機(jī)會。
1.8.敏感信息的存儲和展示
牢記一點(diǎn),你的數(shù)據(jù)庫很有可能被拖庫,我們要未雨綢繆,降低拖庫后的危害!
所以,敏感信息請加密存儲。
1.8.1.密碼的存儲
首先,絕不能僅僅 MD5(password) 存儲,這樣等同于明文存儲。其次鐵律是,禁止明文存儲(登錄)密碼。
最簡單的辦法是,隨機(jī)生成 SaltKey 并存儲,按 MD5(salt+MD5(password)) 存儲密碼。
如果密碼需要二次分發(fā)(而不是重置密碼),請對稱加密存儲(應(yīng)用程序掌管公鑰私鑰)。
1.8.2.敏感信息的展示
牢記一點(diǎn),第三方很有可能拿到你的平臺登錄權(quán)限(通過 session hijacking,或通過內(nèi)部人),所以要未雨綢繆,敏感字段不要輕易示人!
所以,敏感信息盡量不要明文展示,即使是合法用戶登錄狀態(tài)下。
以下信息需“中間若干位星號顯示”,如果沒有特別理由,那么默認(rèn)不能直接顯示(包括導(dǎo)表):
(商戶的)銀行帳號,
(商戶的或用戶的)手機(jī)號碼和郵箱地址。
1.10.防表單重復(fù)提交
有兩個含義:
頁面無刷新情況下,某些業(yè)務(wù)場景需要主動阻止表單重復(fù)提交:
有的業(yè)務(wù)場景支持重復(fù)提交但會提示:如實(shí)物類電商,同一個 SKU 提示重復(fù)加入購物車、但仍能加入購物車,只是在相同 SKU 上加數(shù)量;
有的場景會通過以下手法盡量提前避免表單(尤其是數(shù)據(jù)未變化的情況下)重復(fù)提交,而不用非要到服務(wù)器端在數(shù)據(jù)庫層面做重復(fù)數(shù)據(jù)判斷:
按鈕點(diǎn)擊后變灰(按鈕文字可以更改,如顯示倒計(jì)時(shí)),收到 Ajax 響應(yīng)后再恢復(fù),典型場景是填寫手機(jī)號碼點(diǎn)擊按鈕下發(fā)短信驗(yàn)證碼時(shí);
頁面刷新情況下,盡量阻止表單重復(fù)提交:
用戶行為有:
用戶按F5刷新(注意,不是Ctrl+F5強(qiáng)制刷新);
用戶點(diǎn)擊瀏覽器的后退或前進(jìn)功能回到之前的網(wǎng)頁;
這時(shí)需要服務(wù)器端配合了:
POST ****表單提交后,服務(wù)器端做302跳轉(zhuǎn),利用302跳轉(zhuǎn)清空表單參數(shù);
延續(xù) FormHash 思路,服務(wù)器端收到 formhash 值后存入緩存,幾分鐘后過期,這樣校驗(yàn)邏輯可以阻止幾分鐘內(nèi)的含相同 formhash 值的表單重復(fù)提交。
1.11.訪問速率限制Rate Limits
業(yè)務(wù)限流的目的是:
防掃號防暴力破解場景:防止對手高速掃描(或調(diào)用)我們的系統(tǒng)某個URI。這種場景經(jīng)常發(fā)生在凌晨,對于異常訪問,我們系統(tǒng)必須第一時(shí)間攔截,在這種情況下,可能不允許太高的突發(fā)。
保證系統(tǒng)平穩(wěn)運(yùn)行:系統(tǒng)承載能力有限,數(shù)據(jù)庫支撐能力有限,所以不希望系統(tǒng)由于突發(fā)陡增請求而引發(fā)下游系統(tǒng)性能出現(xiàn)凸起,當(dāng)然適量的波動是允許的。
工程上有幾種做法:
1.11.2.簡單的memcache以秒為單位的度量
memcache 里的 key 可以為:業(yè)務(wù)編號IP地址時(shí)間戳_Limiter種類。時(shí)間戳精確到秒或分鐘。
1.11.3.漏桶(Leaky Bucket)算法
Nginx 的 limitReq 模塊采用的就是漏桶算法,分 delay 和 nodelay 兩種處理模式
1.11.4.令牌桶(Token Bucket)算法
令牌桶具體實(shí)現(xiàn):
在數(shù)據(jù)結(jié)構(gòu)上,沒有必要真的實(shí)現(xiàn)一個令牌桶。
基于時(shí)間的流逝生成受控制數(shù)量的令牌即可——以時(shí)間的流逝來洗滌舊跡,也就是將兩次發(fā)包或者收包的間隔和令牌數(shù)量聯(lián)系起來。
令牌桶和漏桶算法最主要的差別在于:漏桶算法能夠強(qiáng)行限制數(shù)據(jù)的傳輸速率,而令牌桶算法能夠在限制數(shù)據(jù)的平均傳輸速率的同時(shí)還允許某種程度的突發(fā)傳輸。
相對于漏桶算法,令牌桶的波動幅度可能是我們系統(tǒng)無法承受的。
令牌是基于 Request 觸發(fā)投放到令牌桶的,如果是按照下面的投放策略:
delta = self.fill_rate * (now - self.timestamp)#fill_rate is the rate in tokens/second that the bucket will be refilled.
self._tokens = min(self.capacity, self._tokens + delta)
那么代入演算一下,令牌桶令牌總數(shù) capacity=80,還剩余0張令牌,令牌桶填充速率fill_rate=0.5t/s,流逝的時(shí)間是10秒,即過去的10秒沒有任何請求,第11秒突然拿來了一個請求,于是令牌桶里就會放入min(80,5)=5張令牌,意味著第11秒可以消耗5個令牌。
這也就是我們?yōu)槭裁凑f令牌桶算法是“只要令牌桶中存在令牌,那么就允許突發(fā)地傳輸數(shù)據(jù)直到達(dá)到用戶配置的上限,因此它適合于具有突發(fā)特性的流量”的緣故。
了解更多漏桶和令牌桶算法信息,請參考鄭昀的《集群環(huán)境下業(yè)務(wù)限流[注10]》。
1.11.5.滑窗(Sliding Window)算法
淘寶中間件博客上曾提及,『流量預(yù)警和限流方案中,滑窗模式通過統(tǒng)計(jì)多個單元時(shí)間的訪問次數(shù)來進(jìn)行控制,當(dāng)單位時(shí)間的訪問次數(shù)達(dá)到某個峰值時(shí)進(jìn)行限流。
在每次有訪問進(jìn)來時(shí),我們判斷前N個單位時(shí)間里總訪問量是否超過了設(shè)置的閾值,若超過則不允許執(zhí)行。
缺點(diǎn)一:
由于訪問量的不可預(yù)見性,會出現(xiàn)單位時(shí)間的前半段有大量請求涌入,而后半段則拒絕所有請求的情況發(fā)生。
缺點(diǎn)二:
其次,我們很難確定這個閾值設(shè)置在多少比較合適,只能通過經(jīng)驗(yàn)或者模擬(如壓測)來估算。不過即使是壓測也很難估計(jì)準(zhǔn)確。
所以滑窗模式往往用來對某一資源的保護(hù)上(或者說是承諾比較合適:我對某一接口的提供者承諾過,最高調(diào)用量不超過XX),如對 DB 的保護(hù),對某一服務(wù)的調(diào)用的控制上。
代碼實(shí)現(xiàn)思路如下:
每一個窗(單位時(shí)間)就是一個獨(dú)立的計(jì)數(shù)器(原子計(jì)數(shù)器),用以數(shù)組保存。將當(dāng)前時(shí)間以某種方式(比如取模)映射到數(shù)組的一項(xiàng)中。每次訪問先對當(dāng)前窗內(nèi)計(jì)數(shù)器+1,再計(jì)算前N個單元格的訪問量綜合,超過閾值則限流。
這里有個問題,時(shí)間永遠(yuǎn)是遞增的,單純的取模,會導(dǎo)致數(shù)組過長,使用內(nèi)存過多,我們可以用環(huán)形隊(duì)列來解決這個問題?!?/p>