ldapx 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. ldapx-0.1.0/.gitignore +14 -0
  2. ldapx-0.1.0/LICENSE +21 -0
  3. ldapx-0.1.0/PKG-INFO +240 -0
  4. ldapx-0.1.0/README.md +208 -0
  5. ldapx-0.1.0/examples/badldap_adapter.py +27 -0
  6. ldapx-0.1.0/examples/basic_usage.py +52 -0
  7. ldapx-0.1.0/examples/chain_usage.py +36 -0
  8. ldapx-0.1.0/pyproject.toml +52 -0
  9. ldapx-0.1.0/src/ldapx/__init__.py +321 -0
  10. ldapx-0.1.0/src/ldapx/_version.py +1 -0
  11. ldapx-0.1.0/src/ldapx/adapters/__init__.py +10 -0
  12. ldapx-0.1.0/src/ldapx/adapters/badldap.py +130 -0
  13. ldapx-0.1.0/src/ldapx/cli/__init__.py +0 -0
  14. ldapx-0.1.0/src/ldapx/cli/main.py +257 -0
  15. ldapx-0.1.0/src/ldapx/middlewares/__init__.py +19 -0
  16. ldapx-0.1.0/src/ldapx/middlewares/attrentries/__init__.py +16 -0
  17. ldapx-0.1.0/src/ldapx/middlewares/attrentries/obfuscation.py +44 -0
  18. ldapx-0.1.0/src/ldapx/middlewares/attrentries/types.py +26 -0
  19. ldapx-0.1.0/src/ldapx/middlewares/attrlist/__init__.py +30 -0
  20. ldapx-0.1.0/src/ldapx/middlewares/attrlist/obfuscation.py +136 -0
  21. ldapx-0.1.0/src/ldapx/middlewares/attrlist/types.py +26 -0
  22. ldapx-0.1.0/src/ldapx/middlewares/basedn/__init__.py +20 -0
  23. ldapx-0.1.0/src/ldapx/middlewares/basedn/obfuscation.py +95 -0
  24. ldapx-0.1.0/src/ldapx/middlewares/basedn/types.py +26 -0
  25. ldapx-0.1.0/src/ldapx/middlewares/filter/__init__.py +56 -0
  26. ldapx-0.1.0/src/ldapx/middlewares/filter/helpers.py +73 -0
  27. ldapx-0.1.0/src/ldapx/middlewares/filter/obfuscation.py +629 -0
  28. ldapx-0.1.0/src/ldapx/middlewares/filter/types.py +33 -0
  29. ldapx-0.1.0/src/ldapx/middlewares/helpers/__init__.py +43 -0
  30. ldapx-0.1.0/src/ldapx/middlewares/helpers/string.py +214 -0
  31. ldapx-0.1.0/src/ldapx/middlewares/options.py +67 -0
  32. ldapx-0.1.0/src/ldapx/parser/__init__.py +41 -0
  33. ldapx-0.1.0/src/ldapx/parser/consts.py +2915 -0
  34. ldapx-0.1.0/src/ldapx/parser/filter.py +329 -0
  35. ldapx-0.1.0/src/ldapx/parser/validation.py +37 -0
  36. ldapx-0.1.0/tests/__init__.py +0 -0
  37. ldapx-0.1.0/tests/test_attrentries_obf.py +80 -0
  38. ldapx-0.1.0/tests/test_attrlist_obf.py +125 -0
  39. ldapx-0.1.0/tests/test_basedn_obf.py +101 -0
  40. ldapx-0.1.0/tests/test_chains.py +92 -0
  41. ldapx-0.1.0/tests/test_cli.py +135 -0
  42. ldapx-0.1.0/tests/test_filter_obf.py +96 -0
  43. ldapx-0.1.0/tests/test_helpers.py +192 -0
  44. ldapx-0.1.0/tests/test_parser.py +124 -0
