|
| 1 | +--- |
| 2 | +name: secure-http-request-dns-rebinding-prevention |
| 3 | +description: Generate secure code for HTTP request preventing exposure to DNS rebinding attack. Enforces HTTP request not exposed to DNS rebinding attack. Invoke when writing HTTP request code that must only target public IP addresses. |
| 4 | +allowed-tools: Read Grep Glob |
| 5 | +metadata: |
| 6 | + category: security |
| 7 | +--- |
| 8 | + |
| 9 | +# Secure HTTP Request Code Generation Rules |
| 10 | + |
| 11 | +Apply **all** rules below when generating or reviewing any code related to performing an HTTP request. |
| 12 | + |
| 13 | +## 1. DNS rebinding prevention (CRITICAL) |
| 14 | + |
| 15 | +- ALWAYS reject any URL whose scheme is not http or https. |
| 16 | +- ALWAYS ensure that the resolved IP address of the domain is not a private or site-local one (covers RFC 1918 for IPv4 and fec0::/10 / fc00::/7 for IPv6). |
| 17 | +- ALWAYS ensure that the resolved IP address of the domain is not a multicast one. |
| 18 | +- ALWAYS ensure that the resolved IP address of the domain is not a link-local one. |
| 19 | +- ALWAYS ensure that the resolved IP address of the domain is not a loopback one. |
| 20 | +- ALWAYS ensure that the resolved IP address of the domain is not a wildcard/any-local one (0.0.0.0 for IPv4 or :: for IPv6). |
| 21 | +- ALWAYS use the validated IP V4 or V6 address for the HTTP request. |
| 22 | +- ALWAYS disable automatic redirect following so that a redirect response cannot bypass the resolved-IP validation. |
| 23 | +- ALWAYS specify a request timeout; default to 10 seconds. |
| 24 | + |
| 25 | +```java |
| 26 | +// BAD: No validation is performed for any points |
| 27 | +public static String badCall(String targetUrl) throws Exception { |
| 28 | + HttpClient client = HttpClient.newHttpClient(); |
| 29 | + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(targetUrl)).GET().build(); |
| 30 | + HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); |
| 31 | + return response.body(); |
| 32 | +} |
| 33 | + |
| 34 | +// GOOD: All the points are validated |
| 35 | +public static String goodCall(String targetUrl) throws Exception { |
| 36 | + URI uri = URI.create(targetUrl); |
| 37 | + |
| 38 | + // Rule 1: Reject any scheme that is not http or https |
| 39 | + String scheme = uri.getScheme(); |
| 40 | + if (scheme == null || (!scheme.equalsIgnoreCase("https") && !scheme.equalsIgnoreCase("http"))) { |
| 41 | + throw new SecurityException("Blocked scheme: " + scheme); |
| 42 | + } |
| 43 | + |
| 44 | + String host = uri.getHost(); |
| 45 | + if (host == null || host.isBlank()) { |
| 46 | + throw new SecurityException("Missing or blank hostname."); |
| 47 | + } |
| 48 | + |
| 49 | + InetAddress chosen = null; |
| 50 | + for (InetAddress address : InetAddress.getAllByName(host)) { |
| 51 | + // Rules 2–6: Reject private/site-local, multicast, link-local, loopback, and any-local addresses |
| 52 | + if (address.isLoopbackAddress() || address.isSiteLocalAddress() || address.isLinkLocalAddress() || address.isAnyLocalAddress() || address.isMulticastAddress()) { |
| 53 | + throw new SecurityException("Blocked non-public address: " + address.getHostAddress()); |
| 54 | + } |
| 55 | + // Rule 2 (cont.): isSiteLocalAddress() covers fec0::/10 for IPv6 but not fc00::/7; check the unique-local range explicitly |
| 56 | + if (address instanceof Inet6Address) { |
| 57 | + byte[] bytes = address.getAddress(); |
| 58 | + if ((bytes[0] & 0xFE) == 0xFC) { |
| 59 | + throw new SecurityException("Blocked IPv6 unique-local address: " + address.getHostAddress()); |
| 60 | + } |
| 61 | + } |
| 62 | + if (chosen == null || (!(chosen instanceof Inet4Address) && address instanceof Inet4Address)) { |
| 63 | + chosen = address; |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + if (chosen == null) { |
| 68 | + throw new SecurityException("Could not resolve hostname: " + host); |
| 69 | + } |
| 70 | + |
| 71 | + // Rule 7: Build the request URI from the validated IP address, not the hostname |
| 72 | + int port = uri.getPort(); |
| 73 | + String ipLiteral = chosen instanceof Inet6Address ? "[" + chosen.getHostAddress() + "]" : chosen.getHostAddress(); |
| 74 | + |
| 75 | + URI resolvedUri = new URI(scheme, null, ipLiteral, port, uri.getPath(), uri.getQuery(), uri.getFragment()); |
| 76 | + |
| 77 | + // Rule 8: Disable automatic redirects to prevent bypass via redirect to a private address |
| 78 | + HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).followRedirects(HttpClient.Redirect.NEVER).build(); |
| 79 | + |
| 80 | + // Rule 9: Set a request timeout to prevent indefinite blocking (default: 10 seconds) |
| 81 | + HttpRequest request = HttpRequest.newBuilder().uri(resolvedUri).timeout(Duration.ofSeconds(10)).header("Host", port != -1 ? host + ":" + port : host).GET().build(); |
| 82 | + |
| 83 | + return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +## 2. Output Checklist |
| 88 | + |
| 89 | +Before finalizing generated code, verify: |
| 90 | + |
| 91 | +- [ ] The URL scheme is http or https; any other scheme is rejected. |
| 92 | +- [ ] The resolved IP address is not a private, site-local, or IPv6 unique-local address (RFC 1918 for IPv4; fec0::/10 and fc00::/7 for IPv6). |
| 93 | +- [ ] The resolved IP address is not a multicast address. |
| 94 | +- [ ] The resolved IP address is not a link-local address. |
| 95 | +- [ ] The resolved IP address is not a loopback address. |
| 96 | +- [ ] The resolved IP address is not a wildcard/any-local address (0.0.0.0 for IPv4 or :: for IPv6). |
| 97 | +- [ ] The validated IP V4 or V6 address is used directly when performing the HTTP request. |
| 98 | +- [ ] Automatic redirect following is disabled so that a redirect response cannot bypass the resolved-IP validation. |
| 99 | +- [ ] A request timeout is set; default is 10 seconds. |
| 100 | + |
| 101 | +## References |
| 102 | + |
| 103 | +- [OWASP Server-Side Request Forgery Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html) |
| 104 | +- [PortSwigger: Server-side request forgery (SSRF)](https://portswigger.net/web-security/ssrf) |
| 105 | +- [MITRE CAPEC-275: DNS Rebinding](https://capec.mitre.org/data/definitions/275.html) |
| 106 | +- [DNS Rebinding](https://en.wikipedia.org/wiki/DNS_rebinding). |
| 107 | +- [DNS rebinding attacks explained: The lookup is coming from inside the house!](https://github.blog/security/application-security/dns-rebinding-attacks-explained-the-lookup-is-coming-from-in-side-the-house/). |
| 108 | +- [What Is DNS Rebinding?](https://www.paloaltonetworks.com/cyberpedia/what-is-dns-rebinding). |
| 109 | +- [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) |
0 commit comments