偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

HTTP規(guī)范中的那些暗坑

開(kāi)發(fā) 前端
本文總結(jié)了 HTTP 規(guī)范中常見(jiàn)的幾個(gè)暗坑,希望大家開(kāi)發(fā)中有意識(shí)的規(guī)避它們,提升開(kāi)發(fā)體驗(yàn)。

HTTP 協(xié)議可以說(shuō)是開(kāi)發(fā)者最熟悉的一個(gè)網(wǎng)絡(luò)協(xié)議,「簡(jiǎn)單易懂」和「易于擴(kuò)展」兩個(gè)特點(diǎn)讓它成為應(yīng)用最廣泛的應(yīng)用層協(xié)議。

雖然有諸多的優(yōu)點(diǎn),但是在協(xié)議定義時(shí)因?yàn)橹T多的博弈和限制,還是隱藏了不少暗坑,讓人一不小心就會(huì)陷入其中。本文總結(jié)了 HTTP 規(guī)范中常見(jiàn)的幾個(gè)暗坑,希望大家開(kāi)發(fā)中有意識(shí)的規(guī)避它們,提升開(kāi)發(fā)體驗(yàn)。

1.Referer

HTTP 標(biāo)準(zhǔn)把 Referrer 寫(xiě)成 Referer(少些了一個(gè) r),可以說(shuō)是計(jì)算機(jī)歷史上最著名的一個(gè)錯(cuò)別字了。

Referer 的主要作用是攜帶當(dāng)前請(qǐng)求的來(lái)源地址,常用在反爬蟲(chóng)和防盜鏈上。前段時(shí)間鬧的沸沸揚(yáng)揚(yáng)的新浪圖床掛圖事件,就是因?yàn)樾吕藞D床突然開(kāi)始檢查 HTTP Referer 頭,非新浪域名就不返回圖片,導(dǎo)致很多蹭流量的中小博客圖都掛了。

雖然 HTTP 標(biāo)準(zhǔn)里把 Referer 寫(xiě)錯(cuò)了,但是其它可以控制 Referer 的標(biāo)準(zhǔn)并沒(méi)有將錯(cuò)就錯(cuò)。

例如禁止網(wǎng)頁(yè)自動(dòng)攜帶 Referer 頭的 <meta> 標(biāo)簽,相關(guān)關(guān)鍵字拼寫(xiě)就是正確的: 

  1. <!-- 全局禁止發(fā)送 referrer -->  
  2. <meta name="referrer" content="no-referrer" /> 

還有一個(gè)值得注意的是瀏覽器的網(wǎng)絡(luò)請(qǐng)求。從安全性和穩(wěn)定性上考慮,Referer 等請(qǐng)求頭在網(wǎng)絡(luò)請(qǐng)求時(shí),只能由瀏覽器控制,不能直接操作,我們只能通過(guò)一些屬性進(jìn)行控制。比如說(shuō) Fetch 函數(shù),我們可以通過(guò) referrer 和 referrerPolicy 控制,而它們的拼寫(xiě)也是正確的: 

  1. fetch('/page', {  
  2.   headers: {  
  3.     "Content-Type": "text/plain;charset=UTF-8"  
  4.   },  
  5.   referrer: "https://demo.com/anotherpage", // <-  
  6.   referrerPolicy: "no-referrer-when-downgrade", // <-  
  7. }); 

一句話總結(jié):

凡是涉及到 Referrer 的,除了 HTTP 字段是錯(cuò)的,瀏覽器的相關(guān)配置字段拼寫(xiě)都是正確的。

二.「靈異」的空格

1.%20 還是 + ?

這個(gè)是個(gè)史詩(shī)級(jí)的大坑,我曾經(jīng)被這個(gè)協(xié)議沖突坑了一天。

開(kāi)始講解前先看個(gè)小測(cè)試,在瀏覽器里輸入 blank test( blank 和 test 間有個(gè)空格),我們看看瀏覽器如何處理的:

