Skip to content

v1.3.0

Latest

Choose a tag to compare

@Macmod Macmod released this 27 Jul 02:06
42ba26e

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-salt overrides 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-int and auth-conf levels 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 --listen has 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 inbound tls.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's supportedSASLMechanisms attribute to try to steer clients to fallback to other mechanisms. Accepts comma-separated names, aliases (gssapi, spnego, external, digest-md5), or none to remove the attribute from the response entirely.
  • --split-wrapped: a DC often packs several LDAP messages into one sealed buffer (a SearchResultEntry together with its SearchResultDone, for instance), and by default ldapx re-seals them the same way, mirroring the original sender's framing. This flag instead forwards each message as its own sealed frame: in splits client->target, out splits target->client, both splits both directions.
  • New runtime parameters in the interactive shell. spoof-mechs, split-wrapped and tracking join the existing ones, so all three can be adjusted mid-session with set, clear and show rather 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 (closeDone sync.Once). Both direction goroutines could close done independently, causing double-close panic.
  • Write-error propagation (sendPacketsForward/sendPacketsReverse returning bool). 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's 0xFF/0x00 byte instead of the nested SEQUENCE, producing a "malformed" error and silently skipping paging tracking. The parser now checks control.Children[1].Tag and selects child[2] when it's a BOOLEAN.
  • Integer-conversion range checks in set verbfwd/set verbrev.
  • go vet string-conversion warning in filter-obfuscation middleware.

Changed

  • Reorganized package main into decrypt, 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/crypto for RFC 4121 CFX confidentiality sealed GSS_Wrap, plus its rfc4757/rfc1964 primitives for the RC4-HMAC wrap tokens)