Safe Penetration Testing Labs · intermediate · ~15 min
Walk a small fixture of nmap -oX output and count open ports.
Walk an nmap-XML dump byte by byte, count <port> elements whose <state> is "open". Pure string parsing — no network, no XML library.
Reading saved scan output is the bread-and-butter of defensive triage. When someone asks "which hosts have an unexpected port open?", a 20-line parser is the answer.
Token-walk parsing: find the next <port substring, look ahead for state="open" within the same element. Never trust the input — always bound the look-ahead with the next <port or a fixed window.
Defensive parsing: count what you can, skip the rest, never crash. Malformed XML is normal in the wild.
Defenders read scan output far more often than they run scans themselves. When the SOC team asks "which of our hosts have port 23 open?", the answer is one tiny parser away.
This lesson teaches the parsing side: take the bytes that nmap saved to disk and pull out the answer your team actually needs. The file is bundled in the harness; the function never opens a socket.
A trimmed nmap -oX dump:
<host><address addr="127.0.0.1"/><ports>
<port portid="22"><state state="open"/></port>
<port portid="80"><state state="filtered"/></port>
<port portid="443"><state state="open"/></port>
</ports></host>
You don't need a real XML parser. strstr walking from one <port to the
next is enough — the format is regular and the buffer is small.
Implement a function that returns the count of <port> elements whose
nested <state> has state="open". Skip filtered and closed.
int count_open_ports(const char *xml) {
int count = 0;
const char *p = xml;
while ((p = strstr(p, "<port")) != NULL) {
const char *end = strchr(p, '>');
const char *next = strstr(p + 1, "<port");
const char *limit = next ? next : p + 256;
const char *state = strstr(p, "state=\"open\"");
if (state && state < limit) count++;
if (!end) break;
p = end + 1;
}
return count;
}
state="open|filtered" as open. nmap uses that exact string —
match it whole.A live nmap runner. The bytes you read came from a previous, authorised scan. The function never opens a socket and never makes a DNS query.
<port> elements that are 'filtered'. 2. Extend it to ignore comments (<!-- ... -->). 3. Add bounds-checking so the function never reads past the NUL.20 lines of strstr + strchr gives you a working nmap-XML parser. No XML library required. The fixture is static; no network touched.