Reading a Netmask Without Guessing
I spent longer than I’d like to admit stuck on a pivoting exercise last week, and the root cause had nothing to do with pivoting. It was a netmask I guessed instead of calculated.
The setup
The scenario is a standard one for this kind of lab: two internal machines, only one of which I can reach directly from my attack box. I land a foothold on the reachable one, and from there I need to route traffic to the second, hidden machine — pivoting through the first host like a router.
The compromised host can see both networks. My attack box can only see the first. So I add a route through my session on that host, pointed at whatever subnet the second machine lives on, and everything downstream — scans, exploits — gets tunneled through it automatically.
That “whatever subnet” is where I went wrong.
The mistake: guessing instead of reading
I ran ipconfig on the compromised host and got back an IP address and a
subnet mask. I looked at the IP, made an assumption about where the network
boundary was, and routed accordingly. The route came up, some hosts responded,
some didn’t, and I burned real time convinced pivoting itself was broken.
It wasn’t. My subnet was wrong. I’d routed a slice of the network that didn’t actually contain the host I was after — narrower than the real subnet — so traffic for anything outside my guessed range just had nowhere to go.
The fix isn’t a pivoting technique. It’s arithmetic I was skipping.
Netmask to CIDR, done properly
A subnet mask tells you, per octet, which bits are “network” and which are “host.” Convert the mask to CIDR notation and you get the actual size of the network you’re standing on:
| Netmask | CIDR | Addresses | Example range |
|---|---|---|---|
255.255.255.0 | /24 | 256 | x.x.x.0 – x.x.x.255 |
255.255.240.0 | /20 | 4096 | x.x.0.0 – x.x.15.255 |
255.255.0.0 | /16 | 65536 | x.x.0.0 – x.x.255.255 |
The part that actually bit me is the /20 row, because it’s not aligned to
a clean octet boundary the way /24 is. 255.255.240.0 means only the
top four bits of the third octet are network bits. To find the real
network address, you AND the host’s own address against the mask — you don’t
round to the nearest tidy-looking number.
Worked example: a host with address 10.2.23.x and mask 255.255.240.0.
240 in binary is 11110000. 23 in binary is 00010111. AND them together
and you get 00010000 — decimal 16. So the network isn’t 10.2.23.0
(a guess anchored on the octet you can see) — it’s 10.2.16.0/20, covering
everything from 10.2.16.0 through 10.2.31.255. My guessed range fell
entirely inside that block but didn’t cover all of it, which is exactly
narrow enough to make some traffic route fine and other traffic silently die.
The second trap: a target that was never “behind” anything
The machine I was trying to reach turned out to show up in arp -a on the
compromised host, but not as a separate entry in ipconfig. That distinction
matters: ARP only resolves within the same broadcast domain. If a host only
shows up via ARP, it isn’t hiding behind another hop at all — it’s a direct
neighbor on the same subnet as the box you’re already standing on. Once I
had the /20 routed correctly, that “hidden” second machine turned out to
already be reachable — no second pivot needed, just the first subnet done
right.
What I’d tell myself a week ago
Read the mask directly off the compromised host — don’t infer it from the IP
alone. Convert it to CIDR with the actual AND operation, not a mental
rounding. And check arp -a before assuming a target needs a deeper route;
it might already be a neighbor.
None of this is advanced. It’s the kind of thing that’s obvious in hindsight and completely invisible while you’re three failed scans deep, second-guessing a technique that was working fine the whole time.