Documentation
¶
Overview ¶
Package ldap decodes Lightweight Directory Access Protocol v3 messages per RFC 4511 — the canonical directory-service protocol used by **every Active Directory deployment** + most enterprise IAM stacks (Microsoft AD LDS, OpenLDAP, 389 Directory Server, FreeIPA / IdM, Apple Open Directory, Apache Directory Server, Oracle Internet Directory, Novell eDirectory). LDAP runs over TCP/389 (cleartext), TCP/636 (LDAPS — implicit TLS), and UDP/389 (CLDAP — connectionless LDAP, used by Microsoft's NetLogon for site/DC discovery).
Operationally, LDAP is the **AD-pentest counterpart to kerberos_decode** — together they form the complete AD directory-attack dissector pair. The wire format leaks:
**Cleartext credentials via SimpleBind** — `BindRequest` carries authentication CHOICE; when the `[0] simple` option is used, the password is sent IN CLEARTEXT within the request body. This is the canonical credential-disclosure vector on TCP/389 (cleartext) — observing one BindRequest with simple authentication yields the user's password directly. AD allows simple-bind over cleartext by default unless `LDAPServerIntegrity` enforcement is turned on. The decoder surfaces `simple_bind_present` boolean + `bind_password_bytes` (the length, NOT the password itself — privacy-preserving while flagging the exposure).
**Username + DN enumeration via SearchRequest** — anonymous or authenticated SearchRequest enumerates the directory tree. `baseObject` reveals the search root (typically the AD domain — `DC=corp,DC=example, DC=com`); the response SearchResultEntry frames each leak an `objectName` (the DN — every user / computer / group account in the directory). Even unauthenticated `whoami`-style SearchRequest against rootDSE leaks the domain naming context + supported SASL mechanisms.
**Brute-force feedback via resultCode** — BindResponse `resultCode = 49` (`invalidCredentials`) is the canonical wrong-password response; `resultCode = 0` (`success`) confirms a working credential. Password-spray tools (kerbrute, ldapsearch loops) consume this directly.
**CLDAP NetLogon enumeration** — Microsoft's NetLogon uses CLDAP (connectionless LDAP) on UDP/389 for site / DC discovery; querying rootDSE via CLDAP leaks the domain controller's site + GUID + DnsHostName + flags without any authentication. Public attack tools (impacket nmap NSE `ldap-rootdse`) consume this.
**SASL mechanism enumeration** — anonymous query of rootDSE attribute `supportedSASLMechanisms` lists the mechanisms the server accepts (GSSAPI = Kerberos, GSS-SPNEGO = Kerberos via SPNEGO, DIGEST-MD5, CRAM-MD5, EXTERNAL, PLAIN). The decoder surfaces the authentication CHOICE selector so observers can detect `[3] sasl` BindRequest mechanism strings.
Wrap-vs-native judgement
Native. RFC 4511 is publicly available; LDAP v3 messages are ASN.1 BER-encoded SEQUENCEs with an outer messageID INTEGER and a context-tagged [APPLICATION N] protocolOp CHOICE. The DER walker is the same shape as internal/kerberos — short / long-form length, INTEGER, OCTET STRING, ENUMERATED, SEQUENCE traversal, context- tag discrimination. Filter parsing + per-mechanism SASL inner-decode + controls parsing are deliberately out of scope (each is its own nested grammar — filter is RFC 4515; SASL GSSAPI carries Kerberos AP-REQ which is handled by kerberos_decode).
What this package covers
**22-entry operation name table** (RFC 4511 §4.1.1): `[APPLICATION 0]` `0x60` BindRequest / `[APPLICATION 1]` `0x61` BindResponse / `[APPLICATION 2]` `0x42` UnbindRequest (PRIMITIVE) / `[APPLICATION 3]` `0x63` SearchRequest / `[APPLICATION 4]` `0x64` SearchResultEntry / `[APPLICATION 5]` `0x65` SearchResultDone / `[APPLICATION 6]` `0x66` ModifyRequest / `[APPLICATION 7]` `0x67` ModifyResponse / `[APPLICATION 8]` `0x68` AddRequest / `[APPLICATION 9]` `0x69` AddResponse / `[APPLICATION 10]` `0x4A` DelRequest (PRIMITIVE) / `[APPLICATION 11]` `0x6B` DelResponse / `[APPLICATION 12]` `0x6C` ModifyDNRequest / `[APPLICATION 13]` `0x6D` ModifyDNResponse / `[APPLICATION 14]` `0x6E` CompareRequest / `[APPLICATION 15]` `0x6F` CompareResponse / `[APPLICATION 16]` `0x50` AbandonRequest (PRIMITIVE) / `[APPLICATION 19]` `0x73` SearchResultReference / `[APPLICATION 23]` `0x77` ExtendedRequest / `[APPLICATION 24]` `0x78` ExtendedResponse / `[APPLICATION 25]` `0x79` IntermediateResponse.
**BindRequest body** (§4.2): SEQUENCE { version INTEGER (1..127) / name LDAPDN OCTET STRING (the username / UPN / DN — leakable!) / authentication AuthenticationChoice CHOICE { [0] simple OCTET STRING (cleartext password!) / [3] sasl SaslCredentials SEQUENCE { mechanism LDAPString / credentials OCTET STRING OPTIONAL } } }. Surfaces `bind_name` (the username) + `bind_auth_type` (simple / sasl) + `simple_bind_present` (the cleartext-creds classification!) + `bind_password_bytes` (length only — NOT the password) + `sasl_mechanism` (when SASL is used; e.g. "GSSAPI" indicates Kerberos integration).
**BindResponse body** (§4.2.2): SEQUENCE { resultCode ENUMERATED / matchedDN LDAPDN / diagnosticMessage LDAPString / referral [3] Referral OPTIONAL / serverSaslCreds [7] OCTET STRING OPTIONAL }. Surfaces `result_code` + `result_code_name` (49 = invalidCredentials = brute-force feedback signal!) + `matched_dn` + `diagnostic_message`.
**SearchRequest body** (§4.5.1): SEQUENCE { baseObject LDAPDN (the search root — e.g. `DC=corp,DC=example,DC=com`) / scope ENUMERATED { baseObject(0), singleLevel(1), wholeSubtree(2), subordinateSubtree(3) } / derefAliases ENUMERATED / sizeLimit INTEGER / timeLimit INTEGER / typesOnly BOOLEAN / filter Filter / attributes AttributeSelection }. Surfaces `search_base_object`
`search_scope` + `search_scope_name` + `search_size_limit` + `search_time_limit`.
**SearchResultEntry body** (§4.5.2): SEQUENCE { objectName LDAPDN (the matched entry's DN — every user / computer / group account leaked) / attributes PartialAttributeList }. Surfaces `entry_object_name` (the DN — the directory- enumeration leak!).
**SearchResultDone + ModifyResponse + AddResponse + DelResponse + ModifyDNResponse + CompareResponse + ExtendedResponse body**: all share the LDAPResult {resultCode / matchedDN / diagnosticMessage} shape; decoded uniformly via the same code path as BindResponse.
**17-entry resultCode name table** (RFC 4511 §4.1.9): 0 `success` / 1 `operationsError` / 2 `protocolError` / 4 `sizeLimitExceeded` / 7 `authMethodNotSupported` / 8 `strongerAuthRequired` / 10 `referral` / 11 `adminLimitExceeded` / 13 `confidentialityRequired` / 14 `saslBindInProgress` (multi-step SASL — keep reading) / 16 `noSuchAttribute` / 32 `noSuchObject` (canonical "DN doesn't exist") / 48 `inappropriateAuthentication` / 49 `invalidCredentials` (canonical wrong-password — brute-force feedback!) / 50 `insufficientAccessRights` / 51 `busy` / 53 `unwillingToPerform` (canonical "policy rejected").
**4-entry search scope name table** (§4.5.1): 0 `baseObject` (just this entry) / 1 `singleLevel` (immediate children) / 2 `wholeSubtree` (recursive — the canonical full-directory-dump scope) / 3 `subordinateSubtree` (children + descendants, excluding base).
What this package does NOT cover (deliberately out of scope)
- **Network framing** — feed LDAP bytes after the TCP- segment strip; default ports TCP/389 cleartext + TCP/636 LDAPS. For UDP CLDAP, strip the UDP datagram header.
- **LDAPS / StartTLS** — TCP/636 wraps LDAP in TLS; RFC 4513 §5.1 StartTLS upgrades a TCP/389 connection to TLS via the ExtendedRequest OID `1.3.6.1.4.1.1466. 20037`. Handle TLS strip first.
- **LDAP filter parser** — the `filter` field in SearchRequest is a Filter CHOICE per RFC 4511 §4.5.1
- RFC 4515 (string-form representation). The Filter ASN.1 tree (and/or/not/equalityMatch/substrings/ greaterOrEqual/lessOrEqual/present/approxMatch/ extensibleMatch) is its own nested grammar — out of scope here; surfaced as `filter_bytes` length only.
- **SASL mechanism inner-decode** — `[3] sasl SaslCredentials` SEQUENCE carries a mechanism string + opaque credentials. For GSSAPI, the credentials wrap a Kerberos AP-REQ — that's already handled by kerberos_decode. SCRAM-SHA-256 + DIGEST-MD5 + CRAM-MD5 inner decode are out of scope.
- **Controls parsing** — `[0] controls Controls OPTIONAL` at the end of LDAPMessage carries server- side extensions (paging, sort, deleted-objects, virtual-list-view, etc.). Surfaced as `controls_bytes` length only.
- **MS NetLogon / CLDAP rootDSE payload parsing** — CLDAP NetLogon Sample request returns a NETLOGON_SAM_LOGON_RESPONSE_EX struct in the attribute value; that struct's MS-NRPC binary layout is out of scope (surface as raw attribute bytes).
- **Schema parsing** — server-published schema objects (`subschemaSubentry`, `attributeTypes`, `objectClasses`) are surfaced as raw attribute values.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Result ¶
type Result struct {
TotalBytes int `json:"total_bytes"`
MessageID int `json:"message_id"`
ProtocolOp int `json:"protocol_op"`
ProtocolOpName string `json:"protocol_op_name"`
// BindRequest
BindVersion int `json:"bind_version,omitempty"`
BindName string `json:"bind_name,omitempty"`
BindAuthType string `json:"bind_auth_type,omitempty"`
SimpleBindPresent bool `json:"simple_bind_present"`
BindPasswordBytes int `json:"bind_password_bytes,omitempty"`
SASLMechanism string `json:"sasl_mechanism,omitempty"`
// LDAPResult-bearing responses (Bind/Search/Modify/
// Add/Del/ModifyDN/Compare/Extended)
ResultCode int `json:"result_code,omitempty"`
ResultCodeName string `json:"result_code_name,omitempty"`
MatchedDN string `json:"matched_dn,omitempty"`
DiagnosticMsg string `json:"diagnostic_message,omitempty"`
// SearchRequest
SearchBaseObject string `json:"search_base_object,omitempty"`
SearchScope int `json:"search_scope,omitempty"`
SearchScopeName string `json:"search_scope_name,omitempty"`
SearchSizeLimit int `json:"search_size_limit,omitempty"`
SearchTimeLimit int `json:"search_time_limit,omitempty"`
FilterBytes int `json:"filter_bytes,omitempty"`
// SearchResultEntry
EntryObjectName string `json:"entry_object_name,omitempty"`
}
Result is the structured decode of an LDAP message.