
WebVerse Pro · Challenge
VelvetRope
Category: Web / LDAP
Difficulty: Easy
XP: 50
Sypnosis
An unescaped, hand-rolled LDAP filter in the login flow allowed full authentication bypass — submitting a bare wildcard (*) in both the username and password fields satisfied the server's "does a matching directory entry exist" check without validating any actual credential, granting access to the Members Portal as an arbitrary account.
Skills Required
- Basic understanding of HTTP POST requests and form encoding
- Familiarity with LDAP filter syntax and search semantics
- Web application manual testing using an intercepting proxy (Burp Suite)
- Comparing raw HTTP responses to distinguish real signal from noise
Skill Learned
- Recognizing LDAP as a backend authentication mechanism from tags/briefing hints and app behavior
- Correctly encoding LDAP filter metacharacters (
(,),*,\) insideapplication/x-www-form-urlencodedbodies - Using response diffing (
Content-Length, byte comparison) to isolate whether injection is landing versus being sanitized - Distinguishing a plausible-but-wrong attack path (guessing a valid
uidand blind-extracting a password) from the actual, much simpler flaw (wildcard match-any bypassing the check entirely)
Briefing
"Gilt & Grain opened its digital cellar doors in 2021: a quiet, members-only corner of the internet where paying subscribers could browse private single-barrel allocations before they hit retail. Member IDs were issued by hand, passphrases set on first login, and the system hummed along without incident. The founding engineer left the company in 2023 to open a distillery of his own. Nobody has looked at the authentication code since. The cellar is still locked, but the mechanism holding the door shut is older than anyone on the current team remembers."
Exploitation

The target was a "Members Portal" login form for a fictional whiskey retailer. The challenge tag (LDAP) plus the briefing's framing — legacy, unmaintained authentication code nobody has touched since 2023 — pointed straight at LDAP injection before I'd even sent a request.
Burp Request and Response

The only endpoint that mattered was:
I noticed early that a failed login always returns HTTP 200 with the login page re-rendered and an error banner:
Since the status code never changed, that banner was going to be my only oracle. I sent a normal wrong login (username=zzz&password=zzz) to establish a baseline response to diff everything else against.

My working theory was that the backend was building a filter by direct string concatenation, something like:
without escaping LDAP metacharacters. So my first real attempt tried to break out of that structure and blind-extract a password for a guessed admin account:
That came back byte-identical to my baseline. Rather than keep guessing more compound payloads, I stepped back and sent the simplest possible structural probes instead — unbalanced parentheses alone, single metacharacters ((, ), *) in isolation, and a generic always-true filter — comparing Content-Length against baseline each time to figure out whether metacharacters were even reaching the filter unescaped, or whether my specific assumptions (attribute name uid, username admin) were just wrong.
Eventually I tried the simplest thing I hadn't yet: a bare wildcard in both fields.
That worked. I was logged in immediately and redirected to /dashboard with the flag — no valid username guessed, no password extracted, no filter syntax broken out of. Just two asterisks.

Root Cause Analysis
A bare * is LDAP's wildcard/match-any operator — the rough equivalent of SQL's % in a LIKE clause. With the filter shape I'd hypothesized, submitting * for both fields produced:
Which reads as: "return any directory entry that has a uid attribute (any value) AND a userPassword attribute (any value)." Since essentially every provisioned member record satisfies that trivially, the filter matched — likely the first entry the directory returned — and the application logged me in as that entry without checking any actual credential.
This is about the simplest possible LDAP injection there is. It doesn't even require breaking out of the filter's parenthesization the way my earlier )(userPassword=a*))(&(uid=admin attempt tried to — it just relies on the filter doing a presence/match check rather than an exact equality check, because * alone satisfies "attribute exists," true for any valid member. That also explains why my earlier, more "clever" attempt failed: it assumed a specific attribute name and a specific guessed username, and if admin isn't a valid uid, the filter can be syntactically fine and still match nothing.
The underlying flaw comes down to two things: the app concatenates raw, unescaped user input directly into an LDAP search filter, and it treats "a matching record was found" as equivalent to "the correct credentials were supplied" — rather than performing a real, separate password check via an LDAP bind.
Impact
Anyone who reaches the login form — no valid credentials, no prior account access, no knowledge of any real member ID — can fully bypass authentication and log in as an arbitrary member using a two-character payload. This exposes the "Private Single-Barrel Allocations" member area to anyone who finds the endpoint, defeating the entire purpose of gating it behind membership.
Attack Chain Summary
| Step | Action |
|---|---|
| 1 | Sent a normal invalid login to establish a baseline response |
| 2 | Hypothesized the backend builds an unescaped (&(uid=...)(userPassword=...)) filter |
| 3 | Tried a targeted injection assuming uid=admin and blind-extracting the password — failed, identical to baseline |
| 4 | Sent isolated structural probes (unbalanced parens, single metacharacters) to test whether input was reaching the filter unescaped |
| 5 | Tried a bare wildcard in both fields (username=*&password=*) — authenticated instantly |
Remediation
- Escape all LDAP filter metacharacters in user input per RFC 4515 before constructing any filter string (
\28,\29,\2a,\5c,\00for(,),*,\, NUL respectively). - Never authenticate on filter match alone. Use an LDAP bind operation with the user-supplied DN and password — a bind attempt with a wildcard or arbitrary password fails immediately, unlike a search-and-compare against
userPassword. - Add input validation/allow-listing on the
usernamefield (e.g., restrict to the expected member-ID format) as defense in depth. - Add automated regression tests asserting that wildcard, empty, and metacharacter-laden inputs in either field are rejected rather than authenticated.