收到了一個漏洞報告,內容是外部可以查看到反向代理前的內部 IP。
在筆者建立的測試環境中,外部會向 192.168.0.210 的 Nginx 發送請求,Nginx 會將請求轉送給 172.23.0.6 主機。如果這個請求沒有身份驗證訊息,就會要求要登入,會在回應的 Header 中夾帶 Location 資訊,而 Location 的內容則是指向登入頁面,瀏覽器看到有 Location 資訊就會自動重定向網頁到指定的頁面。
實際情況:發送請求給 http://192.168.0.210:8000 ,沒有登入就會在 Header 中夾帶 Location ,內容是 http://192.168.0.210:8000/login ,瀏覽器就會把使用者的畫面跳轉到登入頁面。
筆者一開始想到的是在正常的請求中用什麼 host 進來,就會是當前的 host 並且再加上目標頁面,例如 192.168.0.1 進來,就會導向 http://192.168.0.1/login。
在 curl 中可以使用 -H 指定 host 假裝是 192.168.0.1 :
在回應中 Location 就會變成 http://192.168.0.1/login
但是這並不能解釋為什麼外部可以知道內部的 IP ,因為你必須要先知道內部 IP ,才能使用上面的方式得到這樣的結果。
範例輸出:
難道只能使用 netcat 才能重現嗎?又經過一番研究,筆者也找到使用 curl 重現的方式:
重點在於要使用 HTTP 1.0 ,並且不要帶入 Host ,因為 curl 指令會自動帶入 Host 所以無法重現,這裡需要手動塞入空的 Host 才可以。
範例輸出:
在 server 區塊中增加以下內容:
這裡修改的設定是手動指定 server_name ,並且在重定向時自動使用剛剛設定的 server_name ,這樣就不會暴露其他 host 了。
測試設定檔是否修改正確:
重新啟動 nginx:
如果出現以下錯誤:
可以先使用以下指令找到 nginx 的位置:
範例輸出:
然後執行以下指令重新讀取設定檔:
(剛剛查詢出來的結果為 /usr/sbin/nginx ,如果結果不同請自行替換:)
修改完成後我們可以再執行一次,檢查 Location 是否有洩漏
範例輸出:
Location 已經自動被替換為 server_name 了,修改成功!
參考資料:
Web Server HTTP Header Internal IP Disclosure
thorntech.com - HTTP Private IP Disclosure
在筆者建立的測試環境中,外部會向 192.168.0.210 的 Nginx 發送請求,Nginx 會將請求轉送給 172.23.0.6 主機。如果這個請求沒有身份驗證訊息,就會要求要登入,會在回應的 Header 中夾帶 Location 資訊,而 Location 的內容則是指向登入頁面,瀏覽器看到有 Location 資訊就會自動重定向網頁到指定的頁面。
實際情況:發送請求給 http://192.168.0.210:8000 ,沒有登入就會在 Header 中夾帶 Location ,內容是 http://192.168.0.210:8000/login ,瀏覽器就會把使用者的畫面跳轉到登入頁面。
筆者一開始想到的是在正常的請求中用什麼 host 進來,就會是當前的 host 並且再加上目標頁面,例如 192.168.0.1 進來,就會導向 http://192.168.0.1/login。
在 curl 中可以使用 -H 指定 host 假裝是 192.168.0.1 :
curl -v -H "Host: 192.168.0.1" http://192.168.0.210
在回應中 Location 就會變成 http://192.168.0.1/login
但是這並不能解釋為什麼外部可以知道內部的 IP ,因為你必須要先知道內部 IP ,才能使用上面的方式得到這樣的結果。
重現漏洞情況
經過一番研究,使用 Linux 中的 netcat 工具(簡稱 nc),用來發送 HTTP 請求,就可以重現這個漏洞,可以查看到反向代理內部的 IP:
echo -ne "GET / HTTP/1.0\r\n\r\n" | nc 192.168.0.210 8000
範例輸出:
echo -ne "GET / HTTP/1.0\r\n\r\n" | nc 192.168.0.210 8000
HTTP/1.1 302 Found
Server: nginx
Content-Type: text/html; charset=UTF-8
Connection: close
Cache-Control: private, must-revalidate
Date: Tue, 26 Nov 2024 05:46:33 GMT
Location: http://172.23.0.6:8000/login
pragma: no-cache
expires: -1
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url='http://172.23.0.6:8000/login'" />
<title>Redirecting to http://172.23.0.6:8000/login</title>
</head>
<body>
Redirecting to <a href="http://172.23.0.6:8000/login">http://172.23.0.6:8000/login</a>.
</body>
</html>
難道只能使用 netcat 才能重現嗎?又經過一番研究,筆者也找到使用 curl 重現的方式:
curl -v --http1.0 -H "Host:" http://192.168.0.210:8000
重點在於要使用 HTTP 1.0 ,並且不要帶入 Host ,因為 curl 指令會自動帶入 Host 所以無法重現,這裡需要手動塞入空的 Host 才可以。
範例輸出:
curl -v --http1.0 -H "Host:" http://192.168.0.210:8000
* Trying 192.168.0.210:8000...
* Connected to 192.168.0.210 (192.168.0.210) port 8000 (#0)
> GET / HTTP/1.0
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Server: nginx
< Content-Type: text/html; charset=UTF-8
< Connection: close
< Cache-Control: private, must-revalidate
< Date: Tue, 26 Nov 2024 05:45:53 GMT
< Location: http://172.23.0.6:8000/login
< pragma: no-cache
< expires: -1
<
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url='http://172.23.0.6:8000/login'" />
<title>Redirecting to http://172.23.0.6:8000/login</title>
</head>
<body>
Redirecting to <a href="http://172.23.0.6:8000/login">http://172.23.0.6:8000/login</a>.
</body>
* Closing connection 0
</html>
解決方式
修改 Nginx 設定檔:
vi /etc/nginx/nginx.conf
在 server 區塊中增加以下內容:
server {
listen 8000;
listen [::]:8000;
server_name 192.168.0.210
server_name_in_redirect on;
}
這裡修改的設定是手動指定 server_name ,並且在重定向時自動使用剛剛設定的 server_name ,這樣就不會暴露其他 host 了。
測試設定檔是否修改正確:
nginx -t
重新啟動 nginx:
systemctl reload nginx
如果出現以下錯誤:
systemctl reload nginx
bash: systemctl: command not found
可以先使用以下指令找到 nginx 的位置:
which nginx
範例輸出:
which nginx
/usr/sbin/nginx
然後執行以下指令重新讀取設定檔:
(剛剛查詢出來的結果為 /usr/sbin/nginx ,如果結果不同請自行替換:)
/usr/sbin/nginx -s reload
修改完成後我們可以再執行一次,檢查 Location 是否有洩漏
範例輸出:
curl -v --http1.0 -H "Host:" http://192.168.0.210:8000
* Trying 192.168.0.210:8000...
* Connected to 192.168.0.210 (192.168.0.210) port 8000 (#0)
> GET / HTTP/1.0
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Server: nginx
< Content-Type: text/html; charset=UTF-8
< Connection: close
< Cache-Control: private, must-revalidate
< Date: Tue, 26 Nov 2024 05:43:40 GMT
< Location: http://192.168.0.210:8000/login
< pragma: no-cache
< expires: -1
<
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url='http://192.168.0.210:8000/login'" />
<title>Redirecting to http://192.168.0.210:8000/login</title>
</head>
<body>
Redirecting to <a href="http://192.168.0.210:8000/login">http://192.168.0.210:8000/login</a>.
</body>
* Closing connection 0
</html>
Location 已經自動被替換為 server_name 了,修改成功!
參考資料:
Web Server HTTP Header Internal IP Disclosure
thorntech.com - HTTP Private IP Disclosure
留言
張貼留言
如果有任何問題、建議、想說的話或文章題目推薦,都歡迎留言或來信: a@ruyut.com