從動(dòng)圖可以看出瀏覽器把空格解析為一個(gè)加號(hào)「+」。

是不是感覺(jué)有些奇怪?我們?cè)僮鰝€(gè)測(cè)試,用瀏覽器提供的幾個(gè)函數(shù)試一下: 

  1. encodeURIComponent("blank test") // "blank%20test"  
  2. encodeURI("q=blank test")        // "q=blank%20test"  
  3. new URLSearchParams("q=blank test").toString() // "q=blank+test" 

代碼是不會(huì)說(shuō)謊的,其實(shí)上面的結(jié)果都是正確的,encode 結(jié)果不一樣,是因?yàn)?URI 規(guī)范和 W3C 規(guī)范沖突了,才會(huì)搞出這種讓人疑惑的烏龍事件。

2.沖突的協(xié)議

我們首先看看 URI 中的保留字,這些保留字不參與編碼。保留字符一共有兩大類:

  •  gen-delims:: / ? # [ ] @
  •  sub-delims:! $ & ' ( ) * + , ; =

URI 的編碼規(guī)則也很簡(jiǎn)單,先把非限定范圍的字符轉(zhuǎn)為 16 進(jìn)制,然后前面加百分號(hào)。

空格這種不安全字符轉(zhuǎn)為十六進(jìn)制就是 0x20,前面再加上百分號(hào) % 就是 %20:

所以這時(shí)候再看 encodeURIComponent 和 encodeURI 的編碼結(jié)果,就是完全正確的。

既然空格轉(zhuǎn)為%20 是正確的,那轉(zhuǎn)為 + 是怎么回事?這時(shí)候我們就要了解一下 HTML form 表單的歷史。

早期的網(wǎng)頁(yè)沒(méi)有 AJAX 的時(shí)候,提交數(shù)據(jù)都是通過(guò) HTML 的 form 表單。form 表單的提交方法可以用 GET 也可以用 POST,大家可以在 MDN form 詞條上測(cè)試:

經(jīng)過(guò)測(cè)試我們可以看出表單提交的內(nèi)容中,空格都是轉(zhuǎn)為加號(hào)的,這種編碼類型就是 application/x-www-form-urlencoded,在 WHATWG 規(guī)范里是這樣定義的:

