來源:ricterz.me
作者:ricterz
0x00 前言
在不久前的一個滲透測試中,我遇到一個客戶自己實現的代理。這個代理可以用來翻墻,但是由于這個代理搭建在客戶內網中,所以同樣可以訪問內網資源。 報告給客戶后,客戶予以修復。在之后的復測中,訪問內網資源的時候返回 403,只能請求非黑名單 IP 段中的地址。
>>> export all_proxy=http://u:p@proxy_server:1234
>>> curl 10.0.0.1 -v
* Rebuilt URL to: 10.0.0.1/
* Trying proxy_server...
* TCP_NODELAY set
* Connected to proxy_server (proxy_server) port 1234 (#0)
* Proxy auth using Basic with user 'u'
> GET http://10.0.0.1/ HTTP/1.1
> Host: 10.0.0.1
> Proxy-Authorization: Basic dTpw
> User-Agent: curl/7.51.0
> Accept: */*
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 403 Forbidden
< Content-Type: text/plain; charset=utf-8
< X-Content-Type-Options: nosniff
< Date: Mon, 11 Dec 2016 13:10:23 GMT
< Content-Length: 43
<
Request URL http://10.0.0.1/ is forbidden.
于是利用一般性的繞過方式,比如:
- http://baidu.com@10.0.0.1
- http://test.loli.club (ip: 10.0.0.1)
- 301 / 302 Redirect
- file:///etc/passwd
- gopher protocol
- ftp protocol
等一系列姿勢都以失敗告終。于是開始思考其驗證 IP 的具體方式,嘗試繞過 IP 限制請求內網。
0x01 IP 驗證方式
一般來說,驗證 IP 是否在范圍的方式如下圖所示。
獲取到請求的地址后,如果為域名的話,則通過 DNS 解析的方式獲取到真實的 IP 地址,如果直接是 IP 地址的話,則直接對比是否在指定的 IP 段內。
比如如上的 test.loli.club 請求獲得的 IP 地址為 10.0.0.1,黑名單 IP 段為 10.0.0.0/8,則會提示拒絕訪問。
一般來說這種驗證沒有什么問題,但是通過 DNS Rebinding 技術來進行攻擊的話,就可以輕而易舉地繞過這個 IP 限制。
0x02 DNS Rebinding
上圖所示的驗證方法是存在問題的。服務器從獲得請求的 URL 開始,到利用 URL 的 Hostname 獲取到 IP 地址,再從判斷 IP 地址到請求 URL 之間,是有一個時間差的。利用這個時間差,我們可以做一些事情。
眾所周知,DNS 返回的數據包中存在一個 TTL(Time-To-Live),也就是域名解析記錄在 DNS 服務器上的緩存時間。如果兩次 DNS 請求的時間大于 TTL 的大小的話,那么就會重新進行一次 DNS 解析請求。
如果我們在第一次請求 DNS 解析時返回一個不在黑名單里面的 IP 地址,然后在第二次服務端請求 URL 的時候,讓服務器再請求一次 DNS 解析,這次解析到黑名單內的地址,且沒有任何驗證,利用個短暫的時間差來繞過驗證。
我們把 DNS 服務器的 TTL 設置為 0,這樣就可以有足夠的時間來讓服務器再次請求 DNS 服務器而導致繞過 IP 黑名單限制。
0x03 攻擊配置
要進行攻擊首先需要一個域名,然后配置一個 NS 記錄,指向攻擊者配置的 DNS 服務器。
在 DNS 服務器上搭建一個 DNS 服務,核心代碼如下:
測試請求 1.asf.loli.club:
兩次 DNS 請求的結果不同。測試在實際環境中可以繞過 IP 驗證。由于保密原因就不再提供真實環境的測試圖片,但是實際上已經成功請求其內網的 gitlab、kms 等關鍵服務了。
0x04 攻擊面
- CSRF/XSS 竊取用戶數據
- 繞過 SSRF IP 限制
- 繞過代理 IP 限制
0x05 緩解措施
利用第一次請求解析的 IP 來進行后續的 HTTP/HTTPS 請求即可。
def dns_resolve(hostname):
...
def check_ip(ip):
...
url = input()
ip = dns_resolve(urlparse(url.hostname))
if not check_ip(ip):
return '403 Forbidden', 403
data = requests.get(ip, headers={'Host': url.hostname})
return data.content, data.status_code
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/188/
暫無評論