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.
- ldapx-0.1.0/.gitignore +14 -0
- ldapx-0.1.0/LICENSE +21 -0
- ldapx-0.1.0/PKG-INFO +240 -0
- ldapx-0.1.0/README.md +208 -0
- ldapx-0.1.0/examples/badldap_adapter.py +27 -0
- ldapx-0.1.0/examples/basic_usage.py +52 -0
- ldapx-0.1.0/examples/chain_usage.py +36 -0
- ldapx-0.1.0/pyproject.toml +52 -0
- ldapx-0.1.0/src/ldapx/__init__.py +321 -0
- ldapx-0.1.0/src/ldapx/_version.py +1 -0
- ldapx-0.1.0/src/ldapx/adapters/__init__.py +10 -0
- ldapx-0.1.0/src/ldapx/adapters/badldap.py +130 -0
- ldapx-0.1.0/src/ldapx/cli/__init__.py +0 -0
- ldapx-0.1.0/src/ldapx/cli/main.py +257 -0
- ldapx-0.1.0/src/ldapx/middlewares/__init__.py +19 -0
- ldapx-0.1.0/src/ldapx/middlewares/attrentries/__init__.py +16 -0
- ldapx-0.1.0/src/ldapx/middlewares/attrentries/obfuscation.py +44 -0
- ldapx-0.1.0/src/ldapx/middlewares/attrentries/types.py +26 -0
- ldapx-0.1.0/src/ldapx/middlewares/attrlist/__init__.py +30 -0
- ldapx-0.1.0/src/ldapx/middlewares/attrlist/obfuscation.py +136 -0
- ldapx-0.1.0/src/ldapx/middlewares/attrlist/types.py +26 -0
- ldapx-0.1.0/src/ldapx/middlewares/basedn/__init__.py +20 -0
- ldapx-0.1.0/src/ldapx/middlewares/basedn/obfuscation.py +95 -0
- ldapx-0.1.0/src/ldapx/middlewares/basedn/types.py +26 -0
- ldapx-0.1.0/src/ldapx/middlewares/filter/__init__.py +56 -0
- ldapx-0.1.0/src/ldapx/middlewares/filter/helpers.py +73 -0
- ldapx-0.1.0/src/ldapx/middlewares/filter/obfuscation.py +629 -0
- ldapx-0.1.0/src/ldapx/middlewares/filter/types.py +33 -0
- ldapx-0.1.0/src/ldapx/middlewares/helpers/__init__.py +43 -0
- ldapx-0.1.0/src/ldapx/middlewares/helpers/string.py +214 -0
- ldapx-0.1.0/src/ldapx/middlewares/options.py +67 -0
- ldapx-0.1.0/src/ldapx/parser/__init__.py +41 -0
- ldapx-0.1.0/src/ldapx/parser/consts.py +2915 -0
- ldapx-0.1.0/src/ldapx/parser/filter.py +329 -0
- ldapx-0.1.0/src/ldapx/parser/validation.py +37 -0
- ldapx-0.1.0/tests/__init__.py +0 -0
- ldapx-0.1.0/tests/test_attrentries_obf.py +80 -0
- ldapx-0.1.0/tests/test_attrlist_obf.py +125 -0
- ldapx-0.1.0/tests/test_basedn_obf.py +101 -0
- ldapx-0.1.0/tests/test_chains.py +92 -0
- ldapx-0.1.0/tests/test_cli.py +135 -0
- ldapx-0.1.0/tests/test_filter_obf.py +96 -0
- ldapx-0.1.0/tests/test_helpers.py +192 -0
- ldapx-0.1.0/tests/test_parser.py +124 -0
ldapx-0.1.0/.gitignore
ADDED
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
|
+
[](https://pypi.org/project/ldapx/)
|
|
36
|
+
[](https://pypi.org/project/ldapx/)
|
|
37
|
+
[](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
|
+
[](https://pypi.org/project/ldapx/)
|
|
4
|
+
[](https://pypi.org/project/ldapx/)
|
|
5
|
+
[](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"]
|