最新消息:图 床

Use DNS Rebinding to Bypass SSRF in Java

COOL IAM 178浏览 0评论

作者:JoyChou@美聯安全

0x00 前言

本篇文章會比較詳細的介紹,如何使用 DNS Rebinding 繞過 Java 中的 SSRF。網上有蠻多資料介紹用該方法繞過常規的 SSRF,但是由於 Java 的機制和 PHP 等語言不太一樣。所以,我覺得,有必要單獨拿出來聊一聊,畢竟目前很多甲方公司業務代碼都是 Java。

0x01 SSRF修復邏輯

  1. 取URL的Host
  2. 取Host的IP
  3. 判斷是否是內網IP,是內網IP直接return,不再往下執行
  4. 請求URL
  5. 如果有跳轉,取出跳轉URL,執行第1步
  6. 正常的業務邏輯里,當判斷完成最後會去請求URL,實現業務邏輯。

所以,其中會發起 DNS 請求的步驟為,第2、4、6步,看來至少要請求3次。因為第6步至少會執行1次 DNS 請求。

另外,網上有很多不嚴謹的 SSRF 修復邏輯不會判斷跳轉,導致可以被 Bypass。

0x02 DNS Rebinding

我個人理解如下:

通過自己搭建 DNS 服務器,返回自己定義的 IP,進行一些限制的繞過。

所以,我們可以利用 DNS Rebinding 在第一次發起 DNS 請求時,返回外網 IP,後面全部返回內網 IP 這種方式來繞過如上的修復邏輯。

我們來看下是如何繞過的。

首先,修復邏輯中第2步發起 DNS 請求,DNS服務器返回一個外網 IP,通過驗證,執行到第四步。

接着,修復邏輯中第4步會發起 DNS 請求,DNS服務器返回一個內網 IP。此時,SSRF 已經產生。

TTL

不過,這一切都是在 TTL 為0的前提下。

什麼是TTL?

TTL(Time To Live)是 DNS 緩存的時間。簡單理解,假如一個域名的 TTL 為10s,當我們在這10s內,對該域名進行多次 DNS 請求,DNS 服務器,只會收到一次請求,其他的都是緩存。

所以搭建的 DNS 服務器,需要設置 TTL 為0。如果不設置 TTL 為0,第二次 DNS 請求返回的是第一次緩存的外網 IP,也就不能繞過了。

DNS請求過程

步驟如下:

  1. 查詢本地 DNS 服務器(/etc/resolv.conf)
  2. 如果有緩存,返回緩存的結果,不繼續往下執行
  3. 如果沒有緩存,請求遠程 DNS 服務器,並返回結果
DNS緩存機制

平時使用的 MAC 和 Windows 電腦上,為了加快 HTTP 訪問速度,系統都會進行 DNS 緩存。但是,在 Linux 上,默認不會進行 DNS 緩存,除非運行 nscd 等軟件。

不過,知道 Linux 默認不進行 DNS 緩存即可。這也解釋了,我為什麼同樣的配置,我在 MAC 上配置不成功,Linux 上配置可以。

需要注意的是,IP 為8.8.8.8的 DNS 地址,本地不會進行 DNS 緩存。

0x03 漏洞測試

準備如下環境:

  • Java Web應用
  • DNS服務器

我們要先了解下 Java 應用的 TTL。Java 應用的默認 TTL 為10s,這個默認配置會導致 DNS Rebinding 繞過失敗。也就是說,默認情況下,Java 應用不受 DNS Rebinding 影響。

Java TTL的值可以通過下面兩種方式進行修改:

  1. 修改/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/security/java.security(我MAC下的路徑)里的networkaddress.cache.negative.ttl=0

  2. 通過代碼進行修改java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "0");

這個地方是個大坑,我之前在測試時,一直因為這個原因,導致測試不成功。

這也是利用 DNS Rebinding 過程中,Java 和 PHP 不一樣的地方。在測試 PHP 時,這份 PHP 代碼用 DNS Rebinding 可以繞過,類似的代碼 Java 就不能被繞過了。

SSRF漏洞搭建

用 Java Spring 寫了一個漏洞測試地址為
http://test.joychou.org:8080/checkssrf?url=http://dns_rebind.joychou.me。URL會進行SSRF驗證。

SSRF修復代碼如下。也可以在Github上查看

/*
        * check SSRF (判斷邏輯為判斷URL的IP是否是內網IP)
        * 如果是內網IP,返回false,表示checkSSRF不通過。否則返回true。即合法返回true
        * URL只支持HTTP協議
        * 設置了訪問超時時間為3s
     */
    public static Boolean checkSSRF(String url) {

        HttpURLConnection connection;
        String finalUrl = url;
        try {
            do {
                // 判斷當前請求的URL是否是內網ip
                Boolean bRet = isInnerIpFromUrl(finalUrl);
                if (bRet) {
                    return false;
                }

                connection = (HttpURLConnection) new URL(finalUrl).openConnection();
                connection.setInstanceFollowRedirects(false);
                connection.setUseCaches(false); // 設置為false,手動處理跳轉,可以拿到每個跳轉的URL
                connection.setConnectTimeout(3*1000); // 設置連接超時時間為3s
                //connection.setRequestMethod("GET");
                connection.connect(); // send dns request
                int responseCode = connection.getResponseCode(); // 發起網絡請求 no dns request
                if (responseCode >= 300 && responseCode < 400) {
                    String redirectedUrl = connection.getHeaderField("Location");
                    if (null == redirectedUrl)
                        break;
                    finalUrl = redirectedUrl;
                    // System.out.println("redirected url: " + finalUrl);
                } else
                    break;
            } while (connection.getResponseCode() != HttpURLConnection.HTTP_OK);
            connection.disconnect();
        } catch (Exception e) {
            return true;
        }
        return true;
    }

    /*
        內網IP:
        10.0.0.1 - 10.255.255.254       (10.0.0.0/8)
        192.168.0.1 - 192.168.255.254   (192.168.0.0/16)
        127.0.0.1 - 127.255.255.254     (127.0.0.0/8)
        172.16.0.1 - 172.31.255.254     (172.16.0.0/12)
    */
    public static boolean isInnerIp(String strIP) throws IOException {
        try{
            String[] ipArr = strIP.split("//.");
            if (ipArr.length != 4){
                return false;
            }

            int ip_split1 = Integer.parseInt(ipArr[1]);

            return (ipArr[0].equals("10") ||
                    ipArr[0].equals("127") ||
                    (ipArr[0].equals("172") && ip_split1 >= 16 && ip_split1 <=31) ||
                    (ipArr[0].equals("192") && ipArr[1].equals("168")));
        }catch (Exception e) {
            return false;
        }

    }
    /*
        * 域名轉換為IP
        * 會將各種進制的ip轉為正常ip
        * 167772161轉換為10.0.0.1
        * 127.0.0.1.xip.io轉換為127.0.0.1
    */
    public static String DomainToIP(String domain) throws IOException{
        try {
            InetAddress IpAddress = InetAddress.getByName(domain); //  send dns request
            return IpAddress.getHostAddress();
        }
        catch (Exception e) {
            return "";
        }
    }

    /*
        從URL中獲取域名
        限制為http/https協議
    */
    public static String getUrlDomain(String url) throws IOException{
        try {
            URL u = new URL(url);
            if (!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")) {
                throw new IOException("Protocol error: " + u.getProtocol());
            }
            return u.getHost();
        } catch (Exception e) {
            return "";
        }

    }
搭建DNS服務器

域名配置如下:

转载请注明:IAMCOOL » Use DNS Rebinding to Bypass SSRF in Java

0 0 vote
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x