ldapx

April 13, 2026 · View on GitHub

PyPI version PyPI Downloads 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)

CodeNameDescription
CRandom caseRandomize case of attribute names and values
SRandom spacingAdd context-aware spacing (ANR, DN, SID)
GGarbage filtersWrap filters in OR with random garbage
TReplace tautologiesReplace simple presence filters with tautologies
RBoolean reorderRandomly shuffle AND/OR clauses
OOID attributesReplace attribute names with OIDs
XHex value encodingHex-encode characters in DN-type values
tTimestamp garbageAdd garbage to timestamp patterns
BAdd random booleanWrap with redundant AND/OR
DDouble negationApply (!(!(filter)))
MDeMorgan transformApply De Morgan's laws
bBitwise breakoutConvert equality to bitwise matching rules
dBitwise decomposeBreak bitwise values into individual bits
IEquality by inclusion(attr=val) to range + exclusion
EEquality by exclusion(attr=val) to presence + NOT range
AApprox match(attr=val) to (attr~=val) — auto-skips non-string attributes (integer, bitwise, DN, SID, boolean, OID)
xExtensible match(attr=val) to (attr:=val)
ZPrepend zerosAdd leading zeros to numbers/SIDs
sSubstring splitSplit equality into substring match
NNames to ANRReplace ANR-set attributes with aNR
nANR garbageAdd garbage to ANR substring queries
PdnAttributes noiseRandomly toggle :dn: on extensible match (AD ignores it, [MS-ADTS 3.1.1.3.1.3.1])
LTransitive evalConvert link attr equality to LDAP_MATCHING_RULE_TRANSITIVE_EVAL (1941)
FobjectCategory formToggle between shortname and full DN form ([MS-ADTS 3.1.1.3.1.3.5]). Use -o FiltObjectCategoryConfigNC=CN=Configuration,... to expand to DN

BaseDN (-b)

CodeNameDescription
CRandom caseRandomize case
SRandom spacingAdd spaces around DN. Violates RFC 4514 (leading/trailing whitespace not allowed in DNs), accepted by AD but rejected by RFC-strict servers
QDouble quotesWrap DN values in quotes
OOID attributesReplace DN attr names with OIDs
XHex value encodingHex-encode DN value characters
UGUID formatReplace DN with <GUID=hex> ([MS-ADTS 3.1.1.3.1.2.4]). Requires -o BaseDNGuid=hex
ISID formatReplace DN with <SID=string> ([MS-ADTS 3.1.1.3.1.2.4]). Requires -o BaseDNSid=S-1-... (library API also accepts SID bytes)
WWKGUID formatReplace well-known containers (Users, Computers, etc.) with <WKGUID=guid,dn> ([MS-ADTS 3.1.1.3.1.2.4]). No pre-query needed

AttrList (-a)

CodeNameDescription
CRandom caseRandomize case
RReorder listShuffle attribute order
DDuplicateAdd duplicate entries
OOID attributesReplace with OIDs
GGarbage (non-existing)Add random fake attributes
gGarbage (existing)Add random real attributes
WReplace with wildcardReplace list with *
wAdd wildcardAppend * to list
pAdd plusAppend + (operational attrs)
eReplace with emptyReplace with empty list

AttrEntries

CodeNameDescription
CRandom caseRandomize attribute name case
RReorder listShuffle attribute order
OOID attributesReplace 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)

Parser and escaping notes (v0.5.0)

  • query_to_filter() is now strict for malformed filters and raises ValueError for invalid boolean groups (for example (&), (|), incomplete/truncated subfilter lists).
  • RFC4515 escapes are preserved end-to-end (escaped literals are kept as literals and no longer collapse into wildcard semantics).

OID prefix options (v0.5.0)

  • FiltOIDIncludePrefix (default True)
  • BDNOIDIncludePrefix (default True)
  • AttrsOIDIncludePrefix (default True)

Set them to False to emit plain OIDs (1.2.840...) instead of oID.1.2.840....

Approximate match attribute exclusion

The A code automatically skips attributes whose AD syntax doesn't support approximate match (~=). This includes integer, bitwise, boolean, DN, SID, and OID-type attributes (e.g., userAccountControl, samAccountType, objectCategory, objectClass). Unknown attributes default to string type and are converted normally.

You can also exclude additional attributes manually:

opts = ldapx.Options(FiltApproxExcludeAttrs=["cn", "sn"])
result = ldapx.obfuscate_filter("(cn=admin)", "A", options=opts)
# → (cn=admin)  (skipped because cn is in the exclusion list)

CLI:

ldapx filter -f "(cn=admin)" -c "A" -o FiltApproxExcludeAttrs=cn,sn

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

Compatibility Matrix

Active Directory accepts all obfuscation formats, the server-side parser is very permissive. However, each LDAP library has its own client-side parser that validates filters and DNs before sending them to the server. If the client rejects the obfuscated query, it never reaches AD. This is why compatibility varies by library, and why some codes require workarounds (monkey-patching the client validator or using an ASN1 adapter to bypass the client parser entirely).

