Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
TTP 協(xié)議可以說是開發(fā)者最熟悉的一個(gè)網(wǎng)絡(luò)協(xié)議,「簡單易懂」和「易于擴(kuò)展」兩個(gè)特點(diǎn)讓它成為應(yīng)用最廣泛的應(yīng)用層協(xié)議。
雖然有諸多的優(yōu)點(diǎn),但是在協(xié)議定義時(shí)因?yàn)橹T多的博弈和限制,還是隱藏了不少暗坑,讓人一不小心就會陷入其中。本文總結(jié)了 HTTP 規(guī)范中常見的幾個(gè)暗坑,希望大家開發(fā)中有意識的規(guī)避它們,提升開發(fā)體驗(yàn)。
HTTP 標(biāo)準(zhǔn)把 Referrer 寫成 Referer(少些了一個(gè) r),可以說是計(jì)算機(jī)歷史上最著名的一個(gè)錯(cuò)別字了。
Referer 的主要作用是攜帶當(dāng)前請求的來源地址,常用在防爬蟲和防盜鏈上。前段時(shí)間鬧的沸沸揚(yáng)揚(yáng)的新浪圖床掛圖事件,就是因?yàn)樾吕藞D床突然開始檢查 HTTP Referer 頭,非新浪域名就不返回圖片,導(dǎo)致很多蹭流量的中小博客圖都掛了。
雖然 HTTP 標(biāo)準(zhǔn)里把 Referer 寫錯(cuò)了,但是其它可以控制 Referer 的標(biāo)準(zhǔn)并沒有將錯(cuò)就錯(cuò)。
例如禁止網(wǎng)頁自動攜帶 Referer 頭的 <meta> 標(biāo)簽,相關(guān)關(guān)鍵字拼寫就是正確的:
<!-- 全局禁止發(fā)送 referrer -->
<meta name="referrer" content="no-referrer" />
還有一個(gè)值得注意的是瀏覽器的網(wǎng)絡(luò)請求。從安全性和穩(wěn)定性上考慮,Referer 等請求頭在網(wǎng)絡(luò)請求時(shí),只能由瀏覽器控制,不能直接操作,我們只能通過一些屬性進(jìn)行控制。比如說 Fetch 函數(shù),我們可以通過 referrer 和 referrerPolicy 控制,而它們的拼寫也是正確的:
fetch('/page', {
headers: {
"Content-Type": "text/plain;charset=UTF-8"
},
referrer: "https://demo.com/anotherpage", // <-
referrerPolicy: "no-referrer-when-downgrade", // <-
});
凡是涉及到 Referrer 的,除了 HTTP 字段是錯(cuò)的,瀏覽器的相關(guān)配置字段拼寫都是正確的。
這個(gè)是個(gè)史詩級的大坑,我曾經(jīng)被這個(gè)協(xié)議沖突坑了一天。
開始講解前先看個(gè)小測試,在瀏覽器里輸入 blank test( blank 和 test 間有個(gè)空格),我們看看瀏覽器如何處理的:
從動圖可以看出瀏覽器把空格解析為一個(gè)加號「+」。
是不是感覺有些奇怪?我們再做個(gè)測試,用瀏覽器提供的幾個(gè)函數(shù)試一下:
encodeURIComponent("blank test") // "blank%20test"
encodeURI("q=blank test") // "q=blank%20test"
new URLSearchParams("q=blank test").toString() // "q=blank+test"
代碼是不會說謊的,其實(shí)上面的結(jié)果都是正確的,encode 結(jié)果不一樣,是因?yàn)?URI 規(guī)范和 W3C 規(guī)范沖突了,才會搞出這種讓人疑惑的烏龍事件。
我們首先看看 URI 中的保留字,這些保留字不參與編碼。保留字符一共有兩大類:
URI 的編碼規(guī)則也很簡單,先把非限定范圍的字符轉(zhuǎn)為 16 進(jìn)制,然后前面加百分號。
空格這種不安全字符轉(zhuǎn)為十六進(jìn)制就是 0x20,前面再加上百分號 % 就是 %20:
所以這時(shí)候再看 encodeURIComponent 和 encodeURI 的編碼結(jié)果,就是完全正確的。
既然空格轉(zhuǎn)為%20 是正確的,那轉(zhuǎn)為 + 是怎么回事?這時(shí)候我們就要了解一下 HTML form 表單的歷史。
早期的網(wǎng)頁沒有 AJAX 的時(shí)候,提交數(shù)據(jù)都是通過 HTML 的 form 表單。form 表單的提交方法可以用 GET 也可以用 POST,大家可以在 MDN form 詞條上測試:
經(jīng)過測試我們可以看出表單提交的內(nèi)容中,空格都是轉(zhuǎn)為加號的,這種編碼類型就是 application/x-www-form-urlencoded,在 WHATWG 規(guī)范里是這樣定義的:
到這里基本上就破案了,URLSearchParams 做 encode 的時(shí)候,就按這個(gè)規(guī)范來的。我找到了 URLSearchParams 的 Polyfill 代碼,里面就做了 %20 到 + 的映射:
replace={
'!': '%21',
"'": '%27',
'(': '%28',
')': '%29',
'~': '%7E',
'%20': '+', // <=就是這個(gè)
'%00': '\x00'
}
規(guī)范里對這個(gè)編碼類型還有解釋說明:
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)推廣開了
其實(shí)上面一大段句話就是一個(gè)意思:這玩意兒設(shè)計(jì)的就是 ,但積重難返,大家還是忍一下吧
在這個(gè)小節(jié)開始前,我先講一個(gè)開發(fā)中的小故事,可以加深一下大家對這個(gè)字段的理解。
前段時(shí)間要做一個(gè)和風(fēng)控相關(guān)的需求,需要拿到用戶的 IP,開發(fā)后灰度了一小部分用戶,測試發(fā)現(xiàn)后臺日志里灰度的用戶 IP 全是異常的,哪有這么巧的事情。隨后測試發(fā)過來幾個(gè)異常 IP:
10.148.2.122
10.135.2.38
10.149.12.33
...
一看 IP 特征我就明白了,這幾個(gè) IP 都是 10 開頭的,屬于 A 類 IP 的私有 IP 范圍(10.0.0.0-10.255.255.255),后端拿到的肯定是代理服務(wù)器的 IP,而不是用戶的真實(shí) IP。
現(xiàn)在有些規(guī)模的網(wǎng)站基本都不是單點(diǎn) Server 了,為了應(yīng)對更高的流量和更靈活的架構(gòu),應(yīng)用服務(wù)一般都是隱藏在代理服務(wù)器之后的,比如說 Nginx。
加入接入層后,我們就能比較容易的實(shí)現(xiàn)多臺服務(wù)器的負(fù)載均衡和服務(wù)升級,當(dāng)然還有其他的好處,比如說更好的內(nèi)容緩存和安全防護(hù),不過這些不是本文的重點(diǎn)就不展開了。
網(wǎng)站加入代理服務(wù)器后,除了上面的幾個(gè)優(yōu)點(diǎn),同時(shí)引入了一些新的問題。比如說之前的單點(diǎn) Server,服務(wù)器是可以直接拿到用戶的 IP 的,加入代理層后,如上圖所示,(應(yīng)用)原始服務(wù)器拿到的是代理服務(wù)器的 IP,我前面講的故事的問題就出在這里。
Web 開發(fā)這么成熟的領(lǐng)域,肯定是有現(xiàn)成的解決辦法的,那就是 X-Forwarded-For 請求頭。
X-Forwarded-For 是一個(gè)事實(shí)標(biāo)準(zhǔn),雖然沒有寫入 HTTP RFC 規(guī)范里,從普及程度上看其實(shí)可以算 HTTP 規(guī)范了。
這個(gè)標(biāo)準(zhǔn)是這樣定義的,每次代理服務(wù)器轉(zhuǎn)發(fā)請求到下一個(gè)服務(wù)器時(shí),要把代理服務(wù)器的 IP 寫入 X-Forwarded-For 中,這樣在最末端的應(yīng)用服務(wù)收到請求時(shí),就會得到一個(gè) IP 列表:
X-Forwarded-For: client, proxy1, proxy2
因?yàn)?IP 是一個(gè)一個(gè)依次 push 進(jìn)去的,那么第一個(gè) IP 就是用戶的真實(shí) IP,取來用就好了。
但是,事實(shí)有這么簡單嗎?
從安全的角度上考慮,整個(gè)系統(tǒng)最不安全的就是人,用戶端都是最好攻破最好偽造的。有些用戶就開始鉆協(xié)議的漏洞:X-Forwarded-For 是代理服務(wù)器添加的,如果我一開始請求的 Header 頭里就加了 X-Forwarded-For ,不就騙過服務(wù)器了嗎?
1. 首先從客戶端發(fā)出請求,帶有 X-Forwarded-For 請求頭,里面寫一個(gè)偽造的 IP:
X-Forwarded-For: fakeIP
2. 服務(wù)端第一層代理服務(wù)收到請求,發(fā)現(xiàn)已經(jīng)有 X-Forwarded-For,誤把這個(gè)請求當(dāng)成代理服務(wù)器,于是向這個(gè)字段追加了客戶端的真實(shí) IP:
X-Forwarded-For: fakeIP, client
3. 經(jīng)過幾層代理后,最終的服務(wù)器拿到的 Header 是這樣的:
X-Forwarded-For: fakeIP, client, proxy1, proxy2
要是按照取 X-Forwarded-For 第一個(gè) IP 的思路,你就著了攻擊者的道了,你拿到的是 fakeIP,而不是 client IP。
服務(wù)端如何破招?上面三個(gè)步驟:
第二步的破解我拿 Nginx 服務(wù)器舉例。
我們在最外層的 Nginx 上,對 X-Forwarded-For 的配置如下:
proxy_set_header X-Forwarded-For $remote_addr;
什么意思呢?就是最外層代理服務(wù)器不信任客戶端的 X-Forwarded-For 輸入,直接覆蓋,而不是追加。
非最外層的 Nginx 服務(wù)器,我們配置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
$proxy_add_x_forwarded_for 就是追加 IP 的意思。通過這招,就可以破解用戶端的偽造辦法。
第三步的破解思路也很容易,正常思路我們是取X-Forwarded-For 最左側(cè)的 IP,這次我們反其道而行之,從右邊數(shù),減去代理服務(wù)器的數(shù)目,那么剩下的 IP 里,最右邊的就是真實(shí) IP。
X-Forwarded-For: fakeIP, client, proxy1, proxy2
比如說我們已知代理服務(wù)有兩層,從右向左數(shù),把 proxy1 和 proxy2 去掉,剩下的 IP 列表最右邊的就是真實(shí) IP。
相關(guān)思路和代碼實(shí)現(xiàn)可參考 Egg.js 前置代理模式。
通過 X-Forwarded-For 獲取用戶真實(shí) IP 時(shí),最好不要取第一個(gè) IP,以防止用戶偽造 IP。
HTTP 請求頭字段如果涉及到多個(gè) value 時(shí),一般來說每個(gè) value 間是用逗號「,」分隔的,就連非 RFC 標(biāo)準(zhǔn)的 X-Forwarded-For,也是用逗號分隔 value 的:
Accept-Encoding: gzip, deflate, br
cache-control: public, max-age=604800, s-maxage=43200
X-Forwarded-For: fakeIP, client, proxy1, proxy2
因?yàn)橐婚_始用逗號分隔 value,后面想再用一個(gè)字段修飾 value 時(shí),分隔符就變成了分號「;」,最典型的請求頭就是 Accept 了:
// q=0.9 修飾的是 application/xml,雖然它們之間用分號分隔
Accept: text/html, application/xml;q=0.9, */*;q=0.8
雖然 HTTP 協(xié)議易于閱讀,但是這個(gè)分隔符用的還是很不符合常識的。按常理來說,分號的斷句語氣是強(qiáng)于逗號的,但是在 HTTP 內(nèi)容協(xié)商的相關(guān)字段里卻是反過來的。這里的定義可以看 RFC 7231,寫的還是比較清楚的。
和常規(guī)認(rèn)識不同,Cookie 其實(shí)不算 HTTP 標(biāo)準(zhǔn),定義 Cookie 的規(guī)范是 RFC 6265,所以分隔符規(guī)則也不一樣了。規(guī)范里定義的 Cookie 語法規(guī)則是這樣的:
cookie-header="Cookie:" OWS cookie-string OWS
cookie-string=cookie-pair *( ";" SP cookie-pair )
多個(gè) cookie 之間是用分號「;」分隔的,而不是逗號「,」。我隨便扒了個(gè)網(wǎng)站的 cookie,可見是用分號分隔的,這里需要特別注意一下:
之前在漏洞掃描中發(fā)現(xiàn)的漏洞,上網(wǎng)學(xué)習(xí)了一下如何在apache服務(wù)器中配置。雖然在華為云漏洞檢測中完美的解決了(小公司無測試,我們也不會測),把我的方式發(fā)出來。希望有明白的能看看指點(diǎn)一下。
SetEnvIfNoCase Referer "^http://localhost/" local_ref
<Location "/">
Order Allow,Deny
Allow from env=local_ref
</Location>
<Location "/index.html">
Order Allow,Deny
Allow from all
</Location>
<Location "/dp">
Order Allow,Deny
Allow from all
</Location>
<Location "/app">
Order Allow,Deny
Allow from all
</Location>
主要就是規(guī)定好允許訪問的 Referer地址,如果地址不對則不可訪問,地址正確也只可以訪問到定好的入口。
在現(xiàn)有的測試工具中測試,華為云漏洞掃描是解決了bug,而且只能掃描到規(guī)定的入口。但是遇到更專業(yè)的工具是可以繞過的。
希望有更好辦法的hxd能指點(diǎn)指點(diǎn)
相對應(yīng)的項(xiàng)目的配置文件里面,設(shè)置以下文件:
設(shè)置->配置文件
文件放到配置文件:
#PHP-INFO-START PHP引用配置,可以注釋或修改
#SECURITY-START 防盜鏈配置
文件的上面!
文件:
server
{
listen 80;
server_name zcf.micuer.com;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/zcf.micuer.com;
#SSL-START SSL相關(guān)配置,請勿刪除或修改下一行帶注釋的404規(guī)則
#error_page 404/404.html;
limit_conn perserver 300;
limit_conn perip 25;
limit_rate 512k;
#SSL-END
#ERROR-PAGE-START 錯(cuò)誤頁配置,可以javascript:;注釋、刪除或修改
#error_page 404 /404.html;
#error_page 502 /502.html;
#ERROR-PAGE-END
#禁止相關(guān)目錄訪問 php 文件
location ~ static/(.*).(txt)$ {
return 404;
}
location ~ static/(.*).(html)$ {
return 404;
}
location ~ static/(.*).(php)$ {
return 404;
}
#PHP-INFO-START PHP引用配置,可以注釋或修改
#SECURITY-START 防盜鏈配置
location ~ .*\.(jpg|jpeg|gif|png|js|css)$
{
expires 30d;
access_log /dev/null;
valid_referers none blocked zcf.micuer.com;
if ($invalid_referer){
return 404;
}
}
#SECURITY-END
include enable-php-73.conf;
#PHP-INFO-END
#REWRITE-START URL重寫規(guī)則引用,修改后將導(dǎo)致面板設(shè)置的偽靜態(tài)規(guī)則失效
include /www/server/panel/vhost/rewrite/zcf.micuer.com.conf;
#REWRITE-END
#禁止訪問的文件或目錄
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)
{
return 404;
}
#一鍵申請SSL證書驗(yàn)證目錄相關(guān)設(shè)置
location ~ \.well-known{
allow all;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
error_log off;
access_log /dev/null;
}
location ~ .*\.(js|css)?$
{
expires 12h;
error_log off;
access_log /dev/null;
}
access_log /www/wwwlogs/zcf.micuer.com.log;
error_log /www/wwwlogs/zcf.micuer.com.error.log;
}
#禁止相關(guān)目錄訪問 php 文件
代碼就是禁止相關(guān)目錄訪問 php代碼
作者:科技小鍋蓋
鏈接:https://www.xiaoguogai.cn/detail/id/25.html
著作權(quán)歸*科技小鍋蓋*所有,任何形式的轉(zhuǎn)載都請注明出處。
*請認(rèn)真填寫需求信息,我們會在24小時(shí)內(nèi)與您取得聯(lián)系。