ldap

package
v0.783.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: AGPL-3.0 Imports: 3 Imported by: 0

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.

func Decode

func Decode(hexStr string) (*Result, error)

Decode parses an LDAP message from a hex string. Separators (':' '-' '_' whitespace) tolerated; '0x' prefix tolerated.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL