{"title":"LDAP Injection","language":"Python","severity":"Critical","cwe":"CWE-90","source_lines":[5],"flow_lines":[5,6,8],"sink_lines":[8],"vulnerable_code":"import ldap\ndef fetch_device_config(device_id, org_unit):\n    conn = ldap.initialize('ldap://iot-directory.internal:389')\n    conn.simple_bind_s('cn=svc_iot,dc=corp,dc=local', 'S3rv!ceP@ss')\n    base_dn = f'ou={org_unit},dc=corp,dc=local'\n    search_filter = f'(deviceId={device_id})'\n    attrs = ['deviceConfig', 'firmwareVersion', 'accessToken']\n    result = conn.search_s(base_dn, ldap.SCOPE_SUBTREE, search_filter, attrs)\n    conn.unbind_s()\n    return result","explanation":"Both `device_id` and `org_unit` parameters are function arguments accepted from external callers (untrusted input). They are interpolated directly into the LDAP base DN (line 5) and search filter (line 6) using f-strings without any sanitization or escaping, and then consumed by `conn.search_s()` on line 8. An attacker can inject LDAP metacharacters into either parameter to manipulate the directory search scope or filter logic.","remediation":"The fix applies three layers of defense: (1) strict input validation via regex allowlists that reject any characters outside alphanumerics, hyphens, and underscores for both `device_id` and `org_unit`; (2) LDAP DN escaping via `ldap.dn.escape_dn_chars()` for `org_unit` before constructing the base DN; and (3) LDAP filter escaping via `ldap.filter.escape_filter_chars()` for `device_id` before constructing the search filter. This prevents both DN traversal and filter injection attacks while also adding proper resource cleanup with a try/finally block.","secure_code":"import ldap\nfrom ldap.filter import escape_filter_chars\nfrom ldap.dn import escape_dn_chars\nimport re\n\ndef fetch_device_config(device_id, org_unit):\n    # Validate input format: only allow alphanumeric, hyphens, and underscores\n    if not re.match(r'^[a-zA-Z0-9_-]+$', device_id):\n        raise ValueError(\"Invalid device_id: contains disallowed characters\")\n    if not re.match(r'^[a-zA-Z0-9_-]+$', org_unit):\n        raise ValueError(\"Invalid org_unit: contains disallowed characters\")\n\n    conn = ldap.initialize('ldap://iot-directory.internal:389')\n    conn.simple_bind_s('cn=svc_iot,dc=corp,dc=local', 'S3rv!ceP@ss')\n\n    # Escape DN special characters in org_unit to prevent DN injection\n    safe_org_unit = escape_dn_chars(org_unit)\n    base_dn = f'ou={safe_org_unit},dc=corp,dc=local'\n\n    # Escape LDAP filter special characters in device_id to prevent filter injection\n    safe_device_id = escape_filter_chars(device_id)\n    search_filter = f'(deviceId={safe_device_id})'\n\n    attrs = ['deviceConfig', 'firmwareVersion', 'accessToken']\n    try:\n        result = conn.search_s(base_dn, ldap.SCOPE_SUBTREE, search_filter, attrs)\n    finally:\n        conn.unbind_s()\n    return result"}