Below is a full compatibility matrix tested against a real Active Directory environment.

Filter codes

CodeNamebadldapimpacketldap3Notes
CCasevia adapternativenative
SSpacingvia adapternativenative
GGarbagevia adapternativemonkey-patchldap3 rejects unknown attr names
TTautologiesvia adapternativenative
RReordervia adapternativenative
OOIDvia adapterFAILmonkey-patchimpacket/ldap3 reject oID. format
XHex valuevia adapternativenative
tTimestampvia adapternativenative
BAddBoolvia adapternativenative
DDblNegvia adapternativenative
MDeMorganvia adapternativenative
bBitwisevia adapternativenative
dDecomposevia adapternativenative
IInclusionvia adapternativenative
EExclusionvia adapternativenative
AApproxvia adapternativenative
xExtensiblevia adapternativenative
ZZerosvia adapternativenative
sSubstringvia adapternativenative
NANRvia adapternativenative
nANR garbagevia adapternativenative
PdnAttr noisevia adapternativenative
LTransitivevia adapternativenative
FobjCategoryvia adapternativenative

BaseDN codes

CodeNamebadldapimpacketldap3Notes
CCasenativenativenative
SSpacingnativenativeFAILldap3 DN parser rejects spaces
QQuotesnativenativeFAILldap3 DN parser rejects quotes
OOIDnativenativeFAILldap3 DN parser rejects oID.
XHex valuenativenativenative
UGUIDnativenativenativeAlternative DN form, works everywhere
ISIDnativenativenativeAlternative DN form, works everywhere
WWKGUIDnativenativenativeWell-known containers, no pre-query needed

Tools tested

ToolLDAP libraryRecommended filter chainRecommended BaseDN chain
bloodyADbadldapAll codes (via ASN1 adapter)All codes
bloodhound.pyldap3All except O (or with monkey-patch)C, X, U, I
impacket (GetADUsers, GetUserSPNs, etc)impacket customAll except OAll codes
NetExecimpacketAll except OAll codes
Certipyldap3All except O (or with monkey-patch)C, X, U, I

For step-by-step integration examples with each tool (impacket, NetExec, Certipy, bloodhound.py, bloodyAD), see docs/integration-examples.md.

Integration notes

badldap: Requires ASN1 adapter (ldapx.adapters.badldap.ast_to_asn1) + monkey-patch of query_syntax_converter to bypass PEG parser. See bloodyAD integration for reference.

ldap3: Codes G and O in filters need monkey-patching ldap3.protocol.convert.validate_attribute_value to accept unknown attribute names. Do not use connection.check_names = False, it breaks response parsing (SIDs, GUIDs, datetimes returned as raw bytes/strings). BaseDN codes S, Q, O fail due to ldap3's strict safe_dn() parser, use U (GUID) or I (SID) instead.

impacket: Code O (OID) in filters fails due to impacket's filter parser rejecting oID. prefix. All other codes work natively. BaseDN accepts all codes including alternative DN forms.

General AD limitations (all libraries)

  • AttrEntries code O: AD rejects OID attribute names in modify/add operations
  • AttrList codes W/w/p/e: Change query semantics (what server returns), may break response parsing
  • NTLM signing/sealing: Obfuscation works (applied before encryption), but not visible on the wire with Wireshark

Chain ordering notes

  • Code A before O: If code O (OID attributes) runs before A (approx match) in the chain, attribute names will already be in OID form (e.g., 1.2.840.113556.1.4.8 instead of userAccountControl). Since the auto-detection uses display names, it won't recognize the OID and will convert it to ~=. Place A before O in your chain to avoid this (e.g., "ACOGDR" not "COAGDR").
  • OID-form attributes in filters: If a filter already uses OID attribute names (e.g., from a previous transformation), the A code's auto-detection won't identify the attribute type and will treat it as a string. This is a known limitation shared with other token-format-based middlewares.
  • U/I/W in BaseDN chains: Once BaseDN is converted to an alternative form (<GUID=...>, <SID=...>, <WKGUID=...>), DN-shape mutators (C/S/Q/O/X) are intentionally skipped to keep the BaseDN valid.

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

Disclaimer

This tool is provided for authorized security testing, research, and educational purposes only. By using this software, you agree to the following:

  • You will only use this tool on systems and networks where you have explicit written authorization to perform security testing.
  • The author is not responsible for any misuse, damage, service disruption, or any other consequences resulting from the use of this tool. This includes, but is not limited to, crashes, denial of service, data corruption, or unintended behavior on domain controllers or any other systems.
  • Some obfuscation techniques may produce LDAP queries that cause unexpected behavior on certain Active Directory configurations. Always test in a controlled lab environment before using against production systems.
  • The user assumes full responsibility for any actions performed with this tool and their consequences.
  • This tool is not intended to facilitate unauthorized access to computer systems. Unauthorized access to computer systems is illegal in most jurisdictions.

If you discover any issues or unexpected behavior, please report them via GitHub Issues.

License

MIT - see LICENSE