> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/joey727/Phisherman/llms.txt
> Use this file to discover all available pages before exploring further.

# SSRF protection

> How Phisherman protects against Server-Side Request Forgery and DNS rebinding attacks.

Phisherman accepts arbitrary URLs as input and resolves their hostnames during heuristic analysis. Without safeguards, an attacker could submit URLs pointing to internal services and cause the server to make requests on their behalf — a Server-Side Request Forgery (SSRF) attack.

Phisherman blocks SSRF at two levels: a **static IP check** for URLs that already contain a raw IP address, and a **safe DNS resolver** that validates resolved addresses before any connection is made.

<Warning>
  Phisherman is designed to analyze public URLs only. Submitting URLs that point to internal services, loopback addresses, or cloud metadata endpoints will be blocked and scored accordingly.
</Warning>

## Private IP ranges blocked

### IPv4

The following ranges are blocked:

```typescript theme={null}
// src/utils/network.ts
const PRIVATE_IP_RANGES = [
  /^10\./,          // RFC 1918 — 10.0.0.0/8
  /^127\./,         // Loopback — 127.0.0.0/8
  /^172\.(1[6-9]|2[0-9\D]|3[0-1])\./,  // RFC 1918 — 172.16.0.0/12
  /^192\.168\./,    // RFC 1918 — 192.168.0.0/16
  /^169\.254\./,    // Link-local — 169.254.0.0/16
  /^0\./,           // This network — 0.0.0.0/8
];
```

| Range            | Description                                          |
| ---------------- | ---------------------------------------------------- |
| `10.0.0.0/8`     | RFC 1918 private network                             |
| `127.0.0.0/8`    | Loopback                                             |
| `172.16.0.0/12`  | RFC 1918 private network                             |
| `192.168.0.0/16` | RFC 1918 private network                             |
| `169.254.0.0/16` | Link-local (cloud metadata endpoints use this range) |
| `0.0.0.0/8`      | This network                                         |

### IPv6

```typescript theme={null}
// src/utils/network.ts
const PRIVATE_IPV6 = [
  /^fc00:/, // Unique local
  /^fd00:/,
  /^fe80:/, // Link-local
  /^::1$/,  // Loopback
];
```

| Prefix      | Description                    |
| ----------- | ------------------------------ |
| `fc00::/7`  | Unique local (fc00: and fd00:) |
| `fe80::/10` | Link-local                     |
| `::1`       | Loopback                       |

## blockIfPrivate

`blockIfPrivate()` is a synchronous guard applied before any DNS resolution. It handles the case where the user-supplied URL already contains a raw IP address:

```typescript theme={null}
// src/utils/network.ts
export function blockIfPrivate(host: string) {
  if (isIP(host) && isPrivateIP(host)) {
    throw new Error("Blocked private IP address (SSRF protection)");
  }
}
```

In the heuristics checker, `blockIfPrivate` is called immediately after the URL is parsed:

```typescript theme={null}
// src/checkers/heuristics.ts
try {
  blockIfPrivate(hostname);
} catch {
  return { score: 50, reasons: ["Private/Internal network address"] };
}
```

If the hostname is a literal private IP, scanning stops immediately and a score of 50 is returned.

## safeResolveHost

`safeResolveHost()` resolves a hostname to its A and AAAA records and validates every returned IP before returning. If any resolved address falls within a private range, an error is thrown.

```typescript theme={null}
// src/utils/network.ts
export async function safeResolveHost(host: string): Promise<string[]> {
  try {
    const cached = await dnsCache.get<string[]>(host);
    if (cached) return cached;
  } catch (err) { }

  // If host is already an IP address
  if (isIP(host)) {
    if (isPrivateIP(host)) {
      throw new Error(`Blocked direct IP access to private range: ${host}`);
    }
    return [host];
  }

  // Resolve A & AAAA records
  let ips: string[] = [];
  try {
    const A = await dns.resolve4(host).catch(() => []);
    const AAAA = await dns.resolve6(host).catch(() => []);
    ips = [...A, ...AAAA];
  } catch (e) {
    throw new Error("DNS resolution failed: " + (e as any).message);
  }

  if (ips.length === 0) {
    throw new Error("Host resolved but no valid A/AAAA records found");
  }

  // Block private IPs
  for (const ip of ips) {
    if (isPrivateIP(ip)) {
      throw new Error(`Blocked private IP address: ${ip}`);
    }
  }

  // ...
}
```

The function rejects any host where **any** resolved address is private — not just the first one.

## DNS rebinding protection

DNS rebinding is an attack where a hostname first resolves to a public IP (passing the check above), then immediately resolves to a private IP when the actual connection is made. Phisherman mitigates this with a second, independent lookup:

```typescript theme={null}
// src/utils/network.ts
// DNS Rebinding Protection (re-resolve to check consistency)
const secondLookup = await dns.lookup(host, { all: true }).catch(() => []);
const reboundIPs = secondLookup.map((r) => r.address);

for (const ip of reboundIPs) {
  if (isPrivateIP(ip)) {
    throw new Error(`Blocked via DNS rebinding check: ${ip}`);
  }
}
```

The second lookup uses `dns.lookup()` — which goes through the OS resolver rather than querying authoritative servers directly — to simulate what a subsequent connection attempt would see. If any address in the second lookup is private, the host is blocked.

## DNS result caching

Successful DNS resolutions are cached in the `dnsCache` `HashCache` instance for 1 hour:

```typescript theme={null}
// src/utils/network.ts
const DNS_CACHE_TTL = 3600; // 1 hour

// src/utils/hashCache.ts
export const dnsCache = new HashCache("dns_cache", 3600);
```

Caching avoids repeating the full two-lookup sequence for every scan of a recently seen hostname. The `HashCache` stores results in `dns_cache_hash` with expiry tracked in `dns_cache_expiry`, and expired entries are purged on every `CacheManager` cycle.
