RelayLDAP is a menu-driven ldap3 client for reusing Active Directory LDAP and LDAPS sessions retained by Impacket's ntlmrelayx SOCKS server. It can also connect directly with NTLM credentials.
It provides:
- Guided selection of active LDAP/LDAPS relays from the local
ntlmrelayxAPI. - LDAP enumeration for identities, groups, delegation, LAPS, gMSA, policy, and other directory data.
- Explicit warnings for broad or credential-sensitive reads.
- Typed confirmation before every LDAP write.
- Permission-restricted JSONL event and result logs for evidence collection.
Caution
Use RelayLDAP only on systems you own or have explicit permission to test. LDAP writes can disable accounts, reset passwords, change privileged group membership, create identities, or otherwise disrupt production services. Read operations may be logged or detected.
- Which mode do I use?
- Installation
- Quick start
- How RelayLDAP works
- Blog
- Operations
- Traffic and detection considerations
- Evidence handling
- Validation status
- Troubleshooting
- Limitations
- Contributing
- Security reports
- Disclaimer and license
| Mode | Use it when | Connection | Password behavior |
|---|---|---|---|
| Relay | ntlmrelayx already holds the LDAP/LDAPS session you want to reuse. |
Run RelayLDAP through proxychains4 and select or enter the matching retained session. |
Use the built-in non-secret placeholder. Do not supply the account's real password. |
| Direct | You have valid NTLM credentials and want a normal LDAP/LDAPS connection. | Run RelayLDAP directly against the domain controller. | Use --ask-pass. The current release does not validate LDAPS server certificates. |
- Python 3.10 or newer
ldap3pycryptodome(provides MD4 for NTLMv2 construction on modern OpenSSL builds)- For retained-relay mode:
proxychains4and an active LDAP or LDAPS relay inntlmrelayx
Clone the repository and create an isolated Python environment:
git clone https://github.com/CyberAuth/relayldap.git
cd relayldap
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txtRelayLDAP does not import Impacket and does not require it as a Python package.
Impacket is an external operational prerequisite only when ntlmrelayx is the
component providing the retained SOCKS relay.
Example proxychains4.conf entry for the default ntlmrelayx SOCKS port, 1080:
strict_chain
proxy_dns
[ProxyList]
socks5 127.0.0.1 1080
The simplest invocation prompts for every connection setting before opening the operations menu:
proxychains4 -q python3 relayldap.pyRelayLDAP first offers to query the local ntlmrelayx SOCKS HTTP API. The API
defaults to http://127.0.0.1:9090; use the port passed to
ntlmrelayx -http-api-port when it was customized.
Example guided setup
Authentication mode: relay or direct [relay]: relay
Discover active LDAP/LDAPS relays from the ntlmrelayx API? [Y]: Y
ntlmrelayx API URL [http://127.0.0.1:9090]:
Active LDAP/LDAPS relays
# Protocol Target Identity Port ID Admin
1 LDAPS 192.0.2.10 ACME/RELAYUSER 636 23 N/A
Select relay [1]: 1
[+] Selected LDAPS://192.0.2.10:636 as ACME\RELAYUSER (client ID 23)
Base DN (blank discovers it after bind):
The selection populates protocol, target, port, domain, and username directly from the active-relay record. RelayLDAP then performs its normal Sicily NTLM bind; bind success is the final confirmation that the selected retained session is currently usable.
If the API is unavailable, returns no LDAP/LDAPS rows, or the operator answers
N, RelayLDAP falls back to manual entry. To skip discovery completely:
proxychains4 -q python3 relayldap.py --no-relay-discoveryTo use a nondefault API port:
proxychains4 -q python3 relayldap.py \
--relay-api http://127.0.0.1:9092The API request is made by a short child process with proxy-preload and generic
HTTP proxy variables removed. This prevents a localhost API request from being
sent back into the SOCKS listener while the main RelayLDAP process remains under
proxychains4 for LDAPS.
Important
Impacket's SOCKS API does not provide authentication in the tested implementation. Keep it bound to loopback or another appropriately protected management interface.
proxychains4 -q python3 relayldap.py \
--dc 192.0.2.10 \
--port 636 \
--domain ACME \
--username RELAYUSER \
--log-dir ./relayldap-evidenceDo not provide an actual password for retained-relay mode. The default placeholder is sufficient.
RelayLDAP can also act as an ordinary LDAPS client using a real account:
python3 relayldap.py \
--dc dc01.example.test \
--domain EXAMPLE \
--username analyst \
--ask-passPrefer --ask-pass to avoid placing a real password in shell history or the process list.
Warning
The current release disables LDAPS server-certificate validation for IP-addressed targets. Treat direct-authentication mode accordingly. Clear LDAP via --ldap should only be used when the transport and risk are understood.
flowchart LR
A[RelayLDAP / ldap3] -->|Sicily NTLM bind| B[proxychains]
B -->|SOCKS5| C[ntlmrelayx SOCKS server]
C -->|Select DOMAIN/username| D[Retained LDAPS session]
D -->|Authenticated LDAP operations| E[Domain controller]
In relay mode, the password supplied to ldap3 is only used to construct the client-side NTLM exchange. The SOCKS plugin selects an existing server-side relay by domain and username.
That means:
- The domain and username are not arbitrary.
- They must correspond to an active identity shown by
ntlmrelayx> socks. - The placeholder password can be any nonempty value; RelayLDAP defaults to
relayldap-placeholderin relay mode. - The destination and port must match the retained relay exactly.
For a SOCKS identity of ACME/RELAYUSER, enter:
Domain: ACME
Username: RELAYUSER
If the row shows ACME.COM/RELAYUSER, use ACME.COM. Exact matching is the safest choice.
For the origin story and protocol investigation, see When SOCKS Is Not Enough.
RelayLDAP provides an interactive menu after a successful bind.
| Menu | Operation | What it does |
|---|---|---|
| 1 | Current bound identity | Resolves the bound account and returns its direct memberships and control attributes. |
| 2 | Arbitrary LDAP search | Accepts a caller-supplied base, scope, filter, attributes, and result limit. Defaults to 100 results; limit 0 requires typing UNLIMITED. |
| 3 | List users | Enumerates user objects and selected identity, membership, description, and timestamp attributes. |
| 4 | List computers | Enumerates computer objects, operating-system data, SPNs, and delegation-related attributes. |
| 5 | List groups | Enumerates groups and their membership attributes. |
| 6 | Show group members | Retrieves membership for one resolved group. |
| 7 | Recursive memberships | Uses the AD matching-rule-in-chain query for nested memberships. |
| 8 | Kerberoast candidate discovery | Finds user objects with SPNs. It does not request service tickets. |
| 9 | AS-REP candidate discovery | Finds accounts with DONT_REQ_PREAUTH. It does not request an AS-REP. |
| 10 | Delegation configuration | Searches for unconstrained, constrained, and resource-based delegation attributes. |
| 11 | LAPS password attributes (raw/readable) | Finds computers for which a legacy or Windows LAPS password attribute is visible and returns the readable values. Cleartext/JSON values are shown as returned by AD; encrypted values are base64-encoded and are not decrypted. |
| 12 | gMSA accounts and raw password attributes | Lists gMSA objects and requests their SPNs, managed-password blob, and membership security descriptor. Readable binary values are base64-encoded; the tool does not decode them into a password or hash. |
| 13 | Interesting text/path attributes | Searches descriptions, comments, info fields, scripts, profiles, and home paths. |
| 14 | Domain policy | Reads password, lockout, and machine-account-quota policy from the domain object. |
SPN and AS-REP discovery includes disabled accounts and adds enabled and decoded uacFlags. A disabled account is useful configuration evidence but is not presently roastable.
| Menu | Operation | Potential impact |
|---|---|---|
| 20 | Create enabled user | Creates an AD identity, assigns a password, and enables it. |
| 21 | Add member to group | Can grant immediate privilege or application access. |
| 22 | Remove member from group | Can remove administrative or business access. |
| 23 | Set user/computer password | Can break services, scheduled tasks, trusts, or user access. |
| 24 | Enable account | Can reactivate a retired, compromised, or intentionally disabled identity. |
| 25 | Disable account | Can interrupt users, services, applications, or domain operations. |
| 26 | Create computer account | Adds a domain principal and may affect machine-account quota or delegation paths. |
| 27 | Generic attribute modification | Can alter nearly any writable LDAP attribute; misuse can break authentication or AD-integrated services. |
Every write operation displays a summary and requires the operator to type YES. Confirmation reduces accidents; it does not make the operation safe or reversible.
The menu labels broad, bulk, potentially expensive, and credential-sensitive
read operations. Selecting one prints a concise OPSEC notice without adding an
extra confirmation prompt. The exception is an unlimited arbitrary search,
which requires the operator to type UNLIMITED before any LDAP request is sent.
Read-only does not mean stealthy. Query visibility depends on domain-controller auditing, SACL configuration, LDAP diagnostic logging, network monitoring, identity analytics, and EDR/NDR coverage.
| Operation class | Relative traffic | Detection/impact notes |
|---|---|---|
| Current identity and domain policy | Low | Targeted base or account searches. Still attributable to the relayed identity and source connection. |
| SPN and AS-REP candidate discovery | Low to moderate | One directory-wide filtered search each. These options only discover candidates; subsequent Kerberos requests would be separate activity. |
| Users, computers, and groups | Moderate to high | Paged directory-wide enumeration. Volume scales with object count and requested multivalue attributes. |
| Recursive memberships | Moderate; potentially expensive | Matching-rule-in-chain can require significant DC work in large or deeply nested directories. Use against a specific principal. |
| Delegation and interesting-attribute searches | Moderate | Broad filters may touch large portions of the directory and can resemble attack-path or credential-discovery activity. |
| Arbitrary search | Operator controlled | A narrow indexed filter can be cheap; (objectClass=*), unlimited results, broad bases, and large attributes can be expensive. Set a result limit when possible. |
| LAPS and gMSA reads | High sensitivity | Credential-material access may be specifically audited or alerted even when the result is empty or access is denied. A zero-result or omitted attribute does not prove that LAPS or gMSA is absent. Treat returned output as secret material. |
| Any write | High | Account, password, group, computer, and attribute changes are commonly audited and may trigger identity-protection or SIEM detections. They can also cause immediate operational impact. |
Depending on audit policy and object SACLs, defenders may observe Directory Service Access activity such as event 4662. Account-management auditing can expose actions such as user creation, password reset, enable/disable, group changes, and computer creation. See Microsoft's documentation for Audit Directory Service Access and account-management events.
Recommended operator practice:
- Start with option 1 to confirm the retained identity.
- Prefer a precise base DN and indexed filter over a domain-wide wildcard search.
- Keep the default 100-result limit for exploratory arbitrary queries. Entering
0requires a separateUNLIMITEDconfirmation. - Avoid repeatedly requesting LAPS or gMSA material.
- Record the timestamp and purpose before any write.
- Define rollback steps before changing passwords, membership, account state, or arbitrary attributes.
- Stop if the retained relay disappears, changes identity, or reports that it is already in use.
RelayLDAP creates:
- A mode-
0700evidence directory. - A mode-
0600JSONL activity log. - A mode-
0600JSONL results log.
Bind passwords and passwords entered for account changes are redacted from RelayLDAP's evidence logs. Query results are not generally redacted.
Important
LAPS passwords, gMSA blobs, descriptions, comments, paths, group membership, and arbitrary-search output may be sensitive. Protect the evidence directory, do not commit it to source control, and follow the engagement's retention requirements.
The included .gitignore excludes RelayLDAP evidence files and
common local Python artifacts from commits.
The protocol and API assumptions were rechecked on 2026-08-16 against the
current upstream ntlmrelayx entry point,
SOCKS API implementation,
and LDAP SOCKS plugin.
The built-in directory filters use Microsoft's documented
LDAP matching rules,
UAC values,
Windows LAPS schema,
and gMSA class.
Relay discovery has been tested against a local mock of Impacket's six-field API response and a live retained relay. The live read-only check used this sequence:
- Queried a custom local API port.
- Filtered and displayed seven active LDAPS records.
- Selected one user relay and populated protocol, target, port, domain, username, and client ID.
- Completed an
ldap3Sicily NTLM bind through proxychains. - Discovered the domain naming context through RootDSE.
- Performed one targeted current-identity lookup.
- Exited without an LDAP write.
The relay-side debug log showed the expected empty bind, NTLM bind, session selection, intercepted unbind, and clean tunnel completion. Client names, addresses, and directory data are intentionally omitted from this repository.
Run the offline tests with:
python3 -m unittest -v test_relayldap.pyGitHub Actions runs the same test suite with the declared dependencies on Python 3.10, 3.12, and 3.14. Live validation remains limited to the read-only relay sequence described above; the repository does not claim live coverage of every directory operation or Windows Server version.
The client reached the relay, but its LDAP authentication mechanism was not one understood by the SOCKS plugin. RelayLDAP uses ldap3 NTLM specifically to produce the expected Sicily sequence.
The requested domain/username does not match an active relay, the destination differs from the retained target, or the session expired. Check:
ntlmrelayx> socks
Use the exact target, port, domain, and username shown there.
Confirm the port used by ntlmrelayx -http-api-port and query the endpoint
locally:
curl --noproxy '*' http://127.0.0.1:9090/ntlmrelayx/api/v1.0/relaysIf a custom port was used, replace 9090. RelayLDAP accepts either the API base
URL or the complete /ntlmrelayx/api/v1.0/relays endpoint. Use
--no-relay-discovery when manual entry is preferred.
The client attempted LDAP/389 while only an LDAPS/636 relay exists. Ensure the client is targeting 636 and does not perform a separate LDAP-signing or host-information probe against 389.
Check the ntlmrelayx console. If it reports an unknown bind request, this is an application-layer compatibility issue rather than a basic SOCKS routing failure.
RelayLDAP discovers user accounts with SPNs but does not request tickets. Zero results means no matching user SPNs were returned under the current base and permissions. Disabled candidates, when present, are displayed with enabled: false.
No visible user object had the DONT_REQ_PREAUTH flag under the selected search base. RelayLDAP does not send AS-REQ traffic.
RelayLDAP is an LDAP client, not a complete Active Directory exploitation framework. It does not:
- Request Kerberos TGTs, TGSs, or AS-REPs.
- Perform DCSync or DRSUAPI replication.
- Execute commands through SMB, WMI, WinRM, RDP, or MSSQL.
- Capture or crack NetNTLM responses.
- Coerce authentication or start
ntlmrelayx. - Decode every binary security descriptor or managed-password structure.
- Guarantee that a write is operationally safe or automatically reversible.
- Make LDAP activity invisible to EDR, NDR, domain-controller logging, or identity analytics.
The generic search and attribute-modification options cover LDAP operations not included elsewhere in the menu.
Keep client names, domains, addresses, credentials, hashes, tickets, and evidence out of commits. New write operations should document their audit footprint, potential impact, and rollback procedure. Run the offline tests before opening a pull request.
Compatibility reports should include:
- RelayLDAP and Python versions.
ldap3, Impacket, and proxychains versions.- LDAP versus LDAPS.
- The bind-related lines from
ntlmrelayx -debug. - Sanitized errors with all client identifiers removed.
Please follow SECURITY.md for private vulnerability reporting.
Do not include credentials, directory data, or client identifiers in a public
issue.
RelayLDAP is provided for lawful security research and authorized testing only. Use it only on systems you own or have explicit permission to assess and only within the agreed rules of engagement. You are solely responsible for scope, change control, evidence protection, cleanup, and compliance with applicable law and organizational policy.
The software is provided as is, without warranty. LDAP operations can expose sensitive information, change directory state, interrupt services, or trigger security controls. The project authors and contributors are not responsible for misuse, unauthorized activity, data loss, service disruption, security incidents, or other damages resulting from use of the software. No feature or warning in RelayLDAP makes an operation inherently safe, stealthy, or authorized.
RelayLDAP is licensed under the BSD 3-Clause License.
