Skip to main content

LDAP query obfuscation library - Python port of github.com/Macmod/ldapx

Project description

ldapx

PyPI version Python versions License: MIT

Python port of ldapx - LDAP query obfuscation library.

Transform LDAP filters, BaseDNs, attribute lists, and attribute entries using composable middleware chains. Zero dependencies. Works as a library or CLI tool.

Installation

pip install ldapx

Quick Start

import ldapx

# Obfuscate a filter with case mutation + OID attributes
result = ldapx.obfuscate_filter("(cn=admin)", "CO")
# → (oID.02.05.04.03 =aDmIn)

# Obfuscate a BaseDN
result = ldapx.obfuscate_basedn("DC=corp,DC=local", "COQ")
# → oID.0.9.2342.19200300.100.1.25 ="cOrP",oID.0.9.2342.19200300.100.1.25 ="lOcAl"

# Obfuscate an attribute list
result = ldapx.obfuscate_attrlist(["cn", "sAMAccountName"], "COR")
# → ['oID.1.2.840.113556.1.4.221 ', 'oID.02.5.4.3  ']

Usage Patterns

Pattern 1: High-level chain strings (simplest)

import ldapx

result = ldapx.obfuscate_filter("(sAMAccountName=user1)", "COGDR")
result = ldapx.obfuscate_basedn("DC=corp,DC=local", "CSQOX")
result = ldapx.obfuscate_attrlist(["cn", "sAMAccountName"], "CRDG")
result = ldapx.obfuscate_attrentries({"cn": [b"test"]}, "CR")

Pattern 2: Explicit chain (Go-style)

from ldapx.parser import query_to_filter, filter_to_query
from ldapx.middlewares.filter import (
    FilterMiddlewareChain,
    rand_case_filter_obf,
    oid_attribute_filter_obf,
)

chain = FilterMiddlewareChain()
chain.add("Case", lambda: rand_case_filter_obf(0.7))
chain.add("OID", lambda: oid_attribute_filter_obf(4, 4))

f = query_to_filter("(cn=admin)")
f = chain.execute(f, verbose=True)
result = filter_to_query(f)

Pattern 3: Direct composition

from ldapx.parser import query_to_filter, filter_to_query
from ldapx.middlewares.filter import rand_case_filter_obf, oid_attribute_filter_obf

f = query_to_filter("(cn=admin)")
f = rand_case_filter_obf(0.5)(f)
f = oid_attribute_filter_obf(2, 2)(f)
result = filter_to_query(f)

CLI

# Obfuscate a filter
ldapx filter -f "(cn=admin)" -c "COGDR"

# Generate 5 variants
ldapx filter -f "(cn=admin)" -c "COGDR" -n 5

# Obfuscate a BaseDN
ldapx basedn -b "DC=corp,DC=local" -c "CSQOX"

# Obfuscate attribute list
ldapx attrlist -a "cn,sAMAccountName,memberOf" -c "CRDG"

# List available codes
ldapx codes --all

# Pipe from stdin
echo "(cn=admin)" | ldapx filter -c "COGDR"

# JSON output
ldapx filter -f "(cn=admin)" -c "CO" --json

# Custom options
ldapx filter -f "(cn=admin)" -c "CO" -o FiltCaseProb=0.8 -o FiltOIDMaxSpaces=4

Middleware Codes

Filter (-f)

Code Name Description
C Random case Randomize case of attribute names and values
S Random spacing Add context-aware spacing (ANR, DN, SID)
G Garbage filters Wrap filters in OR with random garbage
T Replace tautologies Replace simple presence filters with tautologies
R Boolean reorder Randomly shuffle AND/OR clauses
O OID attributes Replace attribute names with OIDs
X Hex value encoding Hex-encode characters in DN-type values
t Timestamp garbage Add garbage to timestamp patterns
B Add random boolean Wrap with redundant AND/OR
D Double negation Apply (!(!(filter)))
M DeMorgan transform Apply De Morgan's laws
b Bitwise breakout Convert equality to bitwise matching rules
d Bitwise decompose Break bitwise values into individual bits
I Equality by inclusion (attr=val) to range + exclusion
E Equality by exclusion (attr=val) to presence + NOT range
A Approx match (attr=val) to (attr~=val)
x Extensible match (attr=val) to (attr:=val)
Z Prepend zeros Add leading zeros to numbers/SIDs
s Substring split Split equality into substring match
N Names to ANR Replace ANR-set attributes with aNR
n ANR garbage Add garbage to ANR substring queries
P dnAttributes noise Randomly toggle :dn: on extensible match (AD ignores it, [MS-ADTS 3.1.1.3.1.3.1])
L Transitive eval Convert link attr equality to LDAP_MATCHING_RULE_TRANSITIVE_EVAL (1941)

BaseDN (-b)

Code Name Description
C Random case Randomize case
S Random spacing Add spaces around DN
Q Double quotes Wrap DN values in quotes
O OID attributes Replace DN attr names with OIDs
X Hex value encoding Hex-encode DN value characters
U GUID format Replace DN with <GUID=hex> ([MS-ADTS 3.1.1.3.1.2.4]). Requires -o BaseDNGuid=hex
I SID format Replace DN with <SID=string> ([MS-ADTS 3.1.1.3.1.2.4]). Requires -o BaseDNSid=S-1-...

AttrList (-a)

Code Name Description
C Random case Randomize case
R Reorder list Shuffle attribute order
D Duplicate Add duplicate entries
O OID attributes Replace with OIDs
G Garbage (non-existing) Add random fake attributes
g Garbage (existing) Add random real attributes
W Replace with wildcard Replace list with *
w Add wildcard Append * to list
p Add plus Append + (operational attrs)
e Replace with empty Replace with empty list

