August 24, 2026
A Single Full Stop: Solving PortSwigger’s “Username Enumeration via Subtly Different Responses”
Lab two in my Web Security Academy series — and a lesson in paying attention to the smallest details
By Kaustubh Asthana
3 min read
After my first PortSwigger Web Security Academy lab, I moved on to a harder variant of the same vulnerability class: "Username enumeration via subtly different responses." Where the first lab had an error message that was obviously different in wording, this one hides the tell in something almost invisible — a single full stop.
If the last lab taught me that inconsistency is information, this one taught me that sometimes you have to go looking for it, because the application isn't going to make it easy.
The Setup
Same general approach as before: Burp Suite Community Edition, browser traffic routed through the proxy, targeting the lab's login form.
Step 1: Establish a Baseline
I started by submitting fake credentials — a username and password I knew wouldn't exist — and captured that request in Burp's HTTP history. This became my starting point for comparison, and the request I'd send on to Intruder.
Step 2: Automate with Intruder, but Look Closer This Time
I sent the baseline request to Burp Intruder, marked the username parameter as the injection point, set the attack type to Sniper, and loaded the candidate username list provided by the lab.
This is where the lab gets harder than the first one. In my previous lab, the valid username stood out immediately by response length — an easy visual cue in Intruder's results table. This time, response lengths were identical across every attempt. The application had been built to avoid that obvious tell.
So instead of relying on length, I used Burp Intruder's Grep — Extract feature (under the attack's options) to pull the exact error message text out of each response, rather than just the response size. I selected the warning paragraph in a sample response — <p class=is-warning>Invalid username or password.</p> — and let Burp configure the extraction to start right after -warning> and end at the </p> delimiter. That gave me a clean column showing the exact message text for every username in the attack, side by side, instead of a single number to eyeball.
Running it as a Sniper attack against the full candidate list, one entry in that extracted-text column stood out — not because the wording was different, but because it ended in . where every other response ended without one. A single full stop, and nothing else, separated the response for a valid username from the response for an invalid one.
Step 3: Confirm the Finding
That's a genuinely subtle bug. Most manual testers scanning responses by eye would miss a single trailing period, especially across dozens of near-identical error messages. Using Grep Extract to pull out and align the exact text — rather than trusting response length as a proxy — was what actually surfaced it.
Step 4: Brute-Force the Password
With the username confirmed, I repeated the process — holding the username constant this time and running the password candidate list through Intruder. The correct password produced a distinctly different response, confirming the login and completing the lab.
Why This One Matters More
The first lab's vulnerability was almost incidental — a developer used different wording without thinking about it. This lab feels more deliberate on the defensive side: someone clearly tried to make the two error messages look identical, and almost succeeded. That near-miss is the real lesson.
It's a good reminder for both sides of the fence:
- As an attacker, don't rely on a single signal (like response length) to rule something out. A byte-for-byte identical length doesn't mean byte-for-byte identical content — always check the actual text when it matters.
- As a defender, "close enough" isn't good enough. If your fix for username enumeration still leaves any observable difference, however small, it's not actually fixed.
Takeaways From Lab Two
- Response length is a proxy, not a guarantee. Two responses can be the same length and still differ in content — Grep Match catches what length comparisons miss.
- The smallest inconsistency is still an inconsistency. A single full stop was enough to break this application's defense entirely.
- Tooling matters, but so does knowing which tool to reach for. Intruder's Grep Match feature isn't something you'd think to use until you hit a wall with the more obvious approach.