Added
-
Active decryption of protected LDAP traffic. ldapx transparently unwraps, inspects/modifies, and re-wraps post-bind traffic once credentials are supplied. Supported methods:
Sicily(NTLM)SASL/GSSAPI(Kerberos)SASL/GSS-SPNEGO(Kerberos)SASL/GSSAPI(NTLM)SASL/GSS-SPNEGO(NTLM)SASL/DIGEST-MD5
Note
SASL/GSSAPI (NTLM) is a real wire format that Microsoft's specs completely ignore - according to Microsoft, SASL/GSSAPI in Windows is Kerberos-only (what would conform to RFC4752). But SASL/GSSAPI (NTLM) is used by ADExplorer itself (possibly by many Windows libraries) and it works just fine against a Windows DC, so I considered it a valid mechanism to implement.
Essentially, this release means that clients that use security layers on top of the LDAP messages (such as ADExplorer from issue #1) can now be intercepted by ldapx, as long as you provide the necessary credential material for active decryption via --decrypt-* flags. All --decrypt-* flags are opt-in and unprotected traffic remains untouched. You may still intercept/modify LDAP traffic that uses any of these methods for authentication normally, as long as it's unprotected in the wire.
For protected traffic, the following flags are required to enable decryption:
-
NTLM (via Sicily, GSSAPI or SPNEGO):
--decrypt-hash(NT hash of the connecting account) or--decrypt-password(plain password of the connecting account). -
Kerberos (via GSSAPI or SPNEGO): A Kerberos session key can be obtained via
--decrypt-ccache(the connecting account's service ticket, which stores the session key), or derived via--decrypt-svc-keytab,--decrypt-svc-key,--decrypt-svc-password(which supply the target service's own credential to decrypt the ticket and recover the session key).--decrypt-saltoverrides the default AES salt (REALM+ ticket's SPN) when deriving service keys from--decrypt-svc-password.
Important
If using --decrypt-ccache, it must contain the exact service ticket the client is using. It works by trying each cached session key against the observed AP-REQ, so a client that obtains its own TGT and ST in-memory produces a different session key and cannot be decrypted - even though the account is the same. A ccache holding only a TGT doesn't help either: its session key belongs to the KDC exchange, not to the LDAP service, and using it to request a fresh ST would just yield a new session key that differs from the one in flight.
Note
Encryption type coverage. Protected GSSAPI/GSS-SPNEGO Kerberos traffic is decrypted for AES128/AES256-CTS-HMAC-SHA1-96 (RFC 4121 CFX wrap tokens) and for RC4-HMAC (the older, structurally different RFC 4757 wrap tokens) - between them, everything a current Active Directory DC actually negotiates. Any other encryption type is reported as unsupported and the connection is dropped, rather than silently relaying traffic ldapx cannot read.
- DIGEST-MD5:
auth-intandauth-conflevels are decrypted with--decrypt-password(plaintext password is required).
Important
This implementation is complex and experimental - it was validated against a Windows Server 2022 domain controller using the Macmod/msldap-probe testsuite. If you find any bugs please report an issue.
- TLS on the listener. Three new flags enable TLS directly in the listener:
--listener-cert/--listener-key: path to the server certificate and private key PEM files to enable the TLS listener.--listener-tls: use an in-memory self-signed certificate to enable the TLS listener.- When any TLS listener flag is set and
--listenhas no explicit port, the default port changes from 389 to 636.
- Upstream TLS client authentication over LDAPS. ldapx can now present a TLS client certificate to the upstream server by extracting it directly from the connecting client's TLS handshake:
--key: path to the private key PEM that pairs with the connecting client's TLS certificate. The certificate itself is taken automatically from the inboundtls.Conn.- Combined with
--ldaps+--listener-tls(or--listener-cert/--listener-key), this enables end-to-end TLS client authentication through the proxy (aka "Pass the Cert").
--spoof-mechs: rewrites/removes rootDSE'ssupportedSASLMechanismsattribute to try to steer clients to fallback to other mechanisms. Accepts comma-separated names, aliases (gssapi,spnego,external,digest-md5), ornoneto remove the attribute from the response entirely.--split-wrapped: a DC often packs several LDAP messages into one sealed buffer (aSearchResultEntrytogether with itsSearchResultDone, for instance), and by defaultldapxre-seals them the same way, mirroring the original sender's framing. This flag instead forwards each message as its own sealed frame:insplits client->target,outsplits target->client,bothsplits both directions.- New runtime parameters in the interactive shell.
spoof-mechs,split-wrappedandtrackingjoin the existing ones, so all three can be adjusted mid-session withset,clearandshowrather than only at startup - useful for turning spoofing or split framing on once a client is already connected. They take effect on the next message; a connection that has already completed its bind keeps the security layer it negotiated. - Coloring improvements:
[C->T]messages are now cyan,[C<-T]are now magenta. Also, previously warning messages were printed in the default foreground color, making them hard to distinguish visually. Normalized so that all use yellow. To disable colors users can now use-Z/--no-colors.
Fixed
- Connection deadlock (
closeDonesync.Once). Both direction goroutines could closedoneindependently, causing double-close panic. - Write-error propagation (
sendPacketsForward/sendPacketsReversereturningbool). Failures terminate connection instead of silent retry. - Paged results control value indexing with criticality BOOLEAN. The LDAP Control SEQUENCE
{ controlType, criticality?, controlValue }has an optional criticality BOOLEAN as child[1]; when present it shifts the control value from child[1] to child[2]. The paged-results cookie parser (ProcessSearchRequest) always read child[1] as the value, which when the criticality was set would decode the BOOLEAN's0xFF/0x00byte instead of the nested SEQUENCE, producing a "malformed" error and silently skipping paging tracking. The parser now checkscontrol.Children[1].Tagand selects child[2] when it's a BOOLEAN. - Integer-conversion range checks in
set verbfwd/set verbrev. go vetstring-conversion warning in filter-obfuscation middleware.
Changed
- Reorganized
package mainintodecrypt,rootdse,internal/app. - Bumped Go toolchain from 1.21.5 to 1.25.0.
Dependencies
github.com/oiweiwei/gokrb5.fork/v9(Kerberos/SPNEGO message parsing and cryptography)github.com/oiweiwei/go-msrpc(ssp/krb5/cryptofor RFC 4121 CFX confidentiality sealed GSS_Wrap, plus itsrfc4757/rfc1964primitives for the RC4-HMAC wrap tokens)