ldapx-0.1.0/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ *.egg
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+ *.so
12
+ .env
13
+ .venv/
14
+ venv/
ldapx-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 João Paulo Assis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ldapx-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.4
2
+ Name: ldapx
3
+ Version: 0.1.0
4
+ Summary: LDAP query obfuscation library - Python port of github.com/Macmod/ldapx
5
+ Project-URL: Homepage, https://github.com/Macmod/ldapx-py
6
+ Project-URL: Repository, https://github.com/Macmod/ldapx-py
7
+ Author: João Paulo Assis
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: active-directory,ldap,obfuscation,red-team,security
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Information Technology
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: System :: Networking
22
+ Requires-Python: >=3.9
23
+ Provides-Extra: badldap
24
+ Requires-Dist: badldap>=0.1.0; extra == 'badldap'
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
27
+ Requires-Dist: pytest>=7.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.4; extra == 'dev'
29
+ Provides-Extra: ldap3
30
+ Requires-Dist: ldap3>=2.9; extra == 'ldap3'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # ldapx
34
+
35
+ [![PyPI version](https://img.shields.io/pypi/v/ldapx)](https://pypi.org/project/ldapx/)
36
+ [![Python versions](https://img.shields.io/pypi/pyversions/ldapx)](https://pypi.org/project/ldapx/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
38
+
39
+ Python port of [ldapx](https://github.com/Macmod/ldapx) - LDAP query obfuscation library.
40
+
41
+ Transform LDAP filters, BaseDNs, attribute lists, and attribute entries using composable middleware chains. Zero dependencies. Works as a library or CLI tool.
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install ldapx
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ```python
52
+ import ldapx
53
+
54
+ # Obfuscate a filter with case mutation + OID attributes
55
+ result = ldapx.obfuscate_filter("(cn=admin)", "CO")
56
+ # → (oID.02.05.04.03 =aDmIn)
57
+
58
+ # Obfuscate a BaseDN
59
+ result = ldapx.obfuscate_basedn("DC=corp,DC=local", "COQ")
60
+ # → oID.0.9.2342.19200300.100.1.25 ="cOrP",oID.0.9.2342.19200300.100.1.25 ="lOcAl"
61
+
62
+ # Obfuscate an attribute list
63
+ result = ldapx.obfuscate_attrlist(["cn", "sAMAccountName"], "COR")
64
+ # → ['oID.1.2.840.113556.1.4.221 ', 'oID.02.5.4.3 ']
65
+ ```
66
+
67
+ ## Usage Patterns
68
+
69
+ ### Pattern 1: High-level chain strings (simplest)
70
+
71
+ ```python
72
+ import ldapx
73
+
74
+ result = ldapx.obfuscate_filter("(sAMAccountName=user1)", "COGDR")
75
+ result = ldapx.obfuscate_basedn("DC=corp,DC=local", "CSQOX")
76
+ result = ldapx.obfuscate_attrlist(["cn", "sAMAccountName"], "CRDG")
77
+ result = ldapx.obfuscate_attrentries({"cn": [b"test"]}, "CR")
78
+ ```
79
+
80
+ ### Pattern 2: Explicit chain (Go-style)
81
+
82
+ ```python
83
+ from ldapx.parser import query_to_filter, filter_to_query
84
+ from ldapx.middlewares.filter import (
85
+ FilterMiddlewareChain,
86
+ rand_case_filter_obf,
87
+ oid_attribute_filter_obf,
88
+ )
89
+
90
+ chain = FilterMiddlewareChain()
91
+ chain.add("Case", lambda: rand_case_filter_obf(0.7))
92
+ chain.add("OID", lambda: oid_attribute_filter_obf(4, 4))
93
+
94
+ f = query_to_filter("(cn=admin)")
95
+ f = chain.execute(f, verbose=True)
96
+ result = filter_to_query(f)
97
+ ```
98
+
99
+ ### Pattern 3: Direct composition
100
+
101
+ ```python
102
+ from ldapx.parser import query_to_filter, filter_to_query
103
+ from ldapx.middlewares.filter import rand_case_filter_obf, oid_attribute_filter_obf
104
+
105
+ f = query_to_filter("(cn=admin)")
106
+ f = rand_case_filter_obf(0.5)(f)
107
+ f = oid_attribute_filter_obf(2, 2)(f)
108
+ result = filter_to_query(f)
109
+ ```
110
+
111
+ ## CLI
112
+
113
+ ```bash
114
+ # Obfuscate a filter
115
+ ldapx filter -f "(cn=admin)" -c "COGDR"
116
+
117
+ # Generate 5 variants
118
+ ldapx filter -f "(cn=admin)" -c "COGDR" -n 5
119
+
120
+ # Obfuscate a BaseDN
121
+ ldapx basedn -b "DC=corp,DC=local" -c "CSQOX"
122
+
123
+ # Obfuscate attribute list
124
+ ldapx attrlist -a "cn,sAMAccountName,memberOf" -c "CRDG"
125
+
126
+ # List available codes
127
+ ldapx codes --all
128
+
129
+ # Pipe from stdin
130
+ echo "(cn=admin)" | ldapx filter -c "COGDR"
131
+
132
+ # JSON output
133
+ ldapx filter -f "(cn=admin)" -c "CO" --json
134
+
135
+ # Custom options
136
+ ldapx filter -f "(cn=admin)" -c "CO" -o FiltCaseProb=0.8 -o FiltOIDMaxSpaces=4
137
+ ```
138
+
139
+ ## Middleware Codes
140
+
141
+ ### Filter (`-f`)
142
+
143
+ | Code | Name | Description |
144
+ |------|------|-------------|
145
+ | `C` | Random case | Randomize case of attribute names and values |
146
+ | `S` | Random spacing | Add context-aware spacing (ANR, DN, SID) |
147
+ | `G` | Garbage filters | Wrap filters in OR with random garbage |
148
+ | `T` | Replace tautologies | Replace simple presence filters with tautologies |
149
+ | `R` | Boolean reorder | Randomly shuffle AND/OR clauses |
150
+ | `O` | OID attributes | Replace attribute names with OIDs |
151
+ | `X` | Hex value encoding | Hex-encode characters in DN-type values |
152
+ | `t` | Timestamp garbage | Add garbage to timestamp patterns |
153
+ | `B` | Add random boolean | Wrap with redundant AND/OR |
154
+ | `D` | Double negation | Apply `(!(!(filter)))` |
155
+ | `M` | DeMorgan transform | Apply De Morgan's laws |
156
+ | `b` | Bitwise breakout | Convert equality to bitwise matching rules |
157
+ | `d` | Bitwise decompose | Break bitwise values into individual bits |
158
+ | `I` | Equality by inclusion | `(attr=val)` to range + exclusion |
159
+ | `E` | Equality by exclusion | `(attr=val)` to presence + NOT range |
160
+ | `A` | Approx match | `(attr=val)` to `(attr~=val)` |
161
+ | `x` | Extensible match | `(attr=val)` to `(attr:=val)` |
162
+ | `Z` | Prepend zeros | Add leading zeros to numbers/SIDs |
163
+ | `s` | Substring split | Split equality into substring match |
164
+ | `N` | Names to ANR | Replace ANR-set attributes with `aNR` |
165
+ | `n` | ANR garbage | Add garbage to ANR substring queries |
166
+
167
+ ### BaseDN (`-b`)
168
+
169
+ | Code | Name | Description |
170
+ |------|------|-------------|
171
+ | `C` | Random case | Randomize case |
172
+ | `S` | Random spacing | Add spaces around DN |
173
+ | `Q` | Double quotes | Wrap DN values in quotes |
174
+ | `O` | OID attributes | Replace DN attr names with OIDs |
175
+ | `X` | Hex value encoding | Hex-encode DN value characters |
176
+
177
+ ### AttrList (`-a`)
178
+
179
+ | Code | Name | Description |
180
+ |------|------|-------------|
181
+ | `C` | Random case | Randomize case |
182
+ | `R` | Reorder list | Shuffle attribute order |
183
+ | `D` | Duplicate | Add duplicate entries |
184
+ | `O` | OID attributes | Replace with OIDs |
185
+ | `G` | Garbage (non-existing) | Add random fake attributes |
186
+ | `g` | Garbage (existing) | Add random real attributes |
187
+ | `W` | Replace with wildcard | Replace list with `*` |
188
+ | `w` | Add wildcard | Append `*` to list |
189
+ | `p` | Add plus | Append `+` (operational attrs) |
190
+ | `e` | Replace with empty | Replace with empty list |
191
+
192
+ ### AttrEntries
193
+
194
+ | Code | Name | Description |
195
+ |------|------|-------------|
196
+ | `C` | Random case | Randomize attribute name case |
197
+ | `R` | Reorder list | Shuffle attribute order |
198
+ | `O` | OID attributes | Replace with plain OIDs |
199
+
200
+ ## Options
201
+
202
+ Customize middleware parameters via `Options`:
203
+
204
+ ```python
205
+ import ldapx
206
+
207
+ opts = ldapx.Options(
208
+ FiltCaseProb=0.8, # Higher case mutation probability
209
+ FiltOIDMaxSpaces=4, # More spaces after OIDs
210
+ FiltGarbageMaxElems=3, # More garbage filters
211
+ BDNSpacingMaxSpaces=4, # More spacing in BaseDN
212
+ )
213
+
214
+ result = ldapx.obfuscate_filter("(cn=admin)", "COGDR", options=opts)
215
+ ```
216
+
217
+ ## Adapters
218
+
219
+ The core library has zero dependencies and returns strings. For integration with specific LDAP libraries, use adapters:
220
+
221
+ ### badldap adapter
222
+
223
+ ```python
224
+ # pip install ldapx[badldap]
225
+ from ldapx.parser import query_to_filter
226
+ from ldapx.middlewares.filter import rand_case_filter_obf
227
+ from ldapx.adapters.badldap import ast_to_asn1
228
+
229
+ f = query_to_filter("(cn=admin)")
230
+ f = rand_case_filter_obf(0.5)(f)
231
+ asn1_filter = ast_to_asn1(f) # badldap ASN1 Filter object
232
+ ```
233
+
234
+ ## Go Version
235
+
236
+ For LDAP proxy mode (intercept and transform packets on the fly), use the Go version: [github.com/Macmod/ldapx](https://github.com/Macmod/ldapx)
237
+
238
+ ## License
239
+
240
+ MIT - see [LICENSE](LICENSE)
ldapx-0.1.0/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # ldapx
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/ldapx)](https://pypi.org/project/ldapx/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/ldapx)](https://pypi.org/project/ldapx/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ Python port of [ldapx](https://github.com/Macmod/ldapx) - LDAP query obfuscation library.
8
+
9
+ Transform LDAP filters, BaseDNs, attribute lists, and attribute entries using composable middleware chains. Zero dependencies. Works as a library or CLI tool.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install ldapx
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ import ldapx
21
+
22
+ # Obfuscate a filter with case mutation + OID attributes
23
+ result = ldapx.obfuscate_filter("(cn=admin)", "CO")
24
+ # → (oID.02.05.04.03 =aDmIn)
25
+
26
+ # Obfuscate a BaseDN
27
+ result = ldapx.obfuscate_basedn("DC=corp,DC=local", "COQ")
28
+ # → oID.0.9.2342.19200300.100.1.25 ="cOrP",oID.0.9.2342.19200300.100.1.25 ="lOcAl"
29
+
30
+ # Obfuscate an attribute list
31
+ result = ldapx.obfuscate_attrlist(["cn", "sAMAccountName"], "COR")
32
+ # → ['oID.1.2.840.113556.1.4.221 ', 'oID.02.5.4.3 ']
33
+ ```
34
+
35
+ ## Usage Patterns
36
+
37
+ ### Pattern 1: High-level chain strings (simplest)
38
+
39
+ ```python
40
+ import ldapx
41
+
42
+ result = ldapx.obfuscate_filter("(sAMAccountName=user1)", "COGDR")
43
+ result = ldapx.obfuscate_basedn("DC=corp,DC=local", "CSQOX")
44
+ result = ldapx.obfuscate_attrlist(["cn", "sAMAccountName"], "CRDG")
45
+ result = ldapx.obfuscate_attrentries({"cn": [b"test"]}, "CR")
46
+ ```
47
+
48
+ ### Pattern 2: Explicit chain (Go-style)
49
+
50
+ ```python
51
+ from ldapx.parser import query_to_filter, filter_to_query
52
+ from ldapx.middlewares.filter import (
53
+ FilterMiddlewareChain,
54
+ rand_case_filter_obf,
55
+ oid_attribute_filter_obf,
56
+ )
57
+
58
+ chain = FilterMiddlewareChain()
59
+ chain.add("Case", lambda: rand_case_filter_obf(0.7))
60
+ chain.add("OID", lambda: oid_attribute_filter_obf(4, 4))
61
+
62
+ f = query_to_filter("(cn=admin)")
63
+ f = chain.execute(f, verbose=True)
64
+ result = filter_to_query(f)
65
+ ```
66
+
67
+ ### Pattern 3: Direct composition
68
+
69
+ ```python
70
+ from ldapx.parser import query_to_filter, filter_to_query
71
+ from ldapx.middlewares.filter import rand_case_filter_obf, oid_attribute_filter_obf
72
+
73
+ f = query_to_filter("(cn=admin)")
74
+ f = rand_case_filter_obf(0.5)(f)
75
+ f = oid_attribute_filter_obf(2, 2)(f)
76
+ result = filter_to_query(f)
77
+ ```
78
+
79
+ ## CLI
80
+
81
+ ```bash
82
+ # Obfuscate a filter
83
+ ldapx filter -f "(cn=admin)" -c "COGDR"
84
+
85
+ # Generate 5 variants
86
+ ldapx filter -f "(cn=admin)" -c "COGDR" -n 5
87
+
88
+ # Obfuscate a BaseDN
89
+ ldapx basedn -b "DC=corp,DC=local" -c "CSQOX"
90
+
91
+ # Obfuscate attribute list
92
+ ldapx attrlist -a "cn,sAMAccountName,memberOf" -c "CRDG"
93
+
94
+ # List available codes
95
+ ldapx codes --all
96
+
97
+ # Pipe from stdin
98
+ echo "(cn=admin)" | ldapx filter -c "COGDR"
99
+
100
+ # JSON output
101
+ ldapx filter -f "(cn=admin)" -c "CO" --json
102
+
103
+ # Custom options
104
+ ldapx filter -f "(cn=admin)" -c "CO" -o FiltCaseProb=0.8 -o FiltOIDMaxSpaces=4
105
+ ```
106
+
107
+ ## Middleware Codes
108
+
109
+ ### Filter (`-f`)
110
+
111
+ | Code | Name | Description |
112
+ |------|------|-------------|
113
+ | `C` | Random case | Randomize case of attribute names and values |
114
+ | `S` | Random spacing | Add context-aware spacing (ANR, DN, SID) |
115
+ | `G` | Garbage filters | Wrap filters in OR with random garbage |
116
+ | `T` | Replace tautologies | Replace simple presence filters with tautologies |
117
+ | `R` | Boolean reorder | Randomly shuffle AND/OR clauses |
118
+ | `O` | OID attributes | Replace attribute names with OIDs |
119
+ | `X` | Hex value encoding | Hex-encode characters in DN-type values |
120
+ | `t` | Timestamp garbage | Add garbage to timestamp patterns |
121
+ | `B` | Add random boolean | Wrap with redundant AND/OR |
122
+ | `D` | Double negation | Apply `(!(!(filter)))` |
123
+ | `M` | DeMorgan transform | Apply De Morgan's laws |
124
+ | `b` | Bitwise breakout | Convert equality to bitwise matching rules |
125
+ | `d` | Bitwise decompose | Break bitwise values into individual bits |
126
+ | `I` | Equality by inclusion | `(attr=val)` to range + exclusion |
127
+ | `E` | Equality by exclusion | `(attr=val)` to presence + NOT range |
128
+ | `A` | Approx match | `(attr=val)` to `(attr~=val)` |
129
+ | `x` | Extensible match | `(attr=val)` to `(attr:=val)` |
130
+ | `Z` | Prepend zeros | Add leading zeros to numbers/SIDs |
131
+ | `s` | Substring split | Split equality into substring match |
132
+ | `N` | Names to ANR | Replace ANR-set attributes with `aNR` |
133
+ | `n` | ANR garbage | Add garbage to ANR substring queries |
134
+
135
+ ### BaseDN (`-b`)
136
+
137
+ | Code | Name | Description |
138
+ |------|------|-------------|
139
+ | `C` | Random case | Randomize case |
140
+ | `S` | Random spacing | Add spaces around DN |
141
+ | `Q` | Double quotes | Wrap DN values in quotes |
142
+ | `O` | OID attributes | Replace DN attr names with OIDs |
143
+ | `X` | Hex value encoding | Hex-encode DN value characters |
144
+
145
+ ### AttrList (`-a`)
146
+
147
+ | Code | Name | Description |
148
+ |------|------|-------------|
149
+ | `C` | Random case | Randomize case |
150
+ | `R` | Reorder list | Shuffle attribute order |
151
+ | `D` | Duplicate | Add duplicate entries |
152
+ | `O` | OID attributes | Replace with OIDs |
153
+ | `G` | Garbage (non-existing) | Add random fake attributes |
154
+ | `g` | Garbage (existing) | Add random real attributes |
155
+ | `W` | Replace with wildcard | Replace list with `*` |
156
+ | `w` | Add wildcard | Append `*` to list |
157
+ | `p` | Add plus | Append `+` (operational attrs) |
158
+ | `e` | Replace with empty | Replace with empty list |
159
+
160
+ ### AttrEntries
161
+
162
+ | Code | Name | Description |
163
+ |------|------|-------------|
164
+ | `C` | Random case | Randomize attribute name case |
165
+ | `R` | Reorder list | Shuffle attribute order |
166
+ | `O` | OID attributes | Replace with plain OIDs |
167
+
168
+ ## Options
169
+
170
+ Customize middleware parameters via `Options`:
171
+
172
+ ```python
173
+ import ldapx
174
+
175
+ opts = ldapx.Options(
176
+ FiltCaseProb=0.8, # Higher case mutation probability
177
+ FiltOIDMaxSpaces=4, # More spaces after OIDs
178
+ FiltGarbageMaxElems=3, # More garbage filters
179
+ BDNSpacingMaxSpaces=4, # More spacing in BaseDN
180
+ )
181
+
182
+ result = ldapx.obfuscate_filter("(cn=admin)", "COGDR", options=opts)
183
+ ```
184
+
185
+ ## Adapters
186
+
187
+ The core library has zero dependencies and returns strings. For integration with specific LDAP libraries, use adapters:
188
+
189
+ ### badldap adapter
190
+
191
+ ```python
192
+ # pip install ldapx[badldap]
193
+ from ldapx.parser import query_to_filter
194
+ from ldapx.middlewares.filter import rand_case_filter_obf
195
+ from ldapx.adapters.badldap import ast_to_asn1
196
+
197
+ f = query_to_filter("(cn=admin)")
198
+ f = rand_case_filter_obf(0.5)(f)
199
+ asn1_filter = ast_to_asn1(f) # badldap ASN1 Filter object
200
+ ```
201
+
202
+ ## Go Version
203
+
204
+ For LDAP proxy mode (intercept and transform packets on the fly), use the Go version: [github.com/Macmod/ldapx](https://github.com/Macmod/ldapx)
205
+
206
+ ## License
207
+
208
+ MIT - see [LICENSE](LICENSE)
@@ -0,0 +1,27 @@
1
+ """
2
+ Example: Using ldapx with badldap via the adapter.
3
+
4
+ Requires: pip install ldapx[badldap]
5
+ """
6
+
7
+ from ldapx.parser import query_to_filter
8
+ from ldapx.middlewares.filter import rand_case_filter_obf, oid_attribute_filter_obf
9
+
10
+ # Parse and obfuscate
11
+ f = query_to_filter("(sAMAccountName=admin)")
12
+ f = rand_case_filter_obf(0.5)(f)
13
+ f = oid_attribute_filter_obf(2, 2)(f)
14
+
15
+ # Convert to badldap ASN1 (requires badldap installed)
16
+ try:
17
+ from ldapx.adapters.badldap import ast_to_asn1
18
+
19
+ asn1_filter = ast_to_asn1(f)
20
+ print(f"ASN1 Filter: {asn1_filter}")
21
+ print("Successfully converted to badldap ASN1 Filter object!")
22
+ except ImportError:
23
+ print("badldap not installed. Install with: pip install ldapx[badldap]")
24
+ print("Falling back to string output:")
25
+
26
+ from ldapx.parser import filter_to_query
27
+ print(f"String: {filter_to_query(f)}")
@@ -0,0 +1,52 @@
1
+ """Basic usage examples for ldapx."""
2
+
3
+ import ldapx
4
+
5
+ # --- Filter obfuscation ---
6
+
7
+ print("=== Filter Obfuscation ===\n")
8
+
9
+ # Simple case + OID
10
+ original = "(sAMAccountName=admin)"
11
+ result = ldapx.obfuscate_filter(original, "CO")
12
+ print(f"Original: {original}")
13
+ print(f"CO: {result}\n")
14
+
15
+ # Aggressive chain
16
+ result = ldapx.obfuscate_filter(original, "COGDRM")
17
+ print(f"COGDRM: {result}\n")
18
+
19
+ # --- BaseDN obfuscation ---
20
+
21
+ print("=== BaseDN Obfuscation ===\n")
22
+
23
+ original = "DC=corp,DC=local"
24
+ result = ldapx.obfuscate_basedn(original, "COQ")
25
+ print(f"Original: {original}")
26
+ print(f"COQ: {result}\n")
27
+
28
+ # --- AttrList obfuscation ---
29
+
30
+ print("=== AttrList Obfuscation ===\n")
31
+
32
+ attrs = ["cn", "sAMAccountName", "memberOf"]
33
+ result = ldapx.obfuscate_attrlist(attrs, "COR")
34
+ print(f"Original: {attrs}")
35
+ print(f"COR: {result}\n")
36
+
37
+ # --- AttrEntries obfuscation ---
38
+
39
+ print("=== AttrEntries Obfuscation ===\n")
40
+
41
+ entries = {"cn": [b"TestUser"], "description": [b"A test user"]}
42
+ result = ldapx.obfuscate_attrentries(entries, "CR")
43
+ print(f"Original: {entries}")
44
+ print(f"CR: {result}\n")
45
+
46
+ # --- Custom options ---
47
+
48
+ print("=== Custom Options ===\n")
49
+
50
+ opts = ldapx.Options(FiltCaseProb=1.0, FiltOIDMaxSpaces=5)
51
+ result = ldapx.obfuscate_filter("(cn=admin)", "CO", options=opts)
52
+ print(f"With custom options: {result}")
@@ -0,0 +1,36 @@
1
+ """Example: Using explicit middleware chains (Go-style pattern)."""
2
+
3
+ from ldapx.parser import query_to_filter, filter_to_query
4
+ from ldapx.middlewares.filter import (
5
+ FilterMiddlewareChain,
6
+ rand_case_filter_obf,
7
+ oid_attribute_filter_obf,
8
+ rand_garbage_filter_obf,
9
+ de_morgan_bool_filter_obf,
10
+ rand_bool_reorder_filter_obf,
11
+ )
12
+
13
+ # Build a custom chain
14
+ chain = FilterMiddlewareChain()
15
+ chain.add("Case", lambda: rand_case_filter_obf(0.7))
16
+ chain.add("OID", lambda: oid_attribute_filter_obf(3, 3))
17
+ chain.add("Garbage", lambda: rand_garbage_filter_obf(1, 8))
18
+ chain.add("DeMorgan", lambda: de_morgan_bool_filter_obf())
19
+ chain.add("Reorder", lambda: rand_bool_reorder_filter_obf())
20
+
21
+ # Parse, execute chain, convert back
22
+ query = "(&(objectClass=user)(sAMAccountName=admin))"
23
+ print(f"Original: {query}\n")
24
+
25
+ f = query_to_filter(query)
26
+ result = chain.execute(f, verbose=True)
27
+ output = filter_to_query(result)
28
+ print(f"\nObfuscated: {output}")
29
+
30
+ # --- Direct composition (flashingestor-style) ---
31
+ print("\n--- Direct Composition ---\n")
32
+
33
+ f = query_to_filter("(cn=admin)")
34
+ f = rand_case_filter_obf(0.5)(f)
35
+ f = oid_attribute_filter_obf(2, 2)(f)
36
+ print(f"Direct: {filter_to_query(f)}")
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ldapx"
7
+ version = "0.1.0"
8
+ description = "LDAP query obfuscation library - Python port of github.com/Macmod/ldapx"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "João Paulo Assis" },
14
+ ]
15
+ keywords = ["ldap", "obfuscation", "active-directory", "security", "red-team"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Information Technology",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Security",
27
+ "Topic :: System :: Networking",
28
+ ]
29
+
30
+ dependencies = []
31
+
32
+ [project.optional-dependencies]
33
+ badldap = ["badldap>=0.1.0"]
34
+ ldap3 = ["ldap3>=2.9"]
35
+ dev = ["pytest>=7.0", "pytest-cov>=4.0", "ruff>=0.4"]
36
+
37
+ [project.scripts]
38
+ ldapx = "ldapx.cli.main:main"
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/Macmod/ldapx-py"
42
+ Repository = "https://github.com/Macmod/ldapx-py"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/ldapx"]
46
+
47
+ [tool.ruff]
48
+ target-version = "py39"
49
+ line-length = 120
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]