到這里基本上就破案了,URLSearchParams 做 encode 的時(shí)候,就按這個(gè)規(guī)范來(lái)的。我找到了 URLSearchParams 的 Polyfill 代碼,里面就做了 %20 到 + 的映射: 

  1. replace = {  
  2.     '!': '%21',  
  3.     "'": '%27',  
  4.     '(': '%28',  
  5.     ')': '%29',  
  6.     '~': '%7E',  
  7.     '%20': '+', // <= 就是這個(gè)  
  8.     '%00': '\x00'  

規(guī)范里對(duì)這個(gè)編碼類型還有解釋說(shuō)明:

The application/x-www-form-urlencoded format is in many ways an aberrant monstrosity, the result of many years of implementation accidents and compromises leading to a set of requirements necessary for interoperability, but in no way representing good design practices. In particular, readers are cautioned to pay close attention to the twisted details involving repeated (and in some cases nested) conversions between character encodings and byte sequences. Unfortunately the format is in widespread use due to the prevalence of HTML forms.

這種編碼方式就不是個(gè)好的設(shè)計(jì),不幸的是隨著 HTML form 表單的普及,這種格式已經(jīng)推廣開(kāi)了

其實(shí)上面一大段句話就是一個(gè)意思:這玩意兒設(shè)計(jì)的就是 💩,但積重難返,大家還是忍一下吧

3.一句話總結(jié)

  •  URI 規(guī)范里,空格 encode 為 %20, application/x-www-form-urlencoded 格式里,空格 encode 為 +
  •  實(shí)際業(yè)務(wù)開(kāi)發(fā)時(shí),最好使用業(yè)內(nèi)成熟的 HTTP 請(qǐng)求庫(kù)封裝請(qǐng)求,這些雜活兒累活兒框架都干了;
  •  如果非要使用原生 AJAX 提交 application/x-www-form-urlencoded 格式的數(shù)據(jù),不要手動(dòng)拼接參數(shù),要用 URLSearchParams 處理數(shù)據(jù),這樣可以避免各種惡心的編碼沖突。

三.X-Forwarded-For 拿到的就是真實(shí) IP 嗎?

1.故事

在這個(gè)小節(jié)開(kāi)始前,我先講一個(gè)開(kāi)發(fā)中的小故事,可以加深一下大家對(duì)這個(gè)字段的理解。

前段時(shí)間要做一個(gè)和風(fēng)控相關(guān)的需求,需要拿到用戶的 IP,開(kāi)發(fā)后灰度了一小部分用戶,測(cè)試發(fā)現(xiàn)后臺(tái)日志里灰度的用戶 IP 全是異常的,哪有這么巧的事情。隨后測(cè)試發(fā)過(guò)來(lái)幾個(gè)異常 IP: 

  1. 10.148.2.122  
  2. 10.135.2.38  
  3. 10.149.12.33  
  4. ... 

一看 IP 特征我就明白了,這幾個(gè) IP 都是 10 開(kāi)頭的,屬于 A 類 IP 的私有 IP 范圍(10.0.0.0-10.255.255.255),后端拿到的肯定是代理服務(wù)器的 IP,而不是用戶的真實(shí) IP。

2.原理

現(xiàn)在有些規(guī)模的網(wǎng)站基本都不是單點(diǎn) Server 了,為了應(yīng)對(duì)更高的流量和更靈活的架構(gòu),應(yīng)用服務(wù)一般都是隱藏在代理服務(wù)器之后的,比如說(shuō) Nginx。

加入接入層后,我們就能比較容易的實(shí)現(xiàn)多臺(tái)服務(wù)器的負(fù)載均衡和服務(wù)升級(jí),當(dāng)然還有其他的好處,比如說(shuō)更好的內(nèi)容緩存和安全防護(hù),不過(guò)這些不是本文的重點(diǎn)就不展開(kāi)了。

網(wǎng)站加入代理服務(wù)器后,除了上面的幾個(gè)優(yōu)點(diǎn),同時(shí)引入了一些新的問(wèn)題。比如說(shuō)之前的單點(diǎn) Server,服務(wù)器是可以直接拿到用戶的 IP 的,加入代理層后,如上圖所示,(應(yīng)用)原始服務(wù)器拿到的是代理服務(wù)器的 IP,我前面講的故事的問(wèn)題就出在這里。

Web 開(kāi)發(fā)這么成熟的領(lǐng)域,肯定是有現(xiàn)成的解決辦法的,那就是 X-Forwarded-For 請(qǐng)求頭。

X-Forwarded-For 是一個(gè)事實(shí)標(biāo)準(zhǔn),雖然沒(méi)有寫(xiě)入 HTTP RFC 規(guī)范里,從普及程度上看其實(shí)可以算 HTTP 規(guī)范了。

這個(gè)標(biāo)準(zhǔn)是這樣定義的,每次代理服務(wù)器轉(zhuǎn)發(fā)請(qǐng)求到下一個(gè)服務(wù)器時(shí),要把代理服務(wù)器的 IP 寫(xiě)入 X-Forwarded-For 中,這樣在最末端的應(yīng)用服務(wù)收到請(qǐng)求時(shí),就會(huì)得到一個(gè) IP 列表: 

  1. X-Forwarded-For: client, proxy1, proxy2 

因?yàn)?IP 是一個(gè)一個(gè)依次 push 進(jìn)去的,那么第一個(gè) IP 就是用戶的真實(shí) IP,取來(lái)用就好了。

但是,事實(shí)有這么簡(jiǎn)單嗎?

3.攻擊

