DNS Rebind 在 SSRF 中的作用
这几天沉迷 hgame, 题目质量还是不错的, 其中有一题用到了 DNS rebind, 这里重新记一下笔记. 因为我估计也没多少人看我博客 233, 就直接写了, 不等比赛结束了.
可以先看看 DNS Rebind 的 wiki 页面, 其实就跟这个名字的意思差不多, 重新绑定, 也就是说第一次请求和第二次请求返回的 IP 地址不同. 我这里直接上题目代码, 看看是如何利用的.
1@GetMapping(path={"/you_will_never_find_this_interface"})
2public String YouWillNeverFindThisInterface(@RequestParam(value="url", defaultValue="") String url)
3{
4 if (url.isEmpty()) {
5 return "`url` cant be empty!";
6 }
7try
8{
9 URL u = new URL(url);
10
11 String domain = u.getHost();
12 String ip = InetAddress.getByName(domain).getHostAddress();
13 if (ip.equals("127.0.0.1")) {
14 return "Dont be evil. Dont request 127.0.0.1.";
15 }
16 URLConnection connection = u.openConnection();
17 connection.setConnectTimeout(5000);
18 connection.setReadTimeout(5000);
19 BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
20 StringBuilder sb = new StringBuilder();
21 String current;
22 while ((current = in.readLine()) != null) {
23 sb.append(current);
24 }
25 return sb.toString();
26}
27 catch (Exception e)
28 {
29 return "emmmmmmm, something went wrong: " + e.getMessage();
30 }
31}
注意 String ip = InetAddress.getByName(domain).getHostAddress();
和 URLConnection connection = u.openConnection();.
在拿到 ip 以后, 是直接再用原来的链接打开, 而不是通过 ip 访问. 也就是说, 这里其实解析了两次域名 (如果没有缓存的话, 这个下面说)
这给我们了机会来绕过检测, 我们只要让 DNS 第一次返回一个不是 127.0.0.1
的地址, 第二次再返回 127.0.0.1
即可. 这样 u.openConnection
将会链接 127.0.0.1
, 实现 SSRF.
可以直接在 Github 上找到已有的项目, 试了一下还是不错的. 不过在这个题目下使用有点小问题, 题目这里设置的 DNS 服务器是 8.8.8.8
, 而 8.8.8.8
在递归的时候请求了一个不支持的类型 \x00\xff, 查了一下 RFC, 是这个
13.2.3. QTYPE values
2
3QTYPE fields appear in the question part of a query. QTYPES are a
4superset of TYPEs, hence all TYPEs are valid QTYPEs. In addition, the
5following QTYPEs are defined:
6
7AXFR 252 A request for a transfer of an entire zone
8
9MAILB 253 A request for mailbox-related records (MB, MG or MR)
10
11MAILA 254 A request for mail agent RRs (Obsolete - see MX)
12
13\* 255 A request for all records
请求所有记录, 还好不是什么奇葩的, 我们稍微修改一下, 正常返回 A 记录即可.
1@@ -52,7 +53,8 @@
2TYPE = {
3 "\x00\x0c": "PTR",
4 "\x00\x10": "TXT",
5 "\x00\x0f": "MX",
6- "\x00\x06": "SOA"
7+ "\x00\x06": "SOA",
8+ "\x00\xff": "A"
9}
10
11 # Stolen:
12@@ -346,6 +348,7 @@
13CASE = {
14 "\x00\x0c": PTR,
15 "\x00\x10": TXT,
16 "\x00\x06": SOA,
17+ "\x00\xff": A,
18}
然后设置 conf
1$ cat dns.conf
2A .* 1.1.1.1,127.0.0.1
就可以啦, 这样第一次请求返回 1.1.1.1
, 第二次 127.0.0.1
, 绕过了检测.
再说说缓存, 为了加快请求速度, 现在的操作系统都会将上次请求保存下来, 在一段时间内都会使用第一次请求的结果. 所以这种方式也有对应的局限. 我们可以看看题目的设置
1@SpringBootApplication
2public class HappyjavaApplication
3{
4 public static void main(String[] args)
5 {
6 Security.setProperty("networkaddress.cache.negative.ttl", "0");
7 Security.setProperty("networkaddress.cache.ttl", "0");
8 System.setProperty("sun.net.spi.nameservice.nameservers", "8.8.8.8");
9 System.setProperty("sun.net.spi.nameservice.provider.1", "dns,sun");
10 SpringApplication.run(HappyjavaApplication.class, args);
11 }
12}
是将 networkaddress.cache.ttl
设到了 0, 相当于关闭了缓存, 所以才能这么玩 233