AttrEntries

Code Name Description
C Random case Randomize attribute name case
R Reorder list Shuffle attribute order
O OID attributes Replace with plain OIDs

Options

Customize middleware parameters via Options:

import ldapx

opts = ldapx.Options(
    FiltCaseProb=0.8,           # Higher case mutation probability
    FiltOIDMaxSpaces=4,         # More spaces after OIDs
    FiltGarbageMaxElems=3,      # More garbage filters
    BDNSpacingMaxSpaces=4,      # More spacing in BaseDN
)

result = ldapx.obfuscate_filter("(cn=admin)", "COGDR", options=opts)

Adapters

The core library has zero dependencies and returns strings. For integration with specific LDAP libraries, use adapters:

badldap adapter

# pip install ldapx[badldap]
from ldapx.parser import query_to_filter
from ldapx.middlewares.filter import rand_case_filter_obf
from ldapx.adapters.badldap import ast_to_asn1

f = query_to_filter("(cn=admin)")
f = rand_case_filter_obf(0.5)(f)
asn1_filter = ast_to_asn1(f)  # badldap ASN1 Filter object

Known Limitations by LDAP Library

ldapx-py returns obfuscated queries as strings. How well those strings are accepted depends on the LDAP library your project uses to send them over the wire. Below is a summary of what we found during integration testing against a real Active Directory environment.

badldap (used by bloodyAD)

badldap's internal PEG parser cannot parse most obfuscated filter syntaxes (extensible match, OID attributes with spacing, etc). The solution is to use the ldapx.adapters.badldap adapter, which converts the obfuscated AST directly to badldap's ASN1 Filter objects, completely bypassing the parser.

Additionally, badldap's query_syntax_converter needs to be monkey-patched to pass through pre-built ASN1 Filter objects without re-parsing them.

Target Supported codes Notes
Filter All 20 codes Requires ASN1 adapter + monkey-patch
BaseDN All 5 codes (C, S, Q, O, X) Works natively
AttrList C, R, D, G, g (not W/w/p/e) W/w/p/e change query semantics, breaking response parsing
AttrEntries C, R (not O) AD rejects OID names in modify/add operations

ldap3 (used by bloodhound.py)

ldap3 validates attribute names in filters against the AD schema (check_names=True). This causes obfuscated attribute names (garbage, OID format) to be rejected before they even reach the server.

Solution: Monkey-patch ldap3.protocol.convert.validate_attribute_value to accept unknown attributes gracefully, falling back to raw encoding when validation fails. This preserves ldap3's type conversion (SID, GUID, datetime, int) while allowing obfuscated filters through.

Important: Do not set connection.check_names = False as a workaround — this disables all of ldap3's response parsing (SIDs returned as raw bytes instead of strings, integers as strings, datetimes as strings), which will break most tools that depend on ldap3's automatic type conversion.

ldap3 also validates BaseDN format via its safe_dn() parser, which rejects the oID. prefix format used by the OID obfuscation code.

Target Supported codes Unsupported Reason
Filter All 20 codes Works with validator monkey-patch
BaseDN C, S, Q, X O ldap3's DN parser rejects oID.X.X.X format
AttrList C, R, D, G, g O ldap3's DN parser rejects OID format in attr names
AttrEntries C, R O Same as above

General AD limitations (all libraries)

These are server-side limitations from Active Directory itself, regardless of which LDAP library you use:

  • AttrEntries code O (OID): AD's modify/add handler requires strict attribute names — it does not accept OID format in write operations (only in search filters and attribute lists)
  • AttrList codes W/w/p/e: These change what the server returns (wildcard *, operational +, empty). If your tool expects specific attributes in the response, these will break response parsing
  • NTLM signing/sealing: When enabled, LDAP traffic is encrypted at the transport level. The obfuscation still works (it's applied before encryption), but you won't see the obfuscated queries on the wire with tools like Wireshark. Use debug logs or simple bind for wire verification

Proxy Mode

This library provides programmatic obfuscation (library + CLI). If you need proxy mode — intercepting and transforming LDAP packets on the fly between any tool and an LDAP server, without modifying source code — use the Go version:

  • github.com/Macmod/ldapx — LDAP proxy with real-time packet transformation, interactive shell, LDAPS/SOCKS support

Credits

License

MIT - see LICENSE

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ldapx-0.2.1.tar.gz (55.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ldapx-0.2.1-py3-none-any.whl (58.6 kB view details)

Uploaded Python 3

File details

Details for the file ldapx-0.2.1.tar.gz.

File metadata

  • Download URL: ldapx-0.2.1.tar.gz
  • Upload date:
  • Size: 55.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for ldapx-0.2.1.tar.gz
Algorithm Hash digest
SHA256 a66b6c26fe374f9a8a40fabcc95cf9f267db7569bba8f7c8581d2f7ccdd8e176
MD5 6d1f7eed5a40680fc949d7c132ad07d8
BLAKE2b-256 850a32d6d0555358a65738c1a651128b624272e259c0525106b777c3e756a593

See more details on using hashes here.

File details

Details for the file ldapx-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: ldapx-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 58.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for ldapx-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a78bec5471b285a4c2b7a27d6f063b82336ec7b4ab27e76a0ca62e227102de6d
MD5 707e33bf7bf94c8567d351550bf66cd0
BLAKE2b-256 58361aadd126b5715af7d1fb2160f43a284a333280a73ebc6e9e489b8c8c0647

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page