從安全的角度上考慮,整個(gè)系統(tǒng)最不安全的就是人,用戶端都是最好攻破最好偽造的。有些用戶就開(kāi)始鉆協(xié)議的漏洞:X-Forwarded-For 是代理服務(wù)器添加的,如果我一開(kāi)始請(qǐng)求的 Header 頭里就加了 X-Forwarded-For ,不就騙過(guò)服務(wù)器了嗎?

1. 首先從客戶端發(fā)出請(qǐng)求,帶有 X-Forwarded-For 請(qǐng)求頭,里面寫(xiě)一個(gè)偽造的 IP: 

  1. X-Forwarded-For: fakeIP 

2. 服務(wù)端第一層代理服務(wù)收到請(qǐng)求,發(fā)現(xiàn)已經(jīng)有 X-Forwarded-For,誤把這個(gè)請(qǐng)求當(dāng)成代理服務(wù)器,于是向這個(gè)字段追加了客戶端的真實(shí) IP: 

  1. X-Forwarded-For: fakeIP, client 

3. 經(jīng)過(guò)幾層代理后,最終的服務(wù)器拿到的 Header 是這樣的: 

  1. X-Forwarded-For: fakeIP, client, proxy1, proxy2 

要是按照取 X-Forwarded-For 第一個(gè) IP 的思路,你就著了攻擊者的道了,你拿到的是 fakeIP,而不是 client IP。

4.破招

服務(wù)端如何破招?上面三個(gè)步驟:

  •  第一步是客戶端造假,服務(wù)器無(wú)法介入
  •  第二步是代理服務(wù)器,可控,可防范
  •  第三步是應(yīng)用服務(wù)器,可控,可防范

第二步的破招我拿 Nginx 服務(wù)器舉例。

我們?cè)谧钔鈱拥?Nginx 上,對(duì) X-Forwarded-For 的配置如下:

  1. proxy_set_header X-Forwarded-For $remote_addr; 

什么意思呢?就是最外層代理服務(wù)器不信任客戶端的 X-Forwarded-For 輸入,直接覆蓋,而不是追加。

非最外層的 Nginx 服務(wù)器,我們配置: 

  1. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 

$proxy_add_x_forwarded_for 就是追加 IP 的意思。通過(guò)這招,就可以破除用戶端的偽造辦法。

第三步的破招思路也很容易,正常思路我們是取X-Forwarded-For 最左側(cè)的 IP,這次我們反其道而行之,從右邊數(shù),減去代理服務(wù)器的數(shù)目,那么剩下的 IP 里,最右邊的就是真實(shí) IP。 

  1. X-Forwarded-For: fakeIP, client, proxy1, proxy2 

比如說(shuō)我們已知代理服務(wù)有兩層,從右向左數(shù),把 proxy1 和 proxy2 去掉,剩下的 IP 列表最右邊的就是真實(shí) IP。

相關(guān)思路和代碼實(shí)現(xiàn)可參考 Egg.js 前置代理模式。

5.一句話總結(jié)總結(jié)

通過(guò) X-Forwarded-For 獲取用戶真實(shí) IP 時(shí),最好不要取第一個(gè) IP,以防止用戶偽造 IP。

四.略顯混亂的分隔符

1.HTTP 標(biāo)準(zhǔn)

HTTP 請(qǐng)求頭字段如果涉及到多個(gè) value 時(shí),一般來(lái)說(shuō)每個(gè) value 間是用逗號(hào)「,」分隔的,就連非 RFC 標(biāo)準(zhǔn)的 X-Forwarded-For,也是用逗號(hào)分隔 value 的: 

  1. Accept-Encoding: gzip, deflate, br  
  2. cache-control: public, max-age=604800s-maxage=43200  
  3. X-Forwarded-For: fakeIP, client, proxy1, proxy2 

