A number has one value and many notations. This walks through counting usable hosts in a CIDR block from memory, then writing 127.0.0.1 five ways — the set a filter that pattern-matches on notation, rather than resolving to the value, fails to catch.
Prerequisites
- Comfort with powers of two up to 2²⁴
- A calculator for the large multiplications
- No tools, VM, or network — this is math and notation only
- Prior exposure to what SSRF is, and why loopback is the target of a bypass
Part 1 — CIDR host counts
The method, once: 32 − prefix = free bits, then 2^(free bits) − 2. The −2 removes the network address and the broadcast address, neither assignable to a host. This holds for /30 and larger; /31 and /32 are special cases, covered at the end of this part.
1. Compute /24.
32 − 24 = 8 free bits → 2⁸ = 256 → 256 − 2 = 254 usable hosts.
2. Compute /16.
32 − 16 = 16 free bits → 2¹⁶ = 65,536 → 65,534 usable hosts.
3. Compute /8.
32 − 8 = 24 free bits → 2²⁴ = 16,777,216 → 16,777,214 usable hosts (~16.7 million).
4. State the pattern.
Every 1 you subtract from the prefix doubles the host count: /24 → /23 goes from 254 to 510, and on up. Remember the doubling and you never need the table.
5. Note the two special cases.
A /31 has no room for the −2 — it would compute to zero. RFC 3021 instead makes both addresses usable, giving 2 hosts, because point-to-point links don't need a network or broadcast address. A /32 is a single host: one address, no subtraction. Below /30, the −2 rule stops applying.
Part 2 — Five spellings of 127.0.0.1
Each octet has a positional value: 256³, 256², 256¹, 256⁰. Every representation below points at the same loopback address.
1. As a single decimal integer.
127 × 256³ + 0 × 256² + 0 × 256¹ + 1 × 256⁰
= 127 × 16,777,216 + 1
= 2,130,706,433
→ 2130706433. Some parsers accept this bare integer as a host — which ones is exactly what a bypass attempt is testing.
2. As hex.
Two hex digits per octet, concatenated: 127 = 7f, 0 = 00, 0 = 00, 1 = 01 → 0x7f000001.
3. As octal.
Each octet in base 8, with a leading zero to signal octal: 127 = 0177, so 0177.0.0.1. The leading zero is load-bearing — without it the octet is read as decimal, and 177 is a different value.
4. As the short form.
127.1. Parsers using the traditional inet_aton behavior read the final part as a 24-bit value filling the remaining octets, so 127.1 resolves to 127.0.0.1.
5. As the two IPv6 forms — and know the difference.
[::1] is IPv6's own native loopback: not a conversion of the IPv4 address, its own separate concept. [::ffff:127.0.0.1] is an IPv4-mapped IPv6 address, embedding the actual IPv4 address inside IPv6 syntax. They are not interchangeable, and a filter may handle one but not the other.
Why the list exists.
A filter written as if "127.0.0.1" in url: block checks one spelling of an address that has many. Each of the five forms above reaches the identical machine while walking straight past that check.
Verification check
Reverse the decimal conversion: 2,130,706,433 ÷ 16,777,216 = 127 remainder 1. The whole-number quotient is the first octet, the remainder resolves to the last — confirming 127.x.x.1. If the quotient isn't exactly 127, recheck the arithmetic before trusting the value.
Troubleshooting
These are the traps inherent to the math, not runtime errors — there's nothing to run.
Host count off by two.
The −2 was skipped or applied twice. It removes exactly two reserved addresses (network + broadcast) for /30 and larger. Sanity anchor: a /30 has 4 total, 2 usable. If you're working below /30, use the special cases in Part 1, step 5 instead.
Octal form read as decimal.
Dropping the leading zero — 177.0.0.1 instead of 0177.0.0.1 — changes the value: 177 decimal is a different address. The zero is what signals base 8.
Treating [::1] as a conversion of 127.0.0.1.
It isn't derived from the IPv4 address; it's IPv6's separate loopback. The genuine cross-protocol encoding is [::ffff:127.0.0.1]. Conflating the two produces a bypass list that misrepresents what each entry is — which matters when you're explaining a filter gap in a report.
Closing
CIDR math and the loopback notation list look unrelated, but both come down to one habit: a number has a single value and many notations, and anything that pattern-matches on notation instead of resolving to the value is the thing you're looking for.
Drafted and revised with Claude; all math verified by me.
Top comments (0)