diff --git a/config.go b/config.go index ffcde0e..47d88fb 100644 --- a/config.go +++ b/config.go @@ -36,6 +36,8 @@ var baseDNMidFlags map[rune]string = map[rune]string{ 'X': "HexValue", 'S': "Spacing", 'Q': "DoubleQuotes", + 'U': "GUIDFormat", + 'I': "SIDFormat", } var filterMidFlags map[rune]string = map[rune]string{ @@ -60,6 +62,8 @@ var filterMidFlags map[rune]string = map[rune]string{ 's': "SubstringSplit", 'N': "NamesToANR", 'n': "ANRGarbageSubstring", + 'P': "DNAttributesNoise", + 'L': "TransitiveEval", } var attrListMidFlags map[rune]string = map[rune]string{ @@ -94,6 +98,8 @@ func SetupMiddlewaresMap() { "HexValue": basednmid.RandHexValueBaseDNObf(optFloat("BDNHexValueProb")), "Spacing": basednmid.RandSpacingBaseDNObf(optInt("BDNSpacingMaxElems")), "DoubleQuotes": basednmid.DoubleQuotesBaseDNObf(), + "GUIDFormat": basednmid.GUIDBaseDNObf(optStr("BDNGuid")), + "SIDFormat": basednmid.SIDBaseDNObf(optStr("BDNSid")), } filterMidMap = map[string]filtermid.FilterMiddleware{ @@ -118,6 +124,8 @@ func SetupMiddlewaresMap() { "SubstringSplit": filtermid.RandSubstringSplitFilterObf(optFloat("FiltSubstringSplitProb")), "NamesToANR": filtermid.ANRAttributeFilterObf(ANRSet), "ANRGarbageSubstring": filtermid.ANRSubstringGarbageFilterObf(optInt("FiltANRSubstringMaxElems"), optStr("FiltGarbageCharset")), + "DNAttributesNoise": filtermid.RandDNAttributesNoiseFilterObf(optFloat("FiltDNAttrNoiseProb")), + "TransitiveEval": filtermid.TransitiveEvalFilterObf(), } attrListMidMap = map[string]attrlistmid.AttrListMiddleware{ diff --git a/middlewares/basedn/obfuscation.go b/middlewares/basedn/obfuscation.go index 4b0dd77..5fe0bcd 100644 --- a/middlewares/basedn/obfuscation.go +++ b/middlewares/basedn/obfuscation.go @@ -125,6 +125,31 @@ func DoubleQuotesBaseDNObf() func(string) string { } } +// GUIDBaseDNObf replaces the BaseDN with the alternative form. +// Per MS-ADTS 3.1.1.3.1.2.4, AD supports where object_guid +// is the hex representation of the objectGUID attribute. This completely changes +// the BaseDN format, making it unrecognizable as a traditional DN. +func GUIDBaseDNObf(guidHex string) func(string) string { + return func(dn string) string { + if dn == "" || guidHex == "" { + return dn + } + return "" + } +} + +// SIDBaseDNObf replaces the BaseDN with the alternative form. +// Per MS-ADTS 3.1.1.3.1.2.4, AD supports where sid is either +// the string form (S-1-5-21-...) or hex representation of the binary SID. +func SIDBaseDNObf(sid string) func(string) string { + return func(dn string) string { + if dn == "" || sid == "" { + return dn + } + return "" + } +} + // RandHexValueBaseDNObf randomly hex encodes characters in BaseDN func RandHexValueBaseDNObf(prob float64) func(string) string { return func(dn string) string { diff --git a/middlewares/filter/obfuscation.go b/middlewares/filter/obfuscation.go index 3b9f31e..9c041e8 100755 --- a/middlewares/filter/obfuscation.go +++ b/middlewares/filter/obfuscation.go @@ -1279,3 +1279,50 @@ func ReplaceTautologiesFilterObf() func(parser.Filter) parser.Filter { return filter }) } + +/* + New middlewares based on MS-ADTS research +*/ + +// Known link attributes that support LDAP_MATCHING_RULE_TRANSITIVE_EVAL +var transitiveLinkAttrs = []string{ + "memberof", "member", "manager", "directreports", + "msds-membertransitive", "msds-memberoftransitive", +} + +// RandDNAttributesNoiseFilterObf randomly toggles the dnAttributes field +// on extensible match filters. Per MS-ADTS 3.1.1.3.1.3.1, AD always +// ignores this field and treats it as FALSE, so toggling it adds noise +// without affecting query results. +func RandDNAttributesNoiseFilterObf(prob float64) func(parser.Filter) parser.Filter { + return LeafApplierFilterMiddleware(func(filter parser.Filter) parser.Filter { + switch f := filter.(type) { + case *parser.FilterExtensibleMatch: + if rand.Float64() < prob { + f.DNAttributes = !f.DNAttributes + } + } + return filter + }) +} + +// TransitiveEvalFilterObf converts equality matches on link attributes +// to use LDAP_MATCHING_RULE_TRANSITIVE_EVAL (1.2.840.113556.1.4.1941). +// Per MS-ADTS 3.1.1.3.4.4.3, this rule performs recursive evaluation +// of link attributes. For direct membership queries it produces +// equivalent results while changing the query syntax. +func TransitiveEvalFilterObf() func(parser.Filter) parser.Filter { + return LeafApplierFilterMiddleware(func(filter parser.Filter) parser.Filter { + switch f := filter.(type) { + case *parser.FilterEqualityMatch: + if slices.Contains(transitiveLinkAttrs, strings.ToLower(f.AttributeDesc)) { + return &parser.FilterExtensibleMatch{ + MatchingRule: "1.2.840.113556.1.4.1941", + AttributeDesc: f.AttributeDesc, + MatchValue: f.AssertionValue, + } + } + } + return filter + }) +} diff --git a/middlewares/options.go b/middlewares/options.go index 5fa0e27..ff8afbf 100644 --- a/middlewares/options.go +++ b/middlewares/options.go @@ -6,6 +6,8 @@ var DefaultOptions = map[string]string{ "BDNOIDAttributeMaxSpaces": "4", "BDNOIDAttributeMaxZeros": "4", "BDNOIDAttributeIncludePrefix": "true", + "BDNGuid": "", + "BDNSid": "", "FiltSpacingMaxSpaces": "4", "FiltTimestampGarbageUseComma": "false", @@ -25,6 +27,7 @@ var DefaultOptions = map[string]string{ "FiltOIDAttributeMaxSpaces": "4", "FiltOIDAttributeMaxZeros": "4", "FiltOIDAttributeIncludePrefix": "true", + "FiltDNAttrNoiseProb": "0.5", "AttrsDuplicateProb": "0.7", "AttrsCaseProb": "0.7",