因?yàn)橐婚_(kāi)始用逗號(hào)分隔 value,后面想再用一個(gè)字段修飾 value 時(shí),分隔符就變成了分號(hào)「;」,最典型的請(qǐng)求頭就是 Accept 了: 

  1. //  q=0.9 修飾的是 application/xml,雖然它們之間用分號(hào)分隔  
  2. Accept: text/html, application/xml;q=0.9, */*;q=0.8   

雖然 HTTP 協(xié)議易于閱讀,但是這個(gè)分隔符用的還是很不符合常識(shí)的。按常理來(lái)說(shuō),分號(hào)的斷句語(yǔ)氣是強(qiáng)于逗號(hào)的,但是在 HTTP 內(nèi)容協(xié)商的相關(guān)字段里卻是反過(guò)來(lái)的。這里的定義可以看 RFC 7231,寫(xiě)的還是比較清楚的。

2.Cookie 標(biāo)準(zhǔn)

和常規(guī)認(rèn)識(shí)不同,Cookie 其實(shí)不算 HTTP 標(biāo)準(zhǔn),定義 Cookie 的規(guī)范是 RFC 6265,所以分隔符規(guī)則也不一樣了。規(guī)范里定義的 Cookie 語(yǔ)法規(guī)則是這樣的: 

  1. cookie-header = "Cookie:" OWS cookie-string OWS  
  2. cookiecookie-string = cookie-pair *( ";" SP cookie-pair ) 

多個(gè) cookie 之間是用分號(hào)「;」分隔的,而不是逗號(hào)「,」。我隨便扒了個(gè)網(wǎng)站的 cookie,可見(jiàn)是用分號(hào)分隔的,這里需要特別注意一下:

3.一句話總結(jié)

  •  大部分 HTTP 字段的 value 分隔符是逗號(hào)「,」
  •  Cookie 不屬于 HTTP 標(biāo)準(zhǔn),分隔符是分號(hào)「;」

五、文章推薦

下面我要推薦我的幾篇文章:

    一篇介紹了 webpack 中最易混淆的 5 個(gè)知識(shí)點(diǎn),掘金快 800 贊了,一文講清楚 Webpack 中那些長(zhǎng)得像卻意義不同的概念

    一篇詳細(xì)介紹了 webpack dll 是個(gè)什么東西,并且給出了 2 條最佳實(shí)踐,擺脫繁瑣的 dll 配置

    React Native 性能優(yōu)化指南從渲染層的角度分析了 RN 性能優(yōu)化的 6 個(gè)點(diǎn),并以圖文形式講解了 FlatList 的實(shí)現(xiàn)原理

    Web Scraper——輕量數(shù)據(jù)爬取利器 介紹了一個(gè)小巧的瀏覽器爬蟲(chóng)插件,可以實(shí)現(xiàn)簡(jiǎn)單的數(shù)據(jù)爬取功能 

 

責(zé)任編輯:龐桂玉 來(lái)源: segmentfault
相關(guān)推薦

2022-05-15 08:13:50

Mysql數(shù)據(jù)庫(kù)Mycat

2017-08-04 17:07:32

JavaArraysList

2015-04-13 17:39:11

移動(dòng)IM開(kāi)發(fā)

2017-07-19 14:26:01

前端JavaScriptDOM

2021-09-07 14:35:48

DevSecOps開(kāi)源項(xiàng)目

2020-04-21 15:18:11

財(cái)務(wù)信息化

2015-03-12 09:51:09

CoreDataiCloud

2018-07-30 16:18:51

容災(zāi)備份

2017-03-31 10:27:08

推送服務(wù)移動(dòng)

2017-07-06 11:41:48

CIOIT技術(shù)

2013-04-12 15:59:33

2011-12-02 10:32:23

Java

2013-07-17 10:11:47

云計(jì)算

2017-02-23 08:08:58

2016-12-28 13:19:08

Android開(kāi)發(fā)坑和小技巧

2011-12-15 09:45:21

PhoneGap

2011-12-22 19:57:38

PhoneGap

2018-02-06 08:36:02

簡(jiǎn)歷程序員面試

2017-08-28 15:30:49

Android編碼器編碼

2018-07-05 06:02:38

綜合布線弱電動(dòng)力線
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)