2024-01-21 17:57:23 +01:00
|
|
|
package ldap
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2024-01-21 18:14:20 +01:00
|
|
|
"strconv"
|
2024-01-21 17:57:23 +01:00
|
|
|
"strings"
|
2024-01-21 18:14:20 +01:00
|
|
|
"time"
|
2024-07-15 18:42:22 +05:30
|
|
|
|
|
|
|
|
"github.com/go-ldap/ldap/v3"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type (
|
|
|
|
|
// SearchResult contains search result of any / all ldap search request
|
|
|
|
|
// @example
|
|
|
|
|
// ```javascript
|
|
|
|
|
// const ldap = require('nuclei/ldap');
|
|
|
|
|
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
|
|
|
|
|
// const results = client.Search('(objectClass=*)', 'cn', 'mail');
|
|
|
|
|
// ```
|
|
|
|
|
SearchResult struct {
|
|
|
|
|
// Referrals contains list of referrals
|
|
|
|
|
Referrals []string `json:"referrals"`
|
|
|
|
|
// Controls contains list of controls
|
|
|
|
|
Controls []string `json:"controls"`
|
|
|
|
|
// Entries contains list of entries
|
|
|
|
|
Entries []LdapEntry `json:"entries"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LdapEntry represents a single LDAP entry
|
|
|
|
|
LdapEntry struct {
|
|
|
|
|
// DN contains distinguished name
|
|
|
|
|
DN string `json:"dn"`
|
|
|
|
|
// Attributes contains list of attributes
|
|
|
|
|
Attributes LdapAttributes `json:"attributes"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LdapAttributes represents all LDAP attributes of a particular
|
|
|
|
|
// ldap entry
|
|
|
|
|
LdapAttributes struct {
|
|
|
|
|
// CurrentTime contains current time
|
|
|
|
|
CurrentTime []string `json:"currentTime,omitempty"`
|
|
|
|
|
// SubschemaSubentry contains subschema subentry
|
|
|
|
|
SubschemaSubentry []string `json:"subschemaSubentry,omitempty"`
|
|
|
|
|
// DsServiceName contains ds service name
|
|
|
|
|
DsServiceName []string `json:"dsServiceName,omitempty"`
|
|
|
|
|
// NamingContexts contains naming contexts
|
|
|
|
|
NamingContexts []string `json:"namingContexts,omitempty"`
|
|
|
|
|
// DefaultNamingContext contains default naming context
|
|
|
|
|
DefaultNamingContext []string `json:"defaultNamingContext,omitempty"`
|
|
|
|
|
// SchemaNamingContext contains schema naming context
|
|
|
|
|
SchemaNamingContext []string `json:"schemaNamingContext,omitempty"`
|
|
|
|
|
// ConfigurationNamingContext contains configuration naming context
|
|
|
|
|
ConfigurationNamingContext []string `json:"configurationNamingContext,omitempty"`
|
|
|
|
|
// RootDomainNamingContext contains root domain naming context
|
|
|
|
|
RootDomainNamingContext []string `json:"rootDomainNamingContext,omitempty"`
|
|
|
|
|
// SupportedLDAPVersion contains supported LDAP version
|
|
|
|
|
SupportedLDAPVersion []string `json:"supportedLDAPVersion,omitempty"`
|
|
|
|
|
// HighestCommittedUSN contains highest committed USN
|
|
|
|
|
HighestCommittedUSN []string `json:"highestCommittedUSN,omitempty"`
|
|
|
|
|
// SupportedSASLMechanisms contains supported SASL mechanisms
|
|
|
|
|
SupportedSASLMechanisms []string `json:"supportedSASLMechanisms,omitempty"`
|
|
|
|
|
// DnsHostName contains DNS host name
|
|
|
|
|
DnsHostName []string `json:"dnsHostName,omitempty"`
|
|
|
|
|
// LdapServiceName contains LDAP service name
|
|
|
|
|
LdapServiceName []string `json:"ldapServiceName,omitempty"`
|
|
|
|
|
// ServerName contains server name
|
|
|
|
|
ServerName []string `json:"serverName,omitempty"`
|
|
|
|
|
// IsSynchronized contains is synchronized
|
|
|
|
|
IsSynchronized []string `json:"isSynchronized,omitempty"`
|
|
|
|
|
// IsGlobalCatalogReady contains is global catalog ready
|
|
|
|
|
IsGlobalCatalogReady []string `json:"isGlobalCatalogReady,omitempty"`
|
|
|
|
|
// DomainFunctionality contains domain functionality
|
|
|
|
|
DomainFunctionality []string `json:"domainFunctionality,omitempty"`
|
|
|
|
|
// ForestFunctionality contains forest functionality
|
|
|
|
|
ForestFunctionality []string `json:"forestFunctionality,omitempty"`
|
|
|
|
|
// DomainControllerFunctionality contains domain controller functionality
|
|
|
|
|
DomainControllerFunctionality []string `json:"domainControllerFunctionality,omitempty"`
|
|
|
|
|
// DistinguishedName contains the distinguished name
|
|
|
|
|
DistinguishedName []string `json:"distinguishedName,omitempty"`
|
|
|
|
|
// SAMAccountName contains the SAM account name
|
|
|
|
|
SAMAccountName []string `json:"sAMAccountName,omitempty"`
|
|
|
|
|
// PWDLastSet contains the password last set time
|
|
|
|
|
PWDLastSet []string `json:"pwdLastSet,omitempty"`
|
|
|
|
|
// LastLogon contains the last logon time
|
|
|
|
|
LastLogon []string `json:"lastLogon,omitempty"`
|
|
|
|
|
// MemberOf contains the groups the entry is a member of
|
|
|
|
|
MemberOf []string `json:"memberOf,omitempty"`
|
|
|
|
|
// ServicePrincipalName contains the service principal names
|
|
|
|
|
ServicePrincipalName []string `json:"servicePrincipalName,omitempty"`
|
|
|
|
|
// Extra contains other extra fields which might be present
|
|
|
|
|
Extra map[string]any `json:"extra,omitempty"`
|
|
|
|
|
}
|
2024-01-21 17:57:23 +01:00
|
|
|
)
|
|
|
|
|
|
2024-07-15 18:42:22 +05:30
|
|
|
// getSearchResult converts a ldap.SearchResult to a SearchResult
|
|
|
|
|
func getSearchResult(sr *ldap.SearchResult) *SearchResult {
|
|
|
|
|
t := &SearchResult{
|
|
|
|
|
Referrals: []string{},
|
|
|
|
|
Controls: []string{},
|
|
|
|
|
Entries: []LdapEntry{},
|
|
|
|
|
}
|
|
|
|
|
// add referrals
|
|
|
|
|
t.Referrals = append(t.Referrals, sr.Referrals...)
|
|
|
|
|
// add controls
|
|
|
|
|
for _, ctrl := range sr.Controls {
|
|
|
|
|
t.Controls = append(t.Controls, ctrl.String())
|
|
|
|
|
}
|
|
|
|
|
// add entries
|
|
|
|
|
for _, entry := range sr.Entries {
|
|
|
|
|
t.Entries = append(t.Entries, parseLdapEntry(entry))
|
|
|
|
|
}
|
|
|
|
|
return t
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseLdapEntry(entry *ldap.Entry) LdapEntry {
|
|
|
|
|
e := LdapEntry{
|
|
|
|
|
DN: entry.DN,
|
|
|
|
|
}
|
|
|
|
|
attrs := LdapAttributes{
|
|
|
|
|
Extra: make(map[string]any),
|
|
|
|
|
}
|
|
|
|
|
for _, attr := range entry.Attributes {
|
|
|
|
|
switch attr.Name {
|
|
|
|
|
case "currentTime":
|
|
|
|
|
attrs.CurrentTime = decodeTimestamps(attr.Values)
|
|
|
|
|
case "subschemaSubentry":
|
|
|
|
|
attrs.SubschemaSubentry = attr.Values
|
|
|
|
|
case "dsServiceName":
|
|
|
|
|
attrs.DsServiceName = attr.Values
|
|
|
|
|
case "namingContexts":
|
|
|
|
|
attrs.NamingContexts = attr.Values
|
|
|
|
|
case "defaultNamingContext":
|
|
|
|
|
attrs.DefaultNamingContext = attr.Values
|
|
|
|
|
case "schemaNamingContext":
|
|
|
|
|
attrs.SchemaNamingContext = attr.Values
|
|
|
|
|
case "configurationNamingContext":
|
|
|
|
|
attrs.ConfigurationNamingContext = attr.Values
|
|
|
|
|
case "rootDomainNamingContext":
|
|
|
|
|
attrs.RootDomainNamingContext = attr.Values
|
|
|
|
|
case "supportedLDAPVersion":
|
|
|
|
|
attrs.SupportedLDAPVersion = attr.Values
|
|
|
|
|
case "highestCommittedUSN":
|
|
|
|
|
attrs.HighestCommittedUSN = attr.Values
|
|
|
|
|
case "supportedSASLMechanisms":
|
|
|
|
|
attrs.SupportedSASLMechanisms = attr.Values
|
|
|
|
|
case "dnsHostName":
|
|
|
|
|
attrs.DnsHostName = attr.Values
|
|
|
|
|
case "ldapServiceName":
|
|
|
|
|
attrs.LdapServiceName = attr.Values
|
|
|
|
|
case "serverName":
|
|
|
|
|
attrs.ServerName = attr.Values
|
|
|
|
|
case "isSynchronized":
|
|
|
|
|
attrs.IsSynchronized = attr.Values
|
|
|
|
|
case "isGlobalCatalogReady":
|
|
|
|
|
attrs.IsGlobalCatalogReady = attr.Values
|
|
|
|
|
case "domainFunctionality":
|
|
|
|
|
attrs.DomainFunctionality = attr.Values
|
|
|
|
|
case "forestFunctionality":
|
|
|
|
|
attrs.ForestFunctionality = attr.Values
|
|
|
|
|
case "domainControllerFunctionality":
|
|
|
|
|
attrs.DomainControllerFunctionality = attr.Values
|
|
|
|
|
case "distinguishedName":
|
|
|
|
|
attrs.DistinguishedName = attr.Values
|
|
|
|
|
case "sAMAccountName":
|
|
|
|
|
attrs.SAMAccountName = attr.Values
|
|
|
|
|
case "pwdLastSet":
|
|
|
|
|
attrs.PWDLastSet = decodeTimestamps(attr.Values)
|
|
|
|
|
case "lastLogon":
|
|
|
|
|
attrs.LastLogon = decodeTimestamps(attr.Values)
|
|
|
|
|
case "memberOf":
|
|
|
|
|
attrs.MemberOf = attr.Values
|
|
|
|
|
case "servicePrincipalName":
|
|
|
|
|
attrs.ServicePrincipalName = attr.Values
|
|
|
|
|
default:
|
|
|
|
|
attrs.Extra[attr.Name] = attr.Values
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
e.Attributes = attrs
|
|
|
|
|
return e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// decodeTimestamps decodes multiple timestamps
|
|
|
|
|
func decodeTimestamps(timestamps []string) []string {
|
|
|
|
|
res := []string{}
|
|
|
|
|
for _, timestamp := range timestamps {
|
|
|
|
|
res = append(res, DecodeADTimestamp(timestamp))
|
|
|
|
|
}
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-06 04:02:53 +05:30
|
|
|
// DecodeSID decodes a SID string
|
2024-02-07 21:45:40 +05:30
|
|
|
// @example
|
|
|
|
|
// ```javascript
|
|
|
|
|
// const ldap = require('nuclei/ldap');
|
|
|
|
|
// const sid = ldap.DecodeSID('S-1-5-21-3623811015-3361044348-30300820-1013');
|
|
|
|
|
// log(sid);
|
|
|
|
|
// ```
|
2024-01-21 17:57:23 +01:00
|
|
|
func DecodeSID(s string) string {
|
|
|
|
|
b := []byte(s)
|
|
|
|
|
revisionLvl := int(b[0])
|
|
|
|
|
subAuthorityCount := int(b[1]) & 0xFF
|
|
|
|
|
|
|
|
|
|
var authority int
|
|
|
|
|
for i := 2; i <= 7; i++ {
|
|
|
|
|
authority = authority | int(b[i])<<(8*(5-(i-2)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var size = 4
|
|
|
|
|
var offset = 8
|
|
|
|
|
var subAuthorities []int
|
|
|
|
|
for i := 0; i < subAuthorityCount; i++ {
|
|
|
|
|
var subAuthority int
|
|
|
|
|
for k := 0; k < size; k++ {
|
|
|
|
|
subAuthority = subAuthority | (int(b[offset+k])&0xFF)<<(8*k)
|
|
|
|
|
}
|
|
|
|
|
subAuthorities = append(subAuthorities, subAuthority)
|
|
|
|
|
offset += size
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var builder strings.Builder
|
|
|
|
|
builder.WriteString("S-")
|
|
|
|
|
builder.WriteString(fmt.Sprintf("%d-", revisionLvl))
|
|
|
|
|
builder.WriteString(fmt.Sprintf("%d", authority))
|
|
|
|
|
for _, v := range subAuthorities {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("-%d", v))
|
|
|
|
|
}
|
|
|
|
|
return builder.String()
|
|
|
|
|
}
|
2024-01-21 18:14:20 +01:00
|
|
|
|
2024-02-06 04:02:53 +05:30
|
|
|
// DecodeADTimestamp decodes an Active Directory timestamp
|
2024-02-07 21:45:40 +05:30
|
|
|
// @example
|
|
|
|
|
// ```javascript
|
|
|
|
|
// const ldap = require('nuclei/ldap');
|
|
|
|
|
// const timestamp = ldap.DecodeADTimestamp('132036744000000000');
|
|
|
|
|
// log(timestamp);
|
|
|
|
|
// ```
|
2024-01-21 18:14:20 +01:00
|
|
|
func DecodeADTimestamp(timestamp string) string {
|
|
|
|
|
adtime, _ := strconv.ParseInt(timestamp, 10, 64)
|
|
|
|
|
if (adtime == 9223372036854775807) || (adtime == 0) {
|
|
|
|
|
return "Not Set"
|
|
|
|
|
}
|
|
|
|
|
unixtime_int64 := adtime/(10*1000*1000) - 11644473600
|
|
|
|
|
unixtime := time.Unix(unixtime_int64, 0)
|
|
|
|
|
return unixtime.Format("2006-01-02 3:4:5 pm")
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-06 04:02:53 +05:30
|
|
|
// DecodeZuluTimestamp decodes a Zulu timestamp
|
2024-02-07 21:45:40 +05:30
|
|
|
// @example
|
|
|
|
|
// ```javascript
|
|
|
|
|
// const ldap = require('nuclei/ldap');
|
|
|
|
|
// const timestamp = ldap.DecodeZuluTimestamp('2021-08-25T10:00:00Z');
|
|
|
|
|
// log(timestamp);
|
|
|
|
|
// ```
|
2024-01-21 18:14:20 +01:00
|
|
|
func DecodeZuluTimestamp(timestamp string) string {
|
|
|
|
|
zulu, err := time.Parse(time.RFC3339, timestamp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
return zulu.Format("2006-01-02 3:4:5 pm")
|
|
|
|
|
}
|