├── soaphound-logo.png
├── FodyWeavers.xml
├── Enums
├── Labels.cs
├── UAC.cs
├── DomainTrusts.cs
├── EdgeNames.cs
├── LDAPProperties.cs
├── WellKnownPrincipal.cs
└── PKI.cs
├── OutputTypes
├── TypedPrincipal.cs
├── GPOs.cs
├── CANodes.cs
└── Node.cs
├── Properties
├── DataSources
│ └── System.ServiceModel.Channels.Message.datasource
└── AssemblyInfo.cs
├── Processors
├── PKICache.cs
├── ACEGuids.cs
├── GPOProcessor.cs
├── ContainerProcessor.cs
├── DomainTrustProcessor.cs
├── GroupProcessor.cs
├── OUProcessor.cs
├── DomainProcessor.cs
├── UserProcessor.cs
├── ComputerProcessor.cs
├── CAProcessor.cs
├── Cache.cs
└── ACLProcessor.cs
├── SOAPHound.sln
├── Options.cs
├── ADWS
├── ADObject.cs
└── ADWSConnector.cs
├── packages.config
├── .gitignore
├── README.md
├── ADWSUtils.cs
├── SOAPHound.csproj
└── hDNSRecord.cs
/soaphound-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FalconForceTeam/SOAPHound/HEAD/soaphound-logo.png
--------------------------------------------------------------------------------
/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Enums/Labels.cs:
--------------------------------------------------------------------------------
1 | namespace SOAPHound.Enums
2 | {
3 | public enum Label
4 | {
5 | User,
6 | Computer,
7 | Group,
8 | GPO,
9 | Domain,
10 | OU,
11 | Container,
12 | Base,
13 | CA
14 | }
15 | }
--------------------------------------------------------------------------------
/OutputTypes/TypedPrincipal.cs:
--------------------------------------------------------------------------------
1 | using SOAPHound.Enums;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Converters;
4 |
5 | namespace SOAPHound.OutputTypes
6 | {
7 | public class TypedPrincipal
8 | {
9 | public TypedPrincipal()
10 | {
11 | }
12 |
13 | public TypedPrincipal(string objectIdentifier, Label type)
14 | {
15 | ObjectIdentifier = objectIdentifier;
16 | ObjectType = type;
17 | }
18 |
19 | public string ObjectIdentifier { get; set; }
20 | [JsonConverter(typeof(StringEnumConverter))]
21 | public Label ObjectType { get; set; }
22 |
23 | }
24 | }
--------------------------------------------------------------------------------
/Properties/DataSources/System.ServiceModel.Channels.Message.datasource:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 | System.ServiceModel.Channels.Message, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
10 |
--------------------------------------------------------------------------------
/Enums/UAC.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SOAPHound.Enums
4 | {
5 | [Flags]
6 | public enum UacFlags
7 | {
8 | Script = 0x1,
9 | AccountDisable = 0x2,
10 | HomeDirRequired = 0x8,
11 | Lockout = 0x10,
12 | PasswordNotRequired = 0x20,
13 | PasswordCantChange = 0x40,
14 | EncryptedTextPwdAllowed = 0x80,
15 | TempDuplicateAccount = 0x100,
16 | NormalAccount = 0x200,
17 | InterdomainTrustAccount = 0x800,
18 | WorkstationTrustAccount = 0x1000,
19 | ServerTrustAccount = 0x2000,
20 | DontExpirePassword = 0x10000,
21 | MnsLogonAccount = 0x20000,
22 | SmartcardRequired = 0x40000,
23 | TrustedForDelegation = 0x80000,
24 | NotDelegated = 0x100000,
25 | UseDesKeyOnly = 0x200000,
26 | DontReqPreauth = 0x400000,
27 | PasswordExpired = 0x800000,
28 | TrustedToAuthForDelegation = 0x1000000,
29 | PartialSecretsAccount = 0x04000000
30 | }
31 | }
--------------------------------------------------------------------------------
/Enums/DomainTrusts.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Converters;
3 | using System;
4 |
5 | namespace SOAPHound.Enums
6 | {
7 | [JsonConverter(typeof(StringEnumConverter))]
8 | public enum TrustDirection
9 | {
10 | Disabled = 0,
11 | Inbound = 1,
12 | Outbound = 2,
13 | Bidirectional = 3
14 | }
15 | [JsonConverter(typeof(StringEnumConverter))]
16 | public enum TrustType
17 | {
18 | ParentChild = 0,
19 | CrossLink = 1,
20 | Forest = 2,
21 | External = 3,
22 | Unknown = 4
23 | }
24 |
25 | [Flags]
26 | public enum TrustAttributes
27 | {
28 | NonTransitive = 0x1,
29 | UplevelOnly = 0x2,
30 | FilterSids = 0x4,
31 | ForestTransitive = 0x8,
32 | CrossOrganization = 0x10,
33 | WithinForest = 0x20,
34 | TreatAsExternal = 0x40,
35 | TrustUsesRc4 = 0x80,
36 | TrustUsesAes = 0x100,
37 | CrossOrganizationNoTGTDelegation = 0x200,
38 | PIMTrust = 0x400
39 | }
40 | }
--------------------------------------------------------------------------------
/Processors/PKICache.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System.Collections.Generic;
3 | using System.Runtime.Serialization;
4 | using SOAPHound.Enums;
5 | using SOAPHound.OutputTypes;
6 |
7 | namespace SOAPHound.Processors
8 | {
9 |
10 | public static class PKICache
11 | {
12 | static PKICache()
13 | {
14 |
15 | TemplateToCACache = new Dictionary>();
16 | }
17 |
18 | private static Dictionary> TemplateToCACache { get; set; }
19 |
20 | internal static void AddTemplateCA(string template, string CA)
21 | {
22 | if (!TemplateToCACache.ContainsKey(template))
23 | TemplateToCACache.Add(template, new List());
24 | TemplateToCACache[template].Add(CA);
25 |
26 | }
27 |
28 | internal static List GetTemplateCA(string template)
29 | {
30 | if (TemplateToCACache.ContainsKey(template))
31 | return TemplateToCACache[template];
32 | else
33 | return new List();
34 | }
35 |
36 |
37 |
38 |
39 | }
40 | }
--------------------------------------------------------------------------------
/SOAPHound.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.33214.272
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SOAPHound", "SOAPHound.csproj", "{33571B09-4E94-43CB-ABDC-0226D769E701}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {33571B09-4E94-43CB-ABDC-0226D769E701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {33571B09-4E94-43CB-ABDC-0226D769E701}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {33571B09-4E94-43CB-ABDC-0226D769E701}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {33571B09-4E94-43CB-ABDC-0226D769E701}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {711D8081-AF17-42E7-9A05-10773D84D870}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/Processors/ACEGuids.cs:
--------------------------------------------------------------------------------
1 | namespace SOAPHound.Processors
2 | {
3 | public class ACEGuids
4 | {
5 | public const string DSReplicationGetChanges = "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2";
6 | public const string DSReplicationGetChangesAll = "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2";
7 | public const string DSReplicationGetChangesInFilteredSet = "89e95b76-444d-4c62-991a-0facbeda640c";
8 | public const string UserForceChangePassword = "00299570-246d-11d0-a768-00aa006e0529";
9 | public const string AllGuid = "00000000-0000-0000-0000-000000000000";
10 | public const string WriteMember = "bf9679c0-0de6-11d0-a285-00aa003049e2";
11 | public const string WriteAllowedToAct = "3f78c3e5-f79a-46bd-a0b8-9d18116ddc79";
12 | public const string WriteSPN = "f3a64788-5306-11d1-a9c5-0000f80367c1";
13 | public const string AddKeyPrincipal = "5b47d60f-6090-40b2-9f37-2a4de88f3063";
14 | public const string UserAccountRestrictions = "4c164200-20c0-11d0-a768-00aa006e0529";
15 | public const string Enrollment = "0e10c968-78fb-11d2-90d4-00c04f79dc55";
16 | public const string AutoEnrollment = "a05b8cc2-17bc-4802-a710-e7c15ab866a2";
17 | }
18 | }
--------------------------------------------------------------------------------
/OutputTypes/GPOs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace SOAPHound.OutputTypes
8 | {
9 | public class GPLink
10 | {
11 | private string _guid;
12 |
13 | public bool IsEnforced { get; set; }
14 |
15 | public string GUID
16 | {
17 | get => _guid;
18 | set => _guid = value?.ToUpper();
19 | }
20 | }
21 |
22 | public class ResultingGPOChanges
23 | {
24 | public TypedPrincipal[] LocalAdmins { get; set; } = Array.Empty();
25 | public TypedPrincipal[] RemoteDesktopUsers { get; set; } = Array.Empty();
26 | public TypedPrincipal[] DcomUsers { get; set; } = Array.Empty();
27 | public TypedPrincipal[] PSRemoteUsers { get; set; } = Array.Empty();
28 | public TypedPrincipal[] AffectedComputers { get; set; } = Array.Empty();
29 | }
30 |
31 | public class APIResult
32 | {
33 | public Boolean Collected { get; set; } = false;
34 | public string FailureReason { get; set; } = null;
35 | public string[] Results { get; set; } = Array.Empty();
36 |
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("SOAPHound")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SOAPHound")]
13 | [assembly: AssemblyCopyright("Copyright © 2023")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("33571b09-4e94-43cb-abdc-0226d769e701")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Enums/EdgeNames.cs:
--------------------------------------------------------------------------------
1 | namespace SOAPHound.Enums
2 | {
3 | public static class EdgeNames
4 | {
5 | public const string GenericWrite = "GenericWrite";
6 | public const string Owns = "Owns";
7 | public const string GenericAll = "GenericAll";
8 | public const string WriteDacl = "WriteDacl";
9 | public const string WriteOwner = "WriteOwner";
10 | public const string AddSelf = "AddSelf";
11 | public const string GetChanges = "GetChanges";
12 | public const string GetChangesAll = "GetChangesAll";
13 | public const string GetChangesInFilteredSet = "GetChangesInFilteredSet";
14 | public const string AllExtendedRights = "AllExtendedRights";
15 | public const string ForceChangePassword = "ForceChangePassword";
16 | public const string AddAllowedToAct = "AddAllowedToAct";
17 | public const string ReadLAPSPassword = "ReadLAPSPassword";
18 | public const string ReadGMSAPassword = "ReadGMSAPassword";
19 | public const string AddMember = "AddMember";
20 | public const string WriteSPN = "WriteSPN";
21 | public const string AddKeyCredentialLink = "AddKeyCredentialLink";
22 | public const string SQLAdmin = "SQLAdmin";
23 | public const string WriteAccountRestrictions = "WriteAccountRestrictions";
24 | public const string Enroll = "Enroll";
25 | public const string AutoEnroll = "AutoEnroll";
26 | public const string ManageCA = "ManageCA";
27 | public const string ManageCertificates = "ManageCertificates";
28 | public const string Auditor = "Auditor";
29 | public const string Operator = "Operator";
30 | }
31 | }
--------------------------------------------------------------------------------
/Processors/GPOProcessor.cs:
--------------------------------------------------------------------------------
1 | using SOAPHound.ADWS;
2 | using SOAPHound.Enums;
3 | using SOAPHound.OutputTypes;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.DirectoryServices;
7 | using System.Linq;
8 | using System.Security.Principal;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace SOAPHound.Processors
13 | {
14 | class GPOProcessor
15 | {
16 | public GPONode parseGPOObject(ADObject adobject, string domainName)
17 | {
18 | DateTime EpochDiff = new DateTime(1970, 1, 1);
19 |
20 | Label objectType = Label.GPO;
21 | GPONode adnode = new GPONode()
22 | {
23 |
24 | ObjectIdentifier = adobject.ObjectGUID.ToString().ToUpper(),
25 | Properties = new GPOProperties()
26 | {
27 | name = adobject.DisplayName.ToUpper() + "@" + domainName.ToUpper(),
28 | domainsid = "null",
29 | domain = domainName.ToUpper(),
30 | distinguishedname = adobject.DistinguishedName.ToUpper(),
31 | whencreated = (long)adobject.WhenCreated.Subtract(EpochDiff).TotalSeconds,
32 | description = adobject.Description,
33 | gpcpath = adobject.GPCFileSysPath.ToUpper(),
34 | },
35 | Aces = ACLProcessor.parseAces(adobject.NTSecurityDescriptor, objectType, false),
36 | IsDeleted = (adobject.IsDeleted == null) ? false : true,
37 | IsACLProtected = (adobject.NTSecurityDescriptor.AreAccessRulesProtected || adobject.NTSecurityDescriptor.AreAuditRulesProtected) ? true : false,
38 | };
39 |
40 | return adnode;
41 |
42 | }
43 |
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Processors/ContainerProcessor.cs:
--------------------------------------------------------------------------------
1 | using SOAPHound.ADWS;
2 | using SOAPHound.Enums;
3 | using SOAPHound.OutputTypes;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Diagnostics;
7 | using System.DirectoryServices;
8 | using System.Linq;
9 | using System.Security.Principal;
10 | using System.Text;
11 | using System.Threading.Tasks;
12 |
13 | namespace SOAPHound.Processors
14 | {
15 | class ContainerProcessor
16 | {
17 | public ContainerNode parseContainerObject(ADObject adobject, string domainName)
18 | {
19 |
20 | Label objectType = Label.Container;
21 |
22 | ContainerNode adnode = new ContainerNode()
23 | {
24 |
25 | ObjectIdentifier = adobject.ObjectGUID.ToString().ToUpper(),
26 | Properties = new ContainerProperties()
27 | {
28 | name = adobject.Name.ToUpper() + "@" + domainName.ToUpper(),
29 | domainsid = null,
30 | domain = domainName.ToUpper(),
31 | distinguishedname = adobject.DistinguishedName.ToUpper(),
32 | },
33 | ChildObjects = parseChildObjects(adobject.DistinguishedName),
34 | Aces = ACLProcessor.parseAces(adobject.NTSecurityDescriptor, objectType, false),
35 | IsDeleted = (adobject.IsDeleted == null) ? false : true,
36 | IsACLProtected = (adobject.NTSecurityDescriptor.AreAccessRulesProtected|| adobject.NTSecurityDescriptor.AreAuditRulesProtected) ? true : false,
37 | };
38 |
39 | return adnode;
40 |
41 | }
42 |
43 | private TypedPrincipal[] parseChildObjects(string dn)
44 | {
45 | Cache.GetChildObjects(dn, out TypedPrincipal[] childObjects);
46 | return childObjects;
47 | }
48 |
49 |
50 |
51 |
52 |
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Processors/DomainTrustProcessor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.DirectoryServices.Protocols;
5 | using System.Security.Principal;
6 | using SOAPHound.ADWS;
7 | using SOAPHound.Enums;
8 | using SOAPHound.OutputTypes;
9 |
10 | namespace SOAPHound.Processors
11 | {
12 | public class DomainTrustProcessor
13 | {
14 | public DomainTrust ProcessDomainTrusts(ADObject adobject)
15 | {
16 |
17 | var trust = new DomainTrust();
18 | if (adobject.SecurityIdentifier != null)
19 | {
20 | trust.TargetDomainSid = adobject.SecurityIdentifier.ToString();
21 | trust.TrustDirection = Convert.ToInt32((TrustDirection)adobject.TrustDirection);
22 | TrustAttributes attributes = (TrustAttributes)adobject.TrustAttributes;
23 | trust.IsTransitive = (attributes & TrustAttributes.NonTransitive) == 0;
24 | trust.TargetDomainName = adobject.Name.ToUpper();
25 | trust.SidFilteringEnabled = (attributes & TrustAttributes.FilterSids) != 0;
26 | trust.TrustType = Convert.ToInt32((TrustAttributesToType(attributes)));
27 | }
28 | return trust;
29 | }
30 |
31 | public static TrustType TrustAttributesToType(TrustAttributes attributes)
32 | {
33 | TrustType trustType;
34 |
35 | if ((attributes & TrustAttributes.WithinForest) != 0)
36 | trustType = TrustType.ParentChild;
37 | else if ((attributes & TrustAttributes.ForestTransitive) != 0)
38 | trustType = TrustType.Forest;
39 | else if ((attributes & TrustAttributes.TreatAsExternal) != 0 ||
40 | (attributes & TrustAttributes.CrossOrganization) != 0)
41 | trustType = TrustType.External;
42 | else
43 | trustType = TrustType.Unknown;
44 |
45 | return trustType;
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/Options.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using CommandLine;
7 | using CommandLine.Text;
8 |
9 | namespace SOAPHound
10 | {
11 | public class Options
12 | {
13 | //Connection Options
14 | [Option(HelpText = "Username to use for ADWS Connection. Format: domain\\user or user@domain", Default = null)]
15 | public string User { get; set; }
16 |
17 | [Option(HelpText = "Password to use for ADWS Connection", Default = null)]
18 | public string Password { get; set; }
19 |
20 | [Option(HelpText = "Specify domain for enumeration", Default = null)]
21 | public string Domain { get; set; }
22 |
23 | [Option(HelpText = "Domain Controller to connect to", Default = null)]
24 | public string DC { get; set; }
25 |
26 | //Supported modes
27 | [Option(HelpText = "Only build cache and not perform further actions", Group = "Mode", Default = false)]
28 | public bool BuildCache { get; set; }
29 |
30 | [Option(HelpText = "Show stats of local cache file", Group = "Mode", Default = null)]
31 | public bool ShowStats { get; set; }
32 |
33 | [Option(HelpText = "Dump AD Integrated DNS data", Group = "Mode", Default = false)]
34 | public bool DNSDump { get; set; }
35 |
36 | [Option(HelpText = "Dump AD Certificate Services data", Group = "Mode", Default = false)]
37 | public bool CertDump { get; set; }
38 |
39 | [Option(HelpText = "Dump BH data", Group = "Mode", Default = false)]
40 | public bool BHDump { get; set; }
41 |
42 | //Functional Options
43 | [Option('a',"autosplit",HelpText = "Enable AutoSplit mode: automatically split object retrieval on two depth levels based on defined trheshold", Default = false)]
44 | public bool AutoSplit { get; set; }
45 | [Option('t', "threshold", HelpText = "AutoSplit mode: Define split threshold based on number of objects per starting letter", Default = 0)]
46 | public int Threshold { get; set; }
47 | [Option(HelpText = "Do not request LAPS related information", Default = false)]
48 | public bool NoLAPS { get; set; }
49 | //Output Options
50 | [Option('o',"outputdirectory",HelpText = "Folder to output files to (full path needed)", Default = null)]
51 | public string OutputDirectory { get; set; }
52 | [Option('c',"cachefilename", HelpText = "Filename for the cache file (full path needed)", Default = null)]
53 | public string CacheFileName { get; set; }
54 | [Option(HelpText = "Create log file", Default = null )]
55 | public string LogFile { get; set; }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Enums/LDAPProperties.cs:
--------------------------------------------------------------------------------
1 | namespace SOAPHound.Enums
2 | {
3 | public class LDAPProperties
4 | {
5 | public const string GroupMSAMembership = "msds-groupmsamembership";
6 | public const string UserAccountControl = "useraccountcontrol";
7 | public const string IsDeleted = "isdeleted";
8 | public const string SAMAccountType = "samaccounttype";
9 | public const string SAMAccountName = "samaccountname";
10 | public const string ObjectClass = "objectclass";
11 | public const string Name = "name";
12 | public const string SchemaIDGUID = "schemaidguid";
13 | public const string GPLink = "gplink";
14 | public const string GPOptions = "gpoptions";
15 | public const string TrustDirection = "trustdirection";
16 | public const string TrustAttributes = "trustattributes";
17 | public const string CanonicalName = "cn";
18 | public const string GPCFileSYSPath = "gpcfilesyspath";
19 | public const string Description = "description";
20 | public const string WhenCreated = "whencreated";
21 | public const string DomainFunctionalLevel = "msds-behavior-version";
22 | public const string AdminCount = "admincount";
23 | public const string AllowedToDelegateTo = "msds-allowedtodelegateto";
24 | public const string LastLogon = "lastlogon";
25 | public const string LastLogonTimestamp = "lastlogontimestamp";
26 | public const string PasswordLastSet = "pwdlastset";
27 | public const string ServicePrincipalNames = "serviceprincipalname";
28 | public const string DisplayName = "displayname";
29 | public const string Email = "mail";
30 | public const string Title = "title";
31 | public const string HomeDirectory = "homedirectory";
32 | public const string UserPassword = "userpassword";
33 | public const string SIDHistory = "sidhistory";
34 | public const string AllowedToActOnBehalfOfOtherIdentity = "msds-allowedtoactonbehalfofotheridentity";
35 | public const string OperatingSystem = "operatingsystem";
36 | public const string ServicePack = "operatingsystemservicepack";
37 | public const string DNSHostName = "dnshostname";
38 | public const string LAPSExpirationTime = "ms-mcs-admpwdexpirationtime";
39 | public const string Members = "member";
40 | public const string SecurityDescriptor = "ntsecuritydescriptor";
41 | public const string SecurityIdentifier = "securityidentifier";
42 | public const string ObjectSID = "objectsid";
43 | public const string ObjectGUID = "objectguid";
44 | public const string PrimaryGroupID = "primarygroupid";
45 | public const string GroupPolicyOptions = "gpoptions";
46 | public const string UnixUserPassword = "unixuserpassword";
47 | public const string UnicodePassword = "unicodepwd";
48 | public const string MsSFU30Password = "msSFU30Password";
49 | public const string ScriptPath = "scriptpath";
50 | }
51 | }
--------------------------------------------------------------------------------
/ADWS/ADObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.DirectoryServices;
4 | using System.Globalization;
5 | using System.Linq;
6 | using System.Net.NetworkInformation;
7 | using System.Security.Cryptography.X509Certificates;
8 | using System.Security.Principal;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace SOAPHound.ADWS
13 | {
14 | public class ADObject
15 | {
16 | public string Class { get; set; }
17 | public int AdminCount { get; set; }
18 | public X509Certificate2Collection CACertificate { get; set; }
19 | public string[] CertificateTemplates { get; set; }
20 | public string Description { get; set; }
21 | public string DisplayName { get; set; }
22 | public string DistinguishedName { get; set; }
23 | public string DNSHostName { get; set; }
24 | public string Cn { get; set; }
25 | public byte[] DnsRecord { get; set; }
26 | public int DSMachineAccountQuota { get; set; }
27 | public string GPCFileSysPath { get; set; }
28 | public string IsDeleted { get; set; }
29 | public string GPLink { get; set; }
30 | public int GPOptions { get; set; }
31 | public DateTime LastLogon { get; set; }
32 | public DateTime LastLogonTimestamp { get; set; }
33 | public string[] Member { get; set; }
34 | public ActiveDirectorySecurity MsDSAllowedToActOnBehalfOfOtherIdentity { get; set; }
35 | public string[] MsDSAllowedToDelegateTo { get; set; }
36 | public int FunctionalLevel { get; set; }
37 | public long MsMCSAdmPwdExpirationTime { get; set; }
38 | public int MsPKICertificateNameFlag { get; set; }
39 | public int MsPKIMinimalKeySize { get; set; }
40 | public int MsPKIEnrollmentFlag { get; set; }
41 | public int MsPKIPrivateKeyFlag { get; set; }
42 | public string Name { get; set; }
43 | public ActiveDirectorySecurity NTSecurityDescriptor { get; set; }
44 | public Guid ObjectGUID { get; set; }
45 | public SecurityIdentifier ObjectSid { get; set; }
46 | public string OperatingSystem { get; set; }
47 | public string[] PKIExtendedKeyUsage { get; set; }
48 | public int PrimaryGroupID { get; set; }
49 | public DateTime PwdLastSet { get; set; }
50 | public string SAMAccountName { get; set; }
51 | public string ScriptPath { get; set; }
52 | public SecurityIdentifier SecurityIdentifier { get; set; }
53 | public string[] ServicePrincipalName { get; set; }
54 | public SecurityIdentifier[] SIDHistory { get; set; }
55 | public int TrustAttributes { get; set; }
56 | public int TrustDirection { get; set; }
57 | public int UserAccountControl { get; set; }
58 | public DateTime WhenCreated { get; set; }
59 | public string Email { get; set; }
60 | public string Title { get; set; }
61 | public string HomeDirectory { get; set; }
62 | public string UserPassword { get; set; }
63 | public string UnixUserPassword { get; set; }
64 | public string UnicodePassword { get; set; }
65 | public string MsSFU30Password { get; set; }
66 | public byte[] PKIExpirationPeriod { get; set; }
67 | public byte[] PKIOverlapPeriod { get; set; }
68 | public ADObject()
69 | {
70 | }
71 | }
72 |
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/Processors/GroupProcessor.cs:
--------------------------------------------------------------------------------
1 | using SOAPHound.ADWS;
2 | using SOAPHound.Enums;
3 | using SOAPHound.OutputTypes;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.DirectoryServices;
7 | using System.Linq;
8 | using System.Security.Principal;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace SOAPHound.Processors
13 | {
14 | class GroupProcessor
15 | {
16 | public GroupNode parseGroupObject(ADObject adobject, string domainName)
17 | {
18 | DateTime EpochDiff = new DateTime(1970, 1, 1);
19 |
20 | // Extract domainsid from User SID
21 | string domainsid = adobject.ObjectSid.ToString().Substring(0, adobject.ObjectSid.ToString().LastIndexOf('-'));
22 |
23 | Label objectType = ADWSUtils.ResolveIDAndType(adobject.ObjectSid.ToString()).ObjectType;
24 |
25 | GroupNode adnode = new GroupNode()
26 | {
27 |
28 | ObjectIdentifier = adobject.ObjectSid.ToString(),
29 | Properties = new GroupProperties()
30 | {
31 | name = adobject.SAMAccountName.ToUpper() + "@" + domainName.ToUpper(),
32 | samaccountname = adobject.SAMAccountName,
33 | domainsid = domainsid,
34 | domain = domainName.ToUpper(),
35 | distinguishedname = adobject.DistinguishedName,
36 | whencreated = (long)adobject.WhenCreated.Subtract(EpochDiff).TotalSeconds,
37 | description = adobject.Description,
38 | admincount = (adobject.AdminCount > 0),
39 | highvalue = IsHighValueGroup(adobject.ObjectSid.ToString()),
40 | },
41 | Members = parseMembers(adobject.Member),
42 | Aces = ACLProcessor.parseAces(adobject.NTSecurityDescriptor, objectType, false),
43 | IsDeleted = (adobject.IsDeleted == null) ? false : true,
44 | IsACLProtected = (adobject.NTSecurityDescriptor.AreAccessRulesProtected || adobject.NTSecurityDescriptor.AreAuditRulesProtected) ? true : false,
45 | };
46 |
47 | return adnode;
48 |
49 | }
50 |
51 | private TypedPrincipal[] parseMembers(string[] members)
52 | {
53 | TypedPrincipal[] Members = new TypedPrincipal[] { };
54 | if (members != null)
55 | {
56 | foreach (string member in members)
57 | {
58 | TypedPrincipal Member = ADWSUtils.ResolveDistinguishedName(member);
59 | Members = Members.Append(Member).ToArray();
60 | }
61 | }
62 | return Members;
63 | }
64 |
65 | private bool IsHighValueGroup(string objectId)
66 | {
67 | var suffixes = new string[]
68 | {
69 | "-512",
70 | "-516",
71 | "-519",
72 | "S-1-5-32-544",
73 | "S-1-5-32-548",
74 | "S-1-5-32-549",
75 | "S-1-5-32-550",
76 | "S-1-5-32-551",
77 | };
78 | foreach (var suffix in suffixes)
79 | {
80 | if (objectId.EndsWith(suffix))
81 | {
82 | return true;
83 | }
84 | }
85 | return false;
86 | }
87 |
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Processors/OUProcessor.cs:
--------------------------------------------------------------------------------
1 | using SOAPHound.ADWS;
2 | using SOAPHound.Enums;
3 | using SOAPHound.OutputTypes;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Diagnostics;
7 | using System.DirectoryServices;
8 | using System.Linq;
9 | using System.Security.Principal;
10 | using System.Text;
11 | using System.Threading.Tasks;
12 |
13 | namespace SOAPHound.Processors
14 | {
15 | class OUProcessor
16 | {
17 | public OUNode parseOUObject(ADObject adobject, string domainName)
18 | {
19 | DateTime EpochDiff = new DateTime(1970, 1, 1);
20 |
21 | Label objectType = Label.OU;
22 |
23 | OUNode adnode = new OUNode()
24 | {
25 |
26 | ObjectIdentifier = adobject.ObjectGUID.ToString().ToUpper(),
27 | Properties = new OUProperties()
28 | {
29 | name = adobject.Name.ToUpper() + "@" + domainName.ToUpper(),
30 | domainsid = null,
31 | domain = domainName.ToUpper(),
32 | distinguishedname = adobject.DistinguishedName.ToUpper(),
33 | whencreated = (long)adobject.WhenCreated.Subtract(EpochDiff).TotalSeconds,
34 | description = adobject.Description,
35 | blocksinheritance = (adobject.GPOptions == 1)?true:false,
36 | },
37 | Links = parseLinks(adobject.GPLink),
38 | ChildObjects = parseChildObjects(adobject.DistinguishedName),
39 | Aces = ACLProcessor.parseAces(adobject.NTSecurityDescriptor, objectType, false),
40 | IsDeleted = (adobject.IsDeleted == null) ? false : true,
41 | IsACLProtected = (adobject.NTSecurityDescriptor.AreAccessRulesProtected || adobject.NTSecurityDescriptor.AreAuditRulesProtected) ? true : false,
42 | };
43 |
44 | return adnode;
45 |
46 | }
47 |
48 | private GPLink[] parseLinks(string gplink)
49 | {
50 | GPLink[] links = new GPLink[] { };
51 | if (String.IsNullOrEmpty(gplink)||!gplink.ToUpper().Contains("CN"))
52 | {
53 | return links; // return empty if gplink is invalid
54 | }
55 | string[] splitlinks = gplink.Split(new string[] { "[", "]" }, StringSplitOptions.RemoveEmptyEntries);
56 | foreach (string splitlink in splitlinks)
57 | {
58 | string enforceValue = splitlink.Split(';')[1];
59 | string gpo = splitlink.Split(';')[0].Remove(0,7);
60 | if (Cache.GetConvertedValue(gpo, out var guid))
61 | {
62 | GPLink link = new GPLink();
63 | if (enforceValue == "2")
64 | {
65 | link.IsEnforced = true;
66 | }
67 | else
68 | {
69 | link.IsEnforced = false;
70 | }
71 | link.GUID = guid.ToUpper();
72 | links = links.Append(link).ToArray();
73 | }
74 | else
75 | {
76 | Trace.WriteLine("GPO with dn " + gpo + " not found in cache.");
77 | continue;
78 | }
79 |
80 | }
81 | return links;
82 | }
83 |
84 | private TypedPrincipal[] parseChildObjects(string dn)
85 | {
86 | Cache.GetChildObjects(dn, out TypedPrincipal[] childObjects);
87 | return childObjects;
88 | }
89 |
90 |
91 |
92 |
93 |
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Processors/DomainProcessor.cs:
--------------------------------------------------------------------------------
1 | using SOAPHound.ADWS;
2 | using SOAPHound.Enums;
3 | using SOAPHound.OutputTypes;
4 | using System;
5 | using System.Linq;
6 | using System.Net;
7 | using System.Threading.Tasks;
8 |
9 | namespace SOAPHound.Processors
10 | {
11 | class DomainProcessor
12 | {
13 | private string Server = null;
14 | private int Port = 9389;
15 | private NetworkCredential Credential = null;
16 |
17 | public DomainProcessor(string argServer, int argPort, NetworkCredential argCredential)
18 | {
19 | Server = argServer;
20 | Port = argPort;
21 | Credential = argCredential;
22 | }
23 | public DomainNode parseDomainObject(ADObject adobject)
24 | {
25 | DateTime EpochDiff = new DateTime(1970, 1, 1);
26 |
27 | Label objectType = ADWSUtils.ResolveIDAndType(adobject.ObjectSid.ToString()).ObjectType;
28 | var trustedDomains = ADWSUtils.GetObjects("domaintrusts");
29 |
30 | DomainNode adnode = new DomainNode()
31 | {
32 |
33 | ObjectIdentifier = adobject.ObjectSid.ToString(),
34 | Properties = new DomainProperties()
35 | {
36 | name = ADWSUtils.DistinguishedNameToDomain(adobject.DistinguishedName).ToUpper(),
37 | domainsid = adobject.ObjectSid.ToString(),
38 | domain = ADWSUtils.DistinguishedNameToDomain(adobject.DistinguishedName),
39 | distinguishedname = adobject.DistinguishedName.ToUpper(),
40 | description = adobject.Description,
41 | functionallevel = FunctionalLevelToString(adobject.FunctionalLevel),
42 | whencreated = (long)adobject.WhenCreated.Subtract(EpochDiff).TotalSeconds,
43 | highvalue = true,
44 | },
45 | Aces = ACLProcessor.parseAces(adobject.NTSecurityDescriptor, objectType, false),
46 | Links = { },
47 | ChildObjects = parseChildObjects(adobject.DistinguishedName),
48 | GPOChanges = { },
49 | IsDeleted = (adobject.IsDeleted == null) ? false : true,
50 | IsACLProtected = (adobject.NTSecurityDescriptor.AreAccessRulesProtected || adobject.NTSecurityDescriptor.AreAuditRulesProtected) ? true : false,
51 | };
52 | foreach (ADObject trustedDomain in trustedDomains)
53 | {
54 | DomainTrustProcessor _dtp = new DomainTrustProcessor();
55 | DomainTrust trust = _dtp.ProcessDomainTrusts(trustedDomain);
56 | adnode.Trusts = adnode.Trusts.Append(trust).ToArray();
57 | }
58 | return adnode;
59 |
60 | }
61 |
62 | public static string FunctionalLevelToString(int level)
63 | {
64 | string functionalLevel;
65 | switch (level)
66 | {
67 | case 0:
68 | functionalLevel = "2000 Mixed/Native";
69 | break;
70 | case 1:
71 | functionalLevel = "2003 Interim";
72 | break;
73 | case 2:
74 | functionalLevel = "2003";
75 | break;
76 | case 3:
77 | functionalLevel = "2008";
78 | break;
79 | case 4:
80 | functionalLevel = "2008 R2";
81 | break;
82 | case 5:
83 | functionalLevel = "2012";
84 | break;
85 | case 6:
86 | functionalLevel = "2012 R2";
87 | break;
88 | case 7:
89 | functionalLevel = "2016";
90 | break;
91 | default:
92 | functionalLevel = "Unknown";
93 | break;
94 | }
95 |
96 | return functionalLevel;
97 | }
98 |
99 | private TypedPrincipal[] parseChildObjects(string dn)
100 | {
101 | Cache.GetDomainChildObjects(dn, out TypedPrincipal[] childObjects);
102 | return childObjects;
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/OutputTypes/CANodes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using SOAPHound.Enums;
4 | using Newtonsoft.Json;
5 |
6 | namespace SOAPHound.ADWS
7 | {
8 |
9 | public class OutputCA
10 | {
11 | public List data { get; set; } = new List();
12 | public Meta meta { get; set; } = new Meta();
13 |
14 | public OutputCA()
15 | {
16 | meta.type = "gpos";
17 | }
18 | }
19 |
20 | public class OutputCATemplate
21 | {
22 | public List data { get; set; } = new List();
23 | public Meta meta { get; set; } = new Meta();
24 |
25 | public OutputCATemplate()
26 | {
27 | meta.type = "gpos";
28 | }
29 | }
30 |
31 | public class CATemplate
32 | {
33 | public string ObjectIdentifier { get; set; }
34 | public IEnumerable Aces { get; set; }
35 | public CATemplateProperties Properties { get; set; }
36 | }
37 |
38 | public class CATemplateProperties
39 | {
40 | public string name { get; set; }
41 | public Boolean highvalue { get; set; } = false;
42 | [JsonProperty("Template Name")]
43 | public string templatename { get; set; }
44 | [JsonProperty("Display Name")]
45 | public string displayname { get; set; }
46 | [JsonProperty("Certificate Authorities")]
47 | public List certificateauthorities { get; set; }
48 | public Boolean Enabled { get; set; }
49 | [JsonProperty("Client Authentication")]
50 | public Boolean clientauthentication { get; set; }
51 | [JsonProperty("Enrollment Agent")]
52 | public Boolean enrollmentagent { get; set; }
53 | [JsonProperty("Any Purpose")]
54 | public Boolean anypurpose { get; set; }
55 | [JsonProperty("Enrollee Supplies Subject")]
56 | public Boolean enrolleesuppliessubject { get; set; }
57 | [JsonProperty("Certificate Name Flag")]
58 | public List certificatenameflag { get; set; }
59 | [JsonProperty("Enrollment Flag")]
60 | public List enrollmentflag { get; set; }
61 | [JsonProperty("Private Key Flag")]
62 | public List privatekeyflag { get; set; }
63 | [JsonProperty("Extended Key Usage")]
64 | public List extendedkeyusage { get; set; }
65 | [JsonProperty("Requires Manager Approval")]
66 | public Boolean requiresmanagerapproval { get; set; }
67 | [JsonProperty("Requires Key Archival")]
68 | public Boolean requireskeyarchival { get; set; }
69 | [JsonProperty("Authorized Signatures Required")]
70 | public int authorizedsignaturesrequired { get; set; }
71 | [JsonProperty("Validity Period")]
72 | public string validityperiod { get; set; }
73 | [JsonProperty("Renewal Period")]
74 | public string renewalperiod { get; set; }
75 | [JsonProperty("Minimum RSA Key Length")]
76 | public int minimumrsakeylength { get; set; }
77 | public string domain { get; set; }
78 | public string type { get; set; }
79 | }
80 |
81 |
82 |
83 |
84 | public class CA
85 | {
86 | public string ObjectIdentifier { get; set; }
87 | public IEnumerable Aces { get; set; }
88 | public CAProperties Properties { get; set; }
89 | }
90 |
91 | public class CAProperties
92 | {
93 | public string name { get; set; }
94 | public Boolean highvalue { get; set; } = false;
95 | public string domain { get; set; }
96 | [JsonProperty("CA Name")]
97 | public string caname { get; set; }
98 | [JsonProperty("DNS Name")]
99 | public string dnsname { get; set; }
100 | [JsonProperty("Certificate Subject")]
101 | public string certificatesubject { get; set; }
102 | [JsonProperty("Certificate Serial Number")]
103 | public string certificateserialnumber { get; set; }
104 | [JsonProperty("Certificate Validity Start")]
105 | public DateTime certificatevaliditystart { get; set; }
106 | [JsonProperty("Certificate Validity End")]
107 | public DateTime certificatevalidityend { get; set; }
108 | [JsonProperty("Web Enrollment")]
109 | public string webenrollment { get; set; } = "";
110 | [JsonProperty("User Specified SAN")]
111 | public string userspecifiedsan { get; set; } = "";
112 | [JsonProperty("Request Disposition")]
113 | public string requestdisposition { get; set; } = "";
114 | [JsonProperty("Enforce Encryption For Requests")]
115 | public string enforceencryptionforrequests { get; set; } = "";
116 | public string type { get; set; }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/Processors/UserProcessor.cs:
--------------------------------------------------------------------------------
1 | using SOAPHound.ADWS;
2 | using SOAPHound.Enums;
3 | using SOAPHound.OutputTypes;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.DirectoryServices;
7 | using System.Linq;
8 | using System.Security.Principal;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace SOAPHound.Processors
13 | {
14 | class UserProcessor
15 | {
16 | public UserNode parseUserObject(ADObject adobject, string domainName)
17 | {
18 | DateTime EpochDiff = new DateTime(1970, 1, 1);
19 | bool enabled, trustedToAuth, sensitive, dontReqPreAuth, passwdNotReq, unconstrained, pwdNeverExpires;
20 |
21 | // Extract domainsid from User SID
22 | string domainsid = adobject.ObjectSid.ToString().Substring(0, adobject.ObjectSid.ToString().LastIndexOf('-'));
23 |
24 | //checking if SIDHistory is not null, then convert SecurityIdentifier[] to String[] withn LINQ (triggers exception if null)
25 | string[] sidhistorytmp = new string[] { };
26 | if (adobject.SIDHistory != null)
27 | {
28 | sidhistorytmp = adobject.SIDHistory.Select(i => i.ToString()).ToArray();
29 | }
30 | else
31 | sidhistorytmp = new string[] { };
32 |
33 |
34 | var uac = adobject.UserAccountControl.ToString();
35 |
36 | if (int.TryParse(uac, out var flag))
37 | {
38 | var flags = (UacFlags)flag;
39 | enabled = (flags & UacFlags.AccountDisable) == 0;
40 | trustedToAuth = (flags & UacFlags.TrustedToAuthForDelegation) != 0;
41 | sensitive = (flags & UacFlags.NotDelegated) != 0;
42 | dontReqPreAuth = (flags & UacFlags.DontReqPreauth) != 0;
43 | passwdNotReq = (flags & UacFlags.PasswordNotRequired) != 0;
44 | unconstrained = (flags & UacFlags.TrustedForDelegation) != 0;
45 | pwdNeverExpires = (flags & UacFlags.DontExpirePassword) != 0;
46 | }
47 | else
48 | {
49 | trustedToAuth = false;
50 | enabled = true;
51 | sensitive = false;
52 | dontReqPreAuth = false;
53 | passwdNotReq = false;
54 | unconstrained = false;
55 | pwdNeverExpires = false;
56 | }
57 |
58 |
59 | ////TODO allowedtodelegate
60 |
61 | Label objectType = ADWSUtils.ResolveIDAndType(adobject.ObjectSid.ToString()).ObjectType;
62 |
63 | UserNode adnode = new UserNode()
64 | {
65 |
66 | ObjectIdentifier = adobject.ObjectSid.ToString(),
67 | PrimaryGroupSID = domainsid + "-" + adobject.PrimaryGroupID.ToString(),
68 | Properties = new UserProperties()
69 | {
70 | name = adobject.SAMAccountName.ToUpper() + "@" + domainName.ToUpper(),
71 | samaccountname = adobject.SAMAccountName,
72 | domainsid = domainsid,
73 | domain = domainName.ToUpper(),
74 | distinguishedname = adobject.DistinguishedName.ToUpper(),
75 | unconstraineddelegation = unconstrained,
76 | trustedtoauth = trustedToAuth,
77 | enabled = enabled,
78 | passwordnotreqd = passwdNotReq,
79 | dontreqpreauth = dontReqPreAuth,
80 | pwdneverexpires = pwdNeverExpires,
81 | sensitive = sensitive,
82 | lastlogon = (long)adobject.LastLogon.Subtract(EpochDiff).TotalSeconds,
83 | lastlogontimestamp = (long)adobject.LastLogonTimestamp.Subtract(EpochDiff).TotalSeconds,
84 | pwdlastset = (long)adobject.PwdLastSet.Subtract(EpochDiff).TotalSeconds,
85 | whencreated = (long)adobject.WhenCreated.Subtract(EpochDiff).TotalSeconds,
86 | serviceprincipalnames = adobject.ServicePrincipalName,
87 | description = adobject.Description,
88 | sidhistory = sidhistorytmp,
89 | displayName = adobject.DisplayName,
90 | admincount = (adobject.AdminCount > 0),
91 | email = adobject.Email,
92 | title = adobject.Title,
93 | homedirectory = adobject.HomeDirectory,
94 | userpassword = adobject.UserPassword,
95 | unixpassword = adobject.UnixUserPassword,
96 | unicodepassword = adobject.UnicodePassword,
97 | sfupassword = adobject.MsSFU30Password,
98 | logonscript = adobject.ScriptPath,
99 | },
100 | Aces = ACLProcessor.parseAces(adobject.NTSecurityDescriptor, objectType, false),
101 | IsDeleted = (adobject.IsDeleted == null) ? false : true,
102 | IsACLProtected = (adobject.NTSecurityDescriptor.AreAccessRulesProtected || adobject.NTSecurityDescriptor.AreAuditRulesProtected) ? true : false,
103 | };
104 |
105 | //Update negative values for lastlogon and timestamp, which happens when the object never logged on
106 | if (adnode.Properties.lastlogon < 0)
107 | adnode.Properties.lastlogon = 0;
108 | if (adnode.Properties.lastlogontimestamp < 0)
109 | adnode.Properties.lastlogontimestamp = -1;
110 | //Update negative value for pwdlastset
111 | if (adnode.Properties.pwdlastset < 0)
112 | adnode.Properties.pwdlastset = 0;
113 | //Update hasspn attribute
114 | if (adnode.Properties.serviceprincipalnames != null)
115 | adnode.Properties.hasspn = (adobject.ServicePrincipalName.Length > 0);
116 | else
117 | adnode.Properties.hasspn = false;
118 | return adnode;
119 |
120 | }
121 |
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/Processors/ComputerProcessor.cs:
--------------------------------------------------------------------------------
1 | using SOAPHound.ADWS;
2 | using SOAPHound.Enums;
3 | using SOAPHound.OutputTypes;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.DirectoryServices;
7 | using System.Linq;
8 | using System.Security.Principal;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace SOAPHound.Processors
13 | {
14 | class ComputerProcessor
15 | {
16 | public ComputerNode parseComputerObject(ADObject adobject, string domainName)
17 | {
18 | DateTime EpochDiff = new DateTime(1970, 1, 1);
19 | bool enabled, unconstrained, trustedToAuth, hasLaps;
20 |
21 | // Extract domainsid from Computer SID
22 | string domainsid = adobject.ObjectSid.ToString().Substring(0, adobject.ObjectSid.ToString().LastIndexOf('-'));
23 |
24 | //checking if SIDHistory is not null, then convert SecurityIdentifier[] to String[] withn LINQ (triggers exception if null)
25 | string[] sidhistorytmp = new string[] { };
26 | if (adobject.SIDHistory != null)
27 | {
28 | sidhistorytmp = adobject.SIDHistory.Select(i => i.ToString()).ToArray(); // TO BE TESTED
29 | }
30 | else
31 | sidhistorytmp = new string[] { };
32 |
33 |
34 | var uac = adobject.UserAccountControl.ToString();
35 |
36 | if (int.TryParse(uac, out var flag))
37 | {
38 | var flags = (UacFlags)flag;
39 | enabled = (flags & UacFlags.AccountDisable) == 0;
40 | unconstrained = (flags & UacFlags.TrustedForDelegation) == UacFlags.TrustedForDelegation;
41 | trustedToAuth = (flags & UacFlags.TrustedToAuthForDelegation) != 0;
42 | }
43 | else
44 | {
45 | unconstrained = false;
46 | enabled = true;
47 | trustedToAuth = false;
48 | }
49 |
50 |
51 |
52 |
53 | if (adobject.MsMCSAdmPwdExpirationTime != 0)
54 | {
55 | hasLaps = true;
56 | }
57 | else
58 | hasLaps = false;
59 |
60 |
61 | //TODO allowedtodelegate
62 |
63 | var allowedToActPrincipals = new List();
64 | ActiveDirectorySecurity sd = adobject.MsDSAllowedToActOnBehalfOfOtherIdentity;
65 | if (sd != null)
66 | {
67 | foreach (ActiveDirectoryAccessRule rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier)))
68 | {
69 | var res = new TypedPrincipal();
70 | res.ObjectIdentifier = rule.IdentityReference.ToString();
71 | res.ObjectType = ADWSUtils.ResolveIDAndType(res.ObjectIdentifier).ObjectType;
72 | allowedToActPrincipals.Add(res);
73 | }
74 | }
75 | Label objectType = ADWSUtils.ResolveIDAndType(adobject.ObjectSid.ToString()).ObjectType;
76 |
77 | string compName = "UNKNOWN";
78 | var shortName = adobject.SAMAccountName?.TrimEnd('$');
79 | var dns = adobject.DNSHostName;
80 | var cn = adobject.Cn;
81 | var itemDomain = domainName.ToUpper();
82 |
83 | if (dns != null)
84 | compName = dns;
85 | else if (shortName == null && cn == null)
86 | compName = $"UNKNOWN.{itemDomain}";
87 | else if (shortName != null)
88 | compName = $"{shortName}.{itemDomain}";
89 | else
90 | compName = $"{cn}.{itemDomain}";
91 |
92 | ComputerNode adnode = new ComputerNode()
93 | {
94 | ObjectIdentifier = adobject.ObjectSid.ToString(),
95 | AllowedToAct = allowedToActPrincipals,
96 | PrimaryGroupSID = domainsid + "-" + adobject.PrimaryGroupID.ToString(),
97 | LocalAdmins = new APIResult(),
98 | PSRemoteUsers = new APIResult(),
99 | Properties = new ComputerProperties()
100 | {
101 | name = compName.ToUpper(),
102 | samaccountname = adobject.SAMAccountName,
103 | domainsid = domainsid,
104 | domain = domainName.ToUpper(),
105 | distinguishedname = adobject.DistinguishedName.ToUpper(),
106 | unconstraineddelegation = unconstrained,
107 | enabled = enabled,
108 | trustedtoauth = trustedToAuth,
109 | haslaps = hasLaps,
110 | lastlogon = (long)adobject.LastLogon.Subtract(EpochDiff).TotalSeconds,
111 | lastlogontimestamp = (long)adobject.LastLogonTimestamp.Subtract(EpochDiff).TotalSeconds,
112 | pwdlastset = (long)adobject.PwdLastSet.Subtract(EpochDiff).TotalSeconds,
113 | whencreated = (long)adobject.WhenCreated.Subtract(EpochDiff).TotalSeconds,
114 | serviceprincipalnames = adobject.ServicePrincipalName,
115 | description = adobject.Description,
116 | operatingsystem = adobject.OperatingSystem,
117 | sidhistory = sidhistorytmp,
118 | },
119 | RemoteDesktopUsers = new APIResult(),
120 | DcomUsers = new APIResult(),
121 | PrivilegedSessions = new APIResult(),
122 | Sessions = new APIResult(),
123 | RegistrySessions = new APIResult(),
124 | Aces = ACLProcessor.parseAces(adobject.NTSecurityDescriptor, objectType, hasLaps),
125 | IsDeleted = (adobject.IsDeleted == null) ? false:true,
126 | IsACLProtected = (adobject.NTSecurityDescriptor.AreAccessRulesProtected || adobject.NTSecurityDescriptor.AreAuditRulesProtected) ? true : false,
127 | };
128 | //Update negative values for lastlogon and timestamp, which happens when the object never logged on
129 | if (adnode.Properties.lastlogon < 0)
130 | adnode.Properties.lastlogon = 0;
131 | if (adnode.Properties.lastlogontimestamp < 0)
132 | adnode.Properties.lastlogontimestamp = -1;
133 | //Update negative value for pwdlastset
134 | if (adnode.Properties.pwdlastset < 0)
135 | adnode.Properties.pwdlastset = 0;
136 | return adnode;
137 |
138 | }
139 |
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/Processors/CAProcessor.cs:
--------------------------------------------------------------------------------
1 | using SOAPHound.ADWS;
2 | using SOAPHound.Enums;
3 | using System;
4 | using System.Linq;
5 |
6 | namespace SOAPHound.Processors
7 | {
8 | class CAProcessor
9 | {
10 | public CA parseCA(ADObject adobject, string domainName)
11 | {
12 | DateTime EpochDiff = new DateTime(1970, 1, 1);
13 |
14 |
15 | CA canode = new CA()
16 | {
17 |
18 | ObjectIdentifier = adobject.ObjectGUID.ToString(),
19 | Properties = new CAProperties()
20 | {
21 | name = adobject.Name.ToUpper() + "@" + domainName.ToUpper(),
22 | domain = domainName.ToUpper(),
23 | highvalue = false,
24 | caname = adobject.Name,
25 | dnsname = adobject.DNSHostName,
26 | certificateserialnumber = adobject.CACertificate[0].SerialNumber,
27 | certificatesubject = adobject.CACertificate[0].Subject,
28 | certificatevaliditystart = adobject.CACertificate[0].NotBefore,
29 | certificatevalidityend = adobject.CACertificate[0].NotAfter,
30 | type = "Enrollment Service"
31 | },
32 | Aces = ACLProcessor.parseAces(adobject.NTSecurityDescriptor, Label.CA, false),
33 | };
34 | return canode;
35 | }
36 |
37 | public CATemplate parseCATemplate(ADObject adobject, string domainName)
38 | {
39 | DateTime EpochDiff = new DateTime(1970, 1, 1);
40 | OidConverter _oid = new OidConverter();
41 |
42 | CATemplate catemplate = new CATemplate()
43 | {
44 |
45 | ObjectIdentifier = adobject.ObjectGUID.ToString(),
46 | Properties = new CATemplateProperties()
47 | {
48 | name = adobject.Name.ToUpper() + "@" + domainName.ToUpper(),
49 | templatename = adobject.Name,
50 | displayname = adobject.DisplayName,
51 | certificateauthorities = PKICache.GetTemplateCA(adobject.Name),
52 | Enabled = (PKICache.GetTemplateCA(adobject.Name).Count > 0),
53 | certificatenameflag = ParseIntToEnum(adobject.MsPKICertificateNameFlag.ToString()).ToString().Split(',').Select(x => (msPKICertificateNameFlag)Enum.Parse(typeof(msPKICertificateNameFlag), x.Trim())).ToList(),
54 | enrollmentflag = ParseIntToEnum(adobject.MsPKIEnrollmentFlag.ToString()).ToString().Split(',').Select(x => (msPKIEnrollmentFlag)Enum.Parse(typeof(msPKIEnrollmentFlag), x.Trim())).ToList(),
55 | privatekeyflag = ParseIntToEnum((adobject.MsPKIPrivateKeyFlag & 0x00FFFFFF).ToString()).ToString().Split(',').Select(x => (msPKIPrivateKeyFlag)Enum.Parse(typeof(msPKIPrivateKeyFlag), x.Trim())).ToList(),
56 | extendedkeyusage = _oid.LookupOid(adobject.PKIExtendedKeyUsage),
57 | validityperiod = ConvertPKIPeriod(adobject.PKIExpirationPeriod),
58 | renewalperiod = ConvertPKIPeriod(adobject.PKIOverlapPeriod),
59 | minimumrsakeylength = adobject.MsPKIMinimalKeySize,
60 | domain = domainName.ToUpper(),
61 | type = "Certificate Template"
62 | },
63 | Aces = ACLProcessor.parseAces(adobject.NTSecurityDescriptor, Label.Base, false).ToList(),
64 |
65 | };
66 | catemplate.Properties.clientauthentication = new[] { "Any Purpose", "Client Authentication", "Smart Card Logon", "PKINIT Client Authentication" }.Any(c => catemplate.Properties.extendedkeyusage.Contains(c));
67 | catemplate.Properties.anypurpose = catemplate.Properties.extendedkeyusage.Any("Any Purpose".Contains);
68 | catemplate.Properties.enrollmentagent = new[] { "Any Purpose", "Certificate Request Agent"}.Any(c => catemplate.Properties.extendedkeyusage.Contains(c));
69 | catemplate.Properties.enrolleesuppliessubject = catemplate.Properties.certificatenameflag.Contains(msPKICertificateNameFlag.ENROLLEE_SUPPLIES_SUBJECT);
70 | catemplate.Properties.requiresmanagerapproval = catemplate.Properties.enrollmentflag.Contains(msPKIEnrollmentFlag.PEND_ALL_REQUESTS);
71 | catemplate.Properties.requireskeyarchival = catemplate.Properties.privatekeyflag.Contains(msPKIPrivateKeyFlag.REQUIRE_PRIVATE_KEY_ARCHIVAL);
72 | catemplate.Properties.highvalue = catemplate.Properties.Enabled && ((catemplate.Properties.enrolleesuppliessubject && !catemplate.Properties.requiresmanagerapproval && catemplate.Properties.clientauthentication) || (catemplate.Properties.enrollmentagent && !catemplate.Properties.requiresmanagerapproval));
73 | return catemplate;
74 | }
75 |
76 |
77 | public T ParseIntToEnum(string value)
78 | {
79 | var intVal = Convert.ToInt32(value);
80 | var uintVal = unchecked((uint)intVal);
81 |
82 | return (T)Enum.Parse(typeof(T), uintVal.ToString());
83 | }
84 |
85 | private string ConvertPKIPeriod(byte[] bytes)
86 | {
87 | // ref: https://www.sysadmins.lv/blog-en/how-to-convert-pkiexirationperiod-and-pkioverlapperiod-active-directory-attributes.aspx
88 | try
89 | {
90 | Array.Reverse(bytes);
91 | var temp = BitConverter.ToString(bytes).Replace("-", "");
92 | var value = Convert.ToInt64(temp, 16) * -.0000001;
93 |
94 | if ((value % 31536000 == 0) && (value / 31536000) >= 1)
95 | {
96 | if ((value / 31536000) == 1)
97 | {
98 | return "1 year";
99 | }
100 |
101 | return $"{value / 31536000} years";
102 | }
103 | else if ((value % 2592000 == 0) && (value / 2592000) >= 1)
104 | {
105 | if ((value / 2592000) == 1)
106 | {
107 | return "1 month";
108 | }
109 | else
110 | {
111 | return $"{value / 2592000} months";
112 | }
113 | }
114 | else if ((value % 604800 == 0) && (value / 604800) >= 1)
115 | {
116 | if ((value / 604800) == 1)
117 | {
118 | return "1 week";
119 | }
120 | else
121 | {
122 | return $"{value / 604800} weeks";
123 | }
124 | }
125 | else if ((value % 86400 == 0) && (value / 86400) >= 1)
126 | {
127 | if ((value / 86400) == 1)
128 | {
129 | return "1 day";
130 | }
131 | else
132 | {
133 | return $"{value / 86400} days";
134 | }
135 | }
136 | else if ((value % 3600 == 0) && (value / 3600) >= 1)
137 | {
138 | if ((value / 3600) == 1)
139 | {
140 | return "1 hour";
141 | }
142 | else
143 | {
144 | return $"{value / 3600} hours";
145 | }
146 | }
147 | else
148 | {
149 | return "";
150 | }
151 | }
152 | catch (Exception)
153 | {
154 | return "ERROR";
155 | }
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/Processors/Cache.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Reflection;
6 | using System.Runtime.Serialization;
7 | using SOAPHound.Enums;
8 | using SOAPHound.OutputTypes;
9 | using Newtonsoft.Json;
10 | using Newtonsoft.Json.Serialization;
11 | using System.Linq;
12 | using System.Diagnostics;
13 |
14 | namespace SOAPHound.Processors
15 | {
16 |
17 |
18 | // We're using the WCF datacontract to serialize the cache as a JSON object
19 |
20 | public static class Cache
21 | {
22 | static Cache()
23 | {
24 | ValueToIdCache = new Dictionary(StringComparer.OrdinalIgnoreCase); //added OrdinalIgnoreCase to use case insensitive comparisons for gplink->gpo
25 | IdToTypeCache = new Dictionary();
26 | }
27 |
28 | // This class is here to work aroud the limitation of NewtonSoft in deserializing static classes.
29 | [DataContract]
30 | internal class SerializeableCache
31 | {
32 | [DataMember] public Dictionary IdToTypeCache { get; set; }
33 |
34 | [DataMember] public Dictionary ValueToIdCache { get; set; }
35 | }
36 |
37 | public class CacheContractResolver : DefaultContractResolver
38 | {
39 | private static readonly CacheContractResolver Instance = new CacheContractResolver();
40 | public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings()
41 | {
42 | ContractResolver = Instance
43 | };
44 |
45 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
46 | {
47 | var prop = base.CreateProperty(member, memberSerialization);
48 | if (!prop.Writable && (member as PropertyInfo)?.GetSetMethod(true) != null)
49 | {
50 | prop.Writable = true;
51 | }
52 | return prop;
53 | }
54 |
55 | }
56 |
57 | public static void Deserialize(string path)
58 | {
59 | var json = File.ReadAllText(path);
60 | SerializeableCache tempCache = JsonConvert.DeserializeObject(json, CacheContractResolver.Settings);
61 | Cache.ValueToIdCache = new Dictionary(tempCache.ValueToIdCache, StringComparer.OrdinalIgnoreCase);
62 | Cache.IdToTypeCache = tempCache.IdToTypeCache;
63 | }
64 |
65 | public static void Serialize(string path)
66 | {
67 | SerializeableCache tempCache = new SerializeableCache();
68 | tempCache.IdToTypeCache = Cache.IdToTypeCache;
69 | tempCache.ValueToIdCache = Cache.ValueToIdCache;
70 | var serialized = JsonConvert.SerializeObject(tempCache);
71 | File.WriteAllText(path, serialized);
72 | }
73 |
74 | public static Dictionary IdToTypeCache { get; private set; }
75 |
76 | public static Dictionary ValueToIdCache { get; private set; }
77 |
78 |
79 |
80 | internal static void AddConvertedValue(string key, string value)
81 | {
82 | if (ValueToIdCache.ContainsKey(key))
83 | {
84 | Console.WriteLine("Duplicate key found with value: " + key);
85 | }
86 | else
87 | {
88 | ValueToIdCache.Add(key, value);
89 | }
90 | }
91 |
92 |
93 | internal static void AddType(string key, Label value)
94 | {
95 | if (IdToTypeCache.ContainsKey(key))
96 | {
97 | Console.WriteLine("Duplicate key found with value: " + key);
98 | }
99 | else
100 | {
101 | IdToTypeCache.Add(key, value);
102 | }
103 | }
104 |
105 | internal static bool GetConvertedValue(string key, out string value)
106 | {
107 | return ValueToIdCache.TryGetValue(key, out value);
108 | }
109 |
110 | //internal static bool GetPrefixedValue(string key, string domain, out string value)
111 | //{
112 | // return ValueToIdCache.TryGetValue(GetPrefixKey(key, domain), out value);
113 | //}
114 |
115 | internal static bool GetIDType(string key, out Label value)
116 | {
117 | if (!IdToTypeCache.TryGetValue(key, out value))
118 | {
119 | value = Label.Base;
120 | return false;
121 | }
122 | else
123 | {
124 | return true;
125 | }
126 | }
127 |
128 | internal static bool GetChildObjects(string dn, out TypedPrincipal[] childObjects)
129 | {
130 | childObjects = new TypedPrincipal[] { };
131 | var matchingKeysAll = ValueToIdCache.Where(kvp => kvp.Key.Contains(dn)).Select(kvp => kvp.Key);
132 | var matchingKeys = matchingKeysAll.Where(key => key != dn).ToList();
133 |
134 |
135 |
136 | foreach (string matchingKey in matchingKeys)
137 | {
138 | if (IsDistinguishedNameFiltered(matchingKey))
139 | continue;
140 |
141 | TypedPrincipal childObject = new TypedPrincipal { };
142 | if (GetConvertedValue(matchingKey, out var id) && GetIDType(id, out var type))
143 | {
144 | childObject = new TypedPrincipal
145 | {
146 | ObjectIdentifier = id.ToUpper(),
147 | ObjectType = type
148 | };
149 | childObjects = childObjects.Append(childObject).ToArray();
150 | }
151 | else
152 | continue;
153 | }
154 |
155 | if (matchingKeys == null)
156 | {
157 | return false;
158 | }
159 | else
160 | {
161 | return true;
162 | }
163 | }
164 |
165 |
166 | internal static bool GetDomainChildObjects(string dn, out TypedPrincipal[] childObjects)
167 | {
168 | int dnlevel = dn.Count(f => f == '=');
169 | childObjects = new TypedPrincipal[] { };
170 | var matchingKeysAll = ValueToIdCache.Where(kvp => kvp.Key.Contains(dn)).Select(kvp => kvp.Key);
171 | var matchingKeys = matchingKeysAll.Where(key => key != dn).ToList();
172 |
173 |
174 |
175 | foreach (string matchingKey in matchingKeys)
176 | {
177 | //Getting one sublevel of data for the domain child objects
178 | if (matchingKey.Count(f => f == '=') != (dnlevel + 1))
179 | continue;
180 |
181 | if (IsDistinguishedNameFiltered(matchingKey))
182 | continue;
183 |
184 | TypedPrincipal childObject = new TypedPrincipal { };
185 | if (GetConvertedValue(matchingKey, out var id) && GetIDType(id, out var type))
186 | {
187 | childObject = new TypedPrincipal
188 | {
189 | ObjectIdentifier = id.ToUpper(),
190 | ObjectType = type
191 | };
192 | childObjects = childObjects.Append(childObject).ToArray();
193 | }
194 | else
195 | continue;
196 | }
197 |
198 | if (matchingKeys == null)
199 | {
200 | return false;
201 | }
202 | else
203 | {
204 | return true;
205 | }
206 | }
207 |
208 | private static bool IsDistinguishedNameFiltered(string distinguishedName)
209 | {
210 | var dn = distinguishedName.ToUpper();
211 | if (dn.Contains("CN=PROGRAM DATA,DC=")) return true;
212 |
213 | if (dn.Contains("CN=SYSTEM,DC=")) return true;
214 |
215 | return false;
216 | }
217 |
218 | private static string GetPrefixKey(string key, string domain)
219 | {
220 | return $"{key}|{domain}";
221 | }
222 | public static string GetCacheStats()
223 | {
224 | try
225 | {
226 | return
227 | $"{IdToTypeCache.Count} ID to type mappings.\n {ValueToIdCache.Count} name to SID mappings.\n";
228 | }
229 | catch
230 | {
231 | return "";
232 | }
233 | }
234 |
235 |
236 |
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/OutputTypes/Node.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using SOAPHound.OutputTypes;
4 | using SOAPHound.Enums;
5 | using Newtonsoft.Json;
6 | using Newtonsoft.Json.Converters;
7 |
8 | namespace SOAPHound.ADWS
9 | {
10 | public class Meta
11 | {
12 | public int methods { get; set; } = 0;
13 | public string type { get; set; } = String.Empty;
14 | public int count { get; set; }
15 | public int version { get; set; } = 5;
16 | }
17 |
18 | public abstract class AbstractNode
19 | {
20 | public List data { get; set; } = new List();
21 | public Meta meta { get; set; } = new Meta();
22 |
23 | public AbstractNode(string metaType)
24 | {
25 | meta.type = metaType;
26 | }
27 | }
28 | public class OutputComputers : AbstractNode
29 | {
30 | public OutputComputers() : base("computers")
31 | {
32 | }
33 | }
34 |
35 | public class OutputUsers : AbstractNode
36 | {
37 | public OutputUsers() : base("users")
38 | {
39 | }
40 | }
41 |
42 |
43 | public class OutputGroups : AbstractNode
44 | {
45 | public OutputGroups() : base("groups")
46 | {
47 | }
48 | }
49 |
50 | public class OutputDomains : AbstractNode
51 | {
52 | public OutputDomains() : base("domains")
53 | {
54 | }
55 | }
56 |
57 | public class OutputGPOs : AbstractNode
58 | {
59 | public OutputGPOs() : base("gpos")
60 | {
61 | }
62 | }
63 |
64 | public class OutputOUs : AbstractNode
65 | {
66 | public OutputOUs() : base("ous")
67 | {
68 | }
69 | }
70 |
71 | public class OutputContainers : AbstractNode
72 | {
73 | public OutputContainers() : base("containers")
74 | {
75 | }
76 | }
77 |
78 |
79 | public abstract class BasicNode
80 | {
81 | public string ObjectIdentifier { get; set; }
82 | public IEnumerable Aces { get; set; }
83 | public Boolean IsDeleted { get; set; }
84 | public Boolean IsACLProtected { get; set; } = false;
85 |
86 | }
87 |
88 | public abstract class Node : BasicNode
89 | {
90 | public string PrimaryGroupSID { get; set; }
91 | public string[] AllowedToDelegate { get; set; } = Array.Empty();
92 | public string[] HasSIDHistory { get; set; } = Array.Empty();
93 | }
94 |
95 | public class ComputerNode : Node
96 | {
97 | public List AllowedToAct { get; set; } = new List();
98 | public APIResult LocalAdmins { get; set; }
99 | public APIResult PSRemoteUsers { get; set; }
100 | public ComputerProperties Properties { get; set; }
101 | public APIResult RemoteDesktopUsers { get; set; }
102 | public APIResult DcomUsers { get; set; }
103 | public APIResult PrivilegedSessions { get; set; }
104 | public APIResult Sessions { get; set; }
105 | public APIResult RegistrySessions { get; set; }
106 | }
107 |
108 |
109 |
110 | public class UserNode : Node
111 | {
112 | public string[] SPNTargets { get; set; }
113 | public UserProperties Properties { get; set; }
114 | }
115 |
116 | public class GroupNode : BasicNode
117 | {
118 | public TypedPrincipal[] Members { get; set; }
119 | public GroupProperties Properties { get; set; }
120 | }
121 |
122 | public class DomainNode : BasicNode
123 | {
124 | public TypedPrincipal[] ChildObjects { get; set; } = Array.Empty();
125 | public DomainProperties Properties { get; set; }
126 | public DomainTrust[] Trusts { get; set; } = Array.Empty();
127 | public GPLink[] Links { get; set; } = Array.Empty();
128 | public ResultingGPOChanges GPOChanges { get; set; } = new ResultingGPOChanges();
129 |
130 | }
131 |
132 | public class GPONode : BasicNode
133 | {
134 | public GPOProperties Properties { get; set; }
135 | }
136 |
137 | public class OUNode : BasicNode
138 | {
139 | public ResultingGPOChanges GPOChanges { get; set; } = new ResultingGPOChanges();
140 | public OUProperties Properties { get; set; }
141 | public GPLink[] Links { get; set; }
142 | public TypedPrincipal[] ChildObjects { get; set; }
143 | }
144 |
145 | public class ContainerNode : BasicNode
146 | {
147 | public ContainerProperties Properties { get; set; }
148 | public TypedPrincipal[] ChildObjects { get; set; }
149 | }
150 |
151 | public class DomainTrust
152 | {
153 | public string TargetDomainSid { get; set; }
154 | public string TargetDomainName { get; set; }
155 | public bool IsTransitive { get; set; }
156 | public bool SidFilteringEnabled { get; set; }
157 | public int TrustDirection { get; set; }
158 | public int TrustType { get; set; }
159 | }
160 |
161 | public abstract class BasicProperties
162 | {
163 | public string name { get; set; }
164 | public string domainsid { get; set; }
165 | public string domain { get; set; }
166 | public string distinguishedname { get; set; }
167 | public Boolean highvalue { get; set; } = false;
168 |
169 | }
170 |
171 | public abstract class Properties : BasicProperties
172 | {
173 | public string description { get; set; }
174 | public Boolean unconstraineddelegation { get; set; }
175 | public Boolean enabled { get; set; }
176 | public Boolean trustedtoauth { get; set; }
177 | public long lastlogon { get; set; }
178 | public long lastlogontimestamp { get; set; }
179 | public long pwdlastset { get; set; }
180 | public long whencreated { get; set; }
181 | public string[] sidhistory { get; set; } = Array.Empty();
182 | public string[] serviceprincipalnames { get; set; } = Array.Empty();
183 | }
184 |
185 | public class ComputerProperties : Properties
186 | {
187 | public Boolean haslaps { get; set; }
188 |
189 | public string operatingsystem { get; set; }
190 | public string samaccountname { get; set; }
191 | }
192 |
193 | public class UserProperties : Properties
194 | {
195 | public string samaccountname { get; set; }
196 | public Boolean passwordnotreqd { get; set; }
197 | public Boolean dontreqpreauth { get; set; }
198 | public Boolean pwdneverexpires { get; set; }
199 | public Boolean sensitive { get; set; }
200 | public Boolean hasspn { get; set; } = false;
201 | public Boolean admincount { get; set; }
202 | public string displayName { get; set; }
203 | public string email { get; set; }
204 | public string title { get; set; }
205 | public string homedirectory { get; set; }
206 | public string userpassword { get; set; }
207 | public string unixpassword { get; set; }
208 | public string unicodepassword { get; set; }
209 | public string sfupassword { get; set; }
210 | public string logonscript { get; set; }
211 | }
212 |
213 | public class GroupProperties : BasicProperties
214 | {
215 | public string samaccountname { get; set; }
216 | public string description { get; set; }
217 | public Boolean admincount { get; set; }
218 | public long whencreated { get; set; }
219 | }
220 |
221 |
222 | public class DomainProperties : BasicProperties
223 | {
224 | public string description { get; set; }
225 | public string functionallevel { get; set; }
226 | public long whencreated { get; set; }
227 | }
228 |
229 | public class GPOProperties : BasicProperties
230 | {
231 | public string description { get; set; }
232 | public long whencreated { get; set; }
233 | public string gpcpath { get; set; }
234 | }
235 |
236 | public class OUProperties : BasicProperties
237 | {
238 | public string description { get; set; }
239 | public long whencreated { get; set; }
240 | public Boolean blocksinheritance { get; set; } = false;
241 | }
242 |
243 | public class ContainerProperties : BasicProperties
244 | {
245 |
246 | }
247 | public class Ace
248 | {
249 | public string RightName { get; set; }
250 | public Boolean IsInherited { get; set; }
251 | public string PrincipalSID { get; set; }
252 | [JsonConverter(typeof(StringEnumConverter))]
253 | public Label PrincipalType { get; set; }
254 | }
255 |
256 |
257 |
258 |
259 | }
260 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.tlog
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
298 | *.vbp
299 |
300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
301 | *.dsw
302 | *.dsp
303 |
304 | # Visual Studio 6 technical files
305 | *.ncb
306 | *.aps
307 |
308 | # Visual Studio LightSwitch build output
309 | **/*.HTMLClient/GeneratedArtifacts
310 | **/*.DesktopClient/GeneratedArtifacts
311 | **/*.DesktopClient/ModelManifest.xml
312 | **/*.Server/GeneratedArtifacts
313 | **/*.Server/ModelManifest.xml
314 | _Pvt_Extensions
315 |
316 | # Paket dependency manager
317 | .paket/paket.exe
318 | paket-files/
319 |
320 | # FAKE - F# Make
321 | .fake/
322 |
323 | # CodeRush personal settings
324 | .cr/personal
325 |
326 | # Python Tools for Visual Studio (PTVS)
327 | __pycache__/
328 | *.pyc
329 |
330 | # Cake - Uncomment if you are using it
331 | # tools/**
332 | # !tools/packages.config
333 |
334 | # Tabs Studio
335 | *.tss
336 |
337 | # Telerik's JustMock configuration file
338 | *.jmconfig
339 |
340 | # BizTalk build output
341 | *.btp.cs
342 | *.btm.cs
343 | *.odx.cs
344 | *.xsd.cs
345 |
346 | # OpenCover UI analysis results
347 | OpenCover/
348 |
349 | # Azure Stream Analytics local run output
350 | ASALocalRun/
351 |
352 | # MSBuild Binary and Structured Log
353 | *.binlog
354 |
355 | # NVidia Nsight GPU debugger configuration file
356 | *.nvuser
357 |
358 | # MFractors (Xamarin productivity tool) working folder
359 | .mfractor/
360 |
361 | # Local History for Visual Studio
362 | .localhistory/
363 |
364 | # Visual Studio History (VSHistory) files
365 | .vshistory/
366 |
367 | # BeatPulse healthcheck temp database
368 | healthchecksdb
369 |
370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
371 | MigrationBackup/
372 |
373 | # Ionide (cross platform F# VS Code tools) working folder
374 | .ionide/
375 |
376 | # Fody - auto-generated XML schema
377 | FodyWeavers.xsd
378 |
379 | # VS Code files for those working on multiple tools
380 | .vscode/*
381 | !.vscode/settings.json
382 | !.vscode/tasks.json
383 | !.vscode/launch.json
384 | !.vscode/extensions.json
385 | *.code-workspace
386 |
387 | # Local History for Visual Studio Code
388 | .history/
389 |
390 | # Windows Installer files from build outputs
391 | *.cab
392 | *.msi
393 | *.msix
394 | *.msm
395 | *.msp
396 |
397 | # JetBrains Rider
398 | *.sln.iml
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Description
4 |
5 | SOAPHound is a .NET data collector tool, which collects Active Directory data via the Active Directory Web Services (ADWS) protocol.
6 |
7 | SOAPHound is an alternative to a number of open source security tools which are commonly used to extract Active Directory data via LDAP protocol. SOAPHound is able to extract the same information without directly communicating to the LDAP server. Instead, LDAP queries are wrapped within a series of SOAP messages, which are sent to the ADWS server using NET TCP Binding communication channel. Following, ADWS server unwraps the LDAP queries and forwards them to the LDAP server running on the same Domain Controller. As a result, LDAP traffic is not sent via the wire and therefore is not easily detected by common monitoring tools.
8 |
9 | Note that this is a proof of concept tool and is not intended for production use. The tool is provided as is, without warranty of any kind.
10 |
11 | For additional details on the SOAPHound tool, please refer to the following blog post: [SOAPHound — tool to collect Active Directory data via ADWS](https://falconforce.nl/soaphound-tool-to-collect-active-directory-data-via-adws/).
12 |
13 | # Usage
14 |
15 | The `--help` command line argument can be used to display the following usage information:
16 |
17 | ```
18 | SOAPHound
19 | Copyright (c) 2024 FalconForce
20 |
21 | Connection and authentication options:
22 | --user Username to use for ADWS Connection. Format: domain\user or user@domain
23 | --password Password to use for ADWS Connection
24 | --domain Specify domain for enumeration
25 | --dc Domain Controller to connect to
26 |
27 | Supported collection methods:
28 | --buildcache (Default: false) Only build cache and not perform further actions
29 | --dnsdump (Default: false) Dump AD Integrated DNS data
30 | --certdump (Default: false) Dump AD Certificate Services data
31 | --bhdump (Default: false) Dump BH data
32 |
33 | Output options:
34 | -o, --outputdirectory Folder to output files to (full path needed)
35 | -c, --cachefilename Filename for the cache file (full path needed)
36 |
37 | Splitting options:
38 | -a, --autosplit (Default: false) Enable AutoSplit mode: automatically split object retrieval on two depth levels based on defined trheshold
39 | -t, --threshold (Default: 0) AutoSplit mode: Define split threshold based on number of objects per starting letter
40 |
41 | Miscellaneous options:
42 | --nolaps (Default: false) Do not request LAPS related information
43 | --showstats Show stats of local cache file
44 | --logfile Create log file
45 | --help Display this help screen.
46 | ```
47 |
48 | # Connection and authentication options
49 |
50 | ## Authentication
51 |
52 | SOAPHound supports the following authentication methods:
53 | * Using the existing authentication token of the current user. This is the default option if no username and password are supplied.
54 | * Supplying a username and password on the command line.
55 |
56 | ## Domain Connection Information
57 |
58 | When SOAPHound runs in a domain-joined machine, it will automatically attempt to connect to the Domain Controller of the domain the machine is joined to.
59 | This can be overridden by supplying the `--dc` and `--domain` command line arguments.
60 |
61 | # Supported collection methods
62 |
63 | One of the following collection methods must be specified:
64 | * `--buildcache`: Only build cache and not perform further actions
65 | * `--bhdump`: Dump BloodHound data
66 | * `--certdump`: Dump AD Certificate Services (ADCS) data
67 | * `--dnsdump`: Dump AD Integrated DNS data
68 |
69 |
70 | ## Building the cache
71 |
72 | SOAPHound is able to generate a cache file that contains basic information about all domain objects, such as Security Identifier (SID), Distinguished Name (DN) and ObjectClass.
73 | This cache file is required for BloodHound related data collection (i.e. the `--bhdump` and `--certdump` collection methods), since it is used when crafting the trust relationships between objects via the relevant Access Control Entries (ACEs).
74 |
75 | An example command to build the cache file is:
76 |
77 | ```
78 | SOAPHound.exe --buildcache -c c:\temp\cache.txt
79 | ```
80 |
81 | This will generate a cache file in the `c:\temp` folder. The cache file is a JSON formatted mapping of basic information about all domain objects.
82 | To view some statistics about the cache file (i.e. number of domain objects starting with each letter), you can use the `--showstats` command line argument:
83 |
84 | ```
85 | SOAPHound.exe --showstats -c c:\temp\cache.txt
86 | ```
87 |
88 | ## Collecting BloodHound Data
89 |
90 | After the cache file has been generated, you can use the `--bhdump` collection method to collect data from the domain that can be imported into BloodHound.
91 |
92 | An example command to collect BloodHound data is (note that this references the cache file generated in the previous step):
93 |
94 | ```
95 | SOAPHound.exe -c c:\temp\cache.txt --bhdump -o c:\temp\bloodhound-output
96 | ```
97 |
98 | If the targeted domain does not use LAPS, you can use the `--nolaps` command line argument to skip the LAPS related data collection.
99 |
100 | This command will generate the `c:\temp\bloodhound-output` folder and produce a number of JSON files that can be imported into BloodHound.
101 | The JSON files contain the collected Users, Groups, Computers, Domains, GPOs and Containers, including their relationships. SOAPHound is compatible with Bloodhound version 4.
102 |
103 | ### Dealing with large domains
104 |
105 | If you are dealing with a large domain, you may run into issues with the amount of data that can be retrieved in a single request.
106 | To deal with this, SOAPHound supports the `--autosplit` and `--threshold` command line arguments.
107 |
108 | The `--autosplit` command line argument enables the AutoSplit mode, which will automatically split object retrieval on two depth levels based on a defined threshold.
109 | The `--threshold` command line argument defines the split threshold based on the number of objects per starting letter.
110 |
111 | An example command to collect BloodHound data in AutoSplit mode is:
112 |
113 | ```
114 | SOAPHound.exe -c c:\temp\cache.txt --bhdump -o c:\temp\bloodhound-output --autosplit --threshold 1000
115 | ```
116 |
117 | This will generate the output in batches of a maximum of 1000 objects per starting letter.
118 | If there are more than 1000 objects for a single starting letter, SOAPHound will use two depth levels to retrieve the objects.
119 | This will result in larger number of queries, each one returning a maximum of 1000 objects.
120 |
121 | For example if there are 2000 objects starting with the letter `a`, SOAPHound will retrieve objects
122 | starting with `aa`, `ab`, `ac`, etc., each in a separate query to avoid timeouts.
123 |
124 | ## Collecting ADCS Data
125 |
126 | After the cache file has been generated, you can use the `--certdump` collection method to collect ADCS data from the domain that can be imported into BloodHound.
127 | This collection method does not support the `--autosplit` and `--threshold` command line arguments.
128 |
129 | An example command to collect ADCS data is (note that this references the cache file generated in previous step):
130 |
131 | ```
132 | SOAPHound.exe -c c:\temp\cache.txt --certdump -o c:\temp\bloodhound-output
133 | ```
134 |
135 | This command will generate the `c:\temp\bloodhound-output` folder and produce two JSON files that can be imported into BloodHound, containing information about the Certificate Authorities (CA) and Certificate Templates. SOAPHound is compatible with Bloodhound version 4 and ADCS data are classified as GPO objects in Bloodhound.
136 |
137 | ## Collecting AD Integrated DNS Data
138 |
139 | Apart from BloodHound data, SOAPHound can also be used to collect AD Integrated DNS data. This does not require a cache file and does not support the `--autosplit` and `--threshold` command line arguments.
140 |
141 | An example command to collect AD Integrated DNS data is:
142 |
143 | ```
144 | SOAPHound.exe --dnsdump -o c:\temp\dns-output
145 | ```
146 |
147 | This command will generate a file namely DNS.txt in the `c:\temp\dns-output` folder that contains a dump of all the AD Integrated DNS data.
148 |
149 | # Acknowledgements
150 |
151 | This tool is based on the work of the following open source projects:
152 | * [SharpHound](https://github.com/BloodHoundAD/SharpHound/tree/dev)
153 | * [StandIn](https://github.com/FuzzySecurity/StandIn)
154 | * [Certify](https://github.com/GhostPack/Certify)
155 |
156 | Another big thanks to [PingCastle](https://github.com/vletoux/pingcastle) for their reference implementation of the ADWS protocol.
157 | While we do not use their code directly, it was a great help in understanding the protocol and realizing the potential of the ADWS protocol.
158 |
--------------------------------------------------------------------------------
/Enums/WellKnownPrincipal.cs:
--------------------------------------------------------------------------------
1 | using SOAPHound.Enums;
2 | using SOAPHound.OutputTypes;
3 |
4 | namespace SOAPHound.Enums
5 | {
6 | public static class WellKnownPrincipal
7 | {
8 | ///
9 | /// Gets the principal associated with a well known SID
10 | ///
11 | ///
12 | ///
13 | /// True if SID matches a well known principal, false otherwise
14 | public static bool GetWellKnownPrincipal(string sid, out TypedPrincipal commonPrincipal)
15 | {
16 | switch (sid)
17 | {
18 | case "S-1-0":
19 | commonPrincipal = new TypedPrincipal("Null Authority", Label.User);
20 | break;
21 | case "S-1-0-0":
22 | commonPrincipal = new TypedPrincipal("Nobody", Label.User);
23 | break;
24 | case "S-1-1":
25 | commonPrincipal = new TypedPrincipal("World Authority", Label.User);
26 | break;
27 | case "S-1-1-0":
28 | commonPrincipal = new TypedPrincipal("Everyone", Label.Group);
29 | break;
30 | case "S-1-2":
31 | commonPrincipal = new TypedPrincipal("Local Authority", Label.User);
32 | break;
33 | case "S-1-2-0":
34 | commonPrincipal = new TypedPrincipal("Local", Label.Group);
35 | break;
36 | case "S-1-2-1":
37 | commonPrincipal = new TypedPrincipal("Console Logon", Label.Group);
38 | break;
39 | case "S-1-3":
40 | commonPrincipal = new TypedPrincipal("Creator Authority", Label.User);
41 | break;
42 | case "S-1-3-0":
43 | commonPrincipal = new TypedPrincipal("Creator Owner", Label.User);
44 | break;
45 | case "S-1-3-1":
46 | commonPrincipal = new TypedPrincipal("Creator Label.Group", Label.Group);
47 | break;
48 | case "S-1-3-2":
49 | commonPrincipal = new TypedPrincipal("Creator Owner Server", Label.Computer);
50 | break;
51 | case "S-1-3-3":
52 | commonPrincipal = new TypedPrincipal("Creator Label.Group Server", Label.Computer);
53 | break;
54 | case "S-1-3-4":
55 | commonPrincipal = new TypedPrincipal("Owner Rights", Label.Group);
56 | break;
57 | case "S-1-4":
58 | commonPrincipal = new TypedPrincipal("Non-unique Authority", Label.User);
59 | break;
60 | case "S-1-5":
61 | commonPrincipal = new TypedPrincipal("NT Authority", Label.User);
62 | break;
63 | case "S-1-5-1":
64 | commonPrincipal = new TypedPrincipal("Dialup", Label.Group);
65 | break;
66 | case "S-1-5-2":
67 | commonPrincipal = new TypedPrincipal("Network", Label.Group);
68 | break;
69 | case "S-1-5-3":
70 | commonPrincipal = new TypedPrincipal("Batch", Label.Group);
71 | break;
72 | case "S-1-5-4":
73 | commonPrincipal = new TypedPrincipal("Interactive", Label.Group);
74 | break;
75 | case "S-1-5-6":
76 | commonPrincipal = new TypedPrincipal("Service", Label.Group);
77 | break;
78 | case "S-1-5-7":
79 | commonPrincipal = new TypedPrincipal("Anonymous", Label.Group);
80 | break;
81 | case "S-1-5-8":
82 | commonPrincipal = new TypedPrincipal("Proxy", Label.Group);
83 | break;
84 | case "S-1-5-9":
85 | commonPrincipal = new TypedPrincipal("Enterprise Domain Controllers", Label.Group);
86 | break;
87 | case "S-1-5-10":
88 | commonPrincipal = new TypedPrincipal("Principal Self", Label.User);
89 | break;
90 | case "S-1-5-11":
91 | commonPrincipal = new TypedPrincipal("Authenticated Label.Users", Label.Group);
92 | break;
93 | case "S-1-5-12":
94 | commonPrincipal = new TypedPrincipal("Restricted Code", Label.Group);
95 | break;
96 | case "S-1-5-13":
97 | commonPrincipal = new TypedPrincipal("Terminal Server Label.Users", Label.Group);
98 | break;
99 | case "S-1-5-14":
100 | commonPrincipal = new TypedPrincipal("Remote Interactive Logon", Label.Group);
101 | break;
102 | case "S-1-5-15":
103 | commonPrincipal = new TypedPrincipal("This Organization ", Label.Group);
104 | break;
105 | case "S-1-5-17":
106 | commonPrincipal = new TypedPrincipal("This Organization ", Label.Group);
107 | break;
108 | case "S-1-5-18":
109 | commonPrincipal = new TypedPrincipal("Local System", Label.User);
110 | break;
111 | case "S-1-5-19":
112 | commonPrincipal = new TypedPrincipal("NT Authority", Label.User);
113 | break;
114 | case "S-1-5-20":
115 | commonPrincipal = new TypedPrincipal("NT Authority", Label.User);
116 | break;
117 | case "S-1-5-113":
118 | commonPrincipal = new TypedPrincipal("Local Account", Label.User);
119 | break;
120 | case "S-1-5-114":
121 | commonPrincipal = new TypedPrincipal("Local Account and Member of Administrators Label.Group", Label.User);
122 | break;
123 | case "S-1-5-80-0":
124 | commonPrincipal = new TypedPrincipal("All Services ", Label.Group);
125 | break;
126 | case "S-1-5-32-544":
127 | commonPrincipal = new TypedPrincipal("Administrators", Label.Group);
128 | break;
129 | case "S-1-5-32-545":
130 | commonPrincipal = new TypedPrincipal("Label.Users", Label.Group);
131 | break;
132 | case "S-1-5-32-546":
133 | commonPrincipal = new TypedPrincipal("Guests", Label.Group);
134 | break;
135 | case "S-1-5-32-547":
136 | commonPrincipal = new TypedPrincipal("Power Label.Users", Label.Group);
137 | break;
138 | case "S-1-5-32-548":
139 | commonPrincipal = new TypedPrincipal("Account Operators", Label.Group);
140 | break;
141 | case "S-1-5-32-549":
142 | commonPrincipal = new TypedPrincipal("Server Operators", Label.Group);
143 | break;
144 | case "S-1-5-32-550":
145 | commonPrincipal = new TypedPrincipal("Print Operators", Label.Group);
146 | break;
147 | case "S-1-5-32-551":
148 | commonPrincipal = new TypedPrincipal("Backup Operators", Label.Group);
149 | break;
150 | case "S-1-5-32-552":
151 | commonPrincipal = new TypedPrincipal("Replicators", Label.Group);
152 | break;
153 | case "S-1-5-32-554":
154 | commonPrincipal = new TypedPrincipal("Pre-Windows 2000 Compatible Access", Label.Group);
155 | break;
156 | case "S-1-5-32-555":
157 | commonPrincipal = new TypedPrincipal("Remote Desktop Label.Users", Label.Group);
158 | break;
159 | case "S-1-5-32-556":
160 | commonPrincipal = new TypedPrincipal("Network Configuration Operators", Label.Group);
161 | break;
162 | case "S-1-5-32-557":
163 | commonPrincipal = new TypedPrincipal("Incoming Forest Trust Builders", Label.Group);
164 | break;
165 | case "S-1-5-32-558":
166 | commonPrincipal = new TypedPrincipal("Performance Monitor Label.Users", Label.Group);
167 | break;
168 | case "S-1-5-32-559":
169 | commonPrincipal = new TypedPrincipal("Performance Log Label.Users", Label.Group);
170 | break;
171 | case "S-1-5-32-560":
172 | commonPrincipal = new TypedPrincipal("Windows Authorization Access Label.Group", Label.Group);
173 | break;
174 | case "S-1-5-32-561":
175 | commonPrincipal = new TypedPrincipal("Terminal Server License Servers", Label.Group);
176 | break;
177 | case "S-1-5-32-562":
178 | commonPrincipal = new TypedPrincipal("Distributed COM Label.Users", Label.Group);
179 | break;
180 | case "S-1-5-32-568":
181 | commonPrincipal = new TypedPrincipal("IIS_IUSRS", Label.Group);
182 | break;
183 | case "S-1-5-32-569":
184 | commonPrincipal = new TypedPrincipal("Cryptographic Operators", Label.Group);
185 | break;
186 | case "S-1-5-32-573":
187 | commonPrincipal = new TypedPrincipal("Event Log Readers", Label.Group);
188 | break;
189 | case "S-1-5-32-574":
190 | commonPrincipal = new TypedPrincipal("Certificate Service DCOM Access", Label.Group);
191 | break;
192 | case "S-1-5-32-575":
193 | commonPrincipal = new TypedPrincipal("RDS Remote Access Servers", Label.Group);
194 | break;
195 | case "S-1-5-32-576":
196 | commonPrincipal = new TypedPrincipal("RDS Endpoint Servers", Label.Group);
197 | break;
198 | case "S-1-5-32-577":
199 | commonPrincipal = new TypedPrincipal("RDS Management Servers", Label.Group);
200 | break;
201 | case "S-1-5-32-578":
202 | commonPrincipal = new TypedPrincipal("Hyper-V Administrators", Label.Group);
203 | break;
204 | case "S-1-5-32-579":
205 | commonPrincipal = new TypedPrincipal("Access Control Assistance Operators", Label.Group);
206 | break;
207 | case "S-1-5-32-580":
208 | commonPrincipal = new TypedPrincipal("Remote Management Label.Users", Label.Group);
209 | break;
210 | default:
211 | commonPrincipal = null;
212 | break;
213 |
214 | }
215 |
216 | return commonPrincipal != null;
217 | }
218 | }
219 | }
--------------------------------------------------------------------------------
/ADWSUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using SOAPHound.Enums;
7 | using SOAPHound.ADWS;
8 | using SOAPHound.Processors;
9 | using SOAPHound.OutputTypes;
10 | using System.Net;
11 | using System.Text.RegularExpressions;
12 | using System.Diagnostics;
13 |
14 | namespace SOAPHound
15 | {
16 | static class ADWSUtils
17 | {
18 | public static String Server { get; set; }
19 | public static int Port { get; set; }
20 | public static NetworkCredential Credential { get; set; }
21 | public static Boolean nolaps { get; set; }
22 |
23 | private static readonly Regex DCReplaceRegex = new Regex("DC=", RegexOptions.IgnoreCase | RegexOptions.Compiled);
24 |
25 | public static List GetObjects(string label)
26 | {
27 | string ldapquery = "";
28 | string[] properties = new string[] { };
29 | string banner = "";
30 | string ldapbase = "";
31 |
32 | switch (label)
33 | {
34 | case "dns":
35 | banner = "Gathering DNS data";
36 | ldapquery = "(&(ObjectClass=dnsNode))";
37 | properties = new string[] { "Name", "dnsRecord" };
38 | ldapbase = "CN=MicrosoftDNS,DC=DomainDnsZones,";
39 | break;
40 | case "cache":
41 | banner = "Generating cache";
42 | ldapquery = "(!soaphound=*)";
43 | properties = new string[] { "objectSid", "objectGUID", "distinguishedName" };
44 | break;
45 | case "pkicache":
46 | banner = "Gathering PKI cache";
47 | ldapquery = "(!soaphound=*)";
48 | properties = new string[] { "name", "certificateTemplates" };
49 | ldapbase = "CN=Configuration,";
50 | break;
51 | case "pkidata":
52 | banner = "Gathering PKI data";
53 | ldapquery = "(!soaphound=*)";
54 | properties = new string[] { "name", "displayName", "nTSecurityDescriptor", "objectGUID", "dNSHostName", "nTSecurityDescriptor", "certificateTemplates", "cACertificate", "msPKI-Minimal-Key-Size", "msPKI-Certificate-Name-Flag", "msPKI-Enrollment-Flag", "msPKI-Private-Key-Flag", "pKIExtendedKeyUsage", "pKIOverlapPeriod", "pKIExpirationPeriod" };
55 | ldapbase = "CN=Configuration,";
56 | break;
57 | case "ad":
58 | banner = "Gathering AD data";
59 | ldapquery = "(!soaphound=*)";
60 | if (nolaps)
61 | {
62 | properties = new string[] { "name", "sAMAccountName", "cn", "dNSHostName", "objectSid", "objectGUID", "primaryGroupID", "distinguishedName", "lastLogonTimestamp", "pwdLastSet", "servicePrincipalName", "description", "operatingSystem", "sIDHistory", "nTSecurityDescriptor", "userAccountControl", "whenCreated", "lastLogon", "displayName", "title", "homeDirectory", "userPassword", "unixUserPassword", "scriptPath", "adminCount", "member", "msDS-Behavior-Version", "msDS-AllowedToDelegateTo", "gPCFileSysPath", "gPLink", "gPOptions" };
63 | }
64 | else
65 | {
66 | properties = new string[] { "name", "sAMAccountName", "cn", "dNSHostName", "objectSid", "objectGUID", "primaryGroupID", "distinguishedName", "lastLogonTimestamp", "pwdLastSet", "servicePrincipalName", "description", "operatingSystem", "sIDHistory", "nTSecurityDescriptor", "userAccountControl", "whenCreated", "lastLogon", "ms-MCS-AdmPwdExpirationTime", "displayName", "title", "homeDirectory", "userPassword", "unixUserPassword", "scriptPath", "adminCount", "member", "msDS-Behavior-Version", "msDS-AllowedToDelegateTo", "gPCFileSysPath", "gPLink", "gPOptions" };
67 |
68 | }
69 | break;
70 | case "domaintrusts":
71 | banner = "Gathering DomainTrusts data";
72 | ldapquery = "(trustType=*)";
73 | properties = new string[] { "trustAttributes", "trustDirection", "name", "securityIdentifier" };
74 | break;
75 | case "domains":
76 | banner = "Gathering Domains data";
77 | ldapquery = "(ms-DS-MachineAccountQuota=*)";
78 | properties = new string[] { "name", "sAMAccountName", "cn", "dNSHostName", "objectSid", "objectGUID", "primaryGroupID", "distinguishedName", "lastLogonTimestamp", "pwdLastSet", "servicePrincipalName", "description", "operatingSystem", "sIDHistory", "nTSecurityDescriptor", "userAccountControl", "whenCreated", "lastLogon", "displayName", "title", "homeDirectory", "userPassword", "unixUserPassword", "scriptPath", "adminCount", "member", "msDS-Behavior-Version", "msDS-AllowedToDelegateTo", "gPCFileSysPath", "gPLink", "gPOptions" };
79 | break;
80 | case "nonchars":
81 | banner = "Gathering non alphanumeric objects";
82 | ldapquery = "(&(cn=*)(!(cn=a*))(!(cn=b*))(!(cn=c*))(!(cn=d*))(!(cn=e*))(!(cn=f*))(!(cn=g*))(!(cn=h*))(!(cn=i*))(!(cn=j*))(!(cn=k*))(!(cn=l*))(!(cn=m*))(!(cn=n*))(!(cn=o*))(!(cn=p*))(!(cn=q*))(!(cn=r*))(!(cn=s*))(!(cn=t*))(!(cn=u*))(!(cn=v*))(!(cn=w*))(!(cn=x*))(!(cn=y*))(!(cn=z*))(!(cn=0*))(!(cn=1*))(!(cn=2*))(!(cn=3*))(!(cn=4*))(!(cn=5*))(!(cn=6*))(!(cn=7*))(!(cn=8*))(!(cn=9*)))";
83 | if (nolaps)
84 | {
85 | properties = new string[] { "name", "sAMAccountName", "cn", "dNSHostName", "objectSid", "objectGUID", "primaryGroupID", "distinguishedName", "lastLogonTimestamp", "pwdLastSet", "servicePrincipalName", "description", "operatingSystem", "sIDHistory", "nTSecurityDescriptor", "userAccountControl", "whenCreated", "lastLogon", "displayName", "title", "homeDirectory", "userPassword", "unixUserPassword", "scriptPath", "adminCount", "member", "msDS-Behavior-Version", "msDS-AllowedToDelegateTo", "gPCFileSysPath", "gPLink", "gPOptions" };
86 | }
87 | else
88 | {
89 | properties = new string[] { "name", "sAMAccountName", "cn", "dNSHostName", "objectSid", "objectGUID", "primaryGroupID", "distinguishedName", "lastLogonTimestamp", "pwdLastSet", "servicePrincipalName", "description", "operatingSystem", "sIDHistory", "nTSecurityDescriptor", "userAccountControl", "whenCreated", "lastLogon", "ms-MCS-AdmPwdExpirationTime", "displayName", "title", "homeDirectory", "userPassword", "unixUserPassword", "scriptPath", "adminCount", "member", "msDS-Behavior-Version", "msDS-AllowedToDelegateTo", "gPCFileSysPath", "gPLink", "gPOptions" };
90 |
91 | }
92 | break;
93 | default:
94 | banner = "Gathering autosplit data: " + label;
95 | ldapquery = label;
96 | if (nolaps)
97 | {
98 | properties = new string[] { "name", "sAMAccountName", "cn", "dNSHostName", "objectSid", "objectGUID", "primaryGroupID", "distinguishedName", "lastLogonTimestamp", "pwdLastSet", "servicePrincipalName", "description", "operatingSystem", "sIDHistory", "nTSecurityDescriptor", "userAccountControl", "whenCreated", "lastLogon", "displayName", "title", "homeDirectory", "userPassword", "unixUserPassword", "scriptPath", "adminCount", "member", "msDS-Behavior-Version", "msDS-AllowedToDelegateTo", "gPCFileSysPath", "gPLink", "gPOptions" };
99 | }
100 | else
101 | {
102 | properties = new string[] { "name", "sAMAccountName", "cn", "dNSHostName", "objectSid", "objectGUID", "primaryGroupID", "distinguishedName", "lastLogonTimestamp", "pwdLastSet", "servicePrincipalName", "description", "operatingSystem", "sIDHistory", "nTSecurityDescriptor", "userAccountControl", "whenCreated", "lastLogon", "ms-MCS-AdmPwdExpirationTime", "displayName", "title", "homeDirectory", "userPassword", "unixUserPassword", "scriptPath", "adminCount", "member", "msDS-Behavior-Version", "msDS-AllowedToDelegateTo", "gPCFileSysPath", "gPLink", "gPOptions" };
103 |
104 | }
105 | break;
106 | }
107 | Console.WriteLine("-------------");
108 | Console.WriteLine(banner);
109 | var AWDSConnection = new ADWSConnector(Server, Credential);
110 | ADInfo domainInfo = AWDSConnection.GetADInfo();
111 |
112 | string domainName = domainInfo.DomainName;
113 | ldapbase += domainInfo.DefaultNamingContext;
114 | List adobjects = AWDSConnection.Enumerate(ldapbase, ldapquery, new List(properties));
115 |
116 | Console.WriteLine("ADWS request with ldapbase (" + ldapbase + "), ldapquery: " + ldapquery + " and ldapproperties: " + "[{0}]", string.Join(", ", properties));
117 | Console.WriteLine(banner + " complete");
118 | return adobjects;
119 | }
120 |
121 | public static TypedPrincipal ResolveIDAndType(string id)
122 | {
123 | //This is a duplicated SID object which is weird and makes things unhappy. Throw it out
124 | if (id.Contains("0ACNF"))
125 | return null;
126 |
127 | if (WellKnownPrincipal.GetWellKnownPrincipal(id, out var principal))
128 | return principal;
129 |
130 | var type = LookupSidType(id);
131 | return new TypedPrincipal(id, type);
132 | }
133 |
134 | public static Label LookupSidType(string sid)
135 | {
136 | if (Cache.GetIDType(sid, out var type))
137 | return type;
138 | else
139 | return Label.Base;
140 |
141 | }
142 |
143 | public static Label ClasstoLabel(string Class)
144 | {
145 | if (Class == "group")
146 | return Label.Group;
147 |
148 | if (Class == "user" || Class == "msds-managedserviceaccount" || Class == "msds-groupmanagedserviceaccount")
149 | return Label.User;
150 |
151 | if (Class == "computer")
152 | return Label.Computer;
153 |
154 | if (Class == "grouppolicycontainer")
155 | return Label.GPO;
156 |
157 | if (Class == "container")
158 | return Label.Container;
159 |
160 | if (Class == "organizationalunit")
161 | return Label.OU;
162 |
163 | if (Class == "domain" || Class == "domaindns" || Class == "trusteddomain")
164 | return Label.Domain;
165 |
166 | return Label.Base;
167 | }
168 |
169 | public static TypedPrincipal ResolveDistinguishedName(string dn)
170 | {
171 | if (Cache.GetConvertedValue(dn, out var id) && Cache.GetIDType(id, out var type))
172 | return new TypedPrincipal
173 | {
174 | ObjectIdentifier = id,
175 | ObjectType = type
176 | };
177 | else
178 | return new TypedPrincipal
179 | {
180 | ObjectIdentifier = null,
181 | ObjectType = Label.Base
182 | };
183 | }
184 |
185 | public static string DistinguishedNameToDomain(string distinguishedName)
186 | {
187 | var idx = distinguishedName.IndexOf("DC=",
188 | StringComparison.CurrentCultureIgnoreCase);
189 | if (idx < 0)
190 | return null;
191 |
192 | var temp = distinguishedName.Substring(idx);
193 | temp = DCReplaceRegex.Replace(temp, "").Replace(",", ".").ToUpper();
194 | return temp;
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/Enums/PKI.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Converters;
3 | using Newtonsoft.Json.Serialization;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Text.RegularExpressions;
9 | using System.Threading.Tasks;
10 |
11 | /*
12 |
13 | This file includes code from Certify, which has the following license.
14 |
15 | Certify is provided under the 3-clause BSD license below.
16 |
17 | *************************************************************
18 |
19 | Copyright (c) 2021, Will Schroeder and Lee Christensen
20 | All rights reserved.
21 |
22 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
23 |
24 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
25 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
26 | The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
27 |
28 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 | */
30 |
31 | namespace SOAPHound.Enums
32 | {
33 | public class FlagCaseNamingStrategy : CamelCaseNamingStrategy
34 | {
35 | protected override string ResolvePropertyName(string name)
36 | {
37 | return (Regex.Replace(name.ToLower(), @"((^\w)|(\s|\p{P})\w)", match => match.Value.ToUpper())).Replace("_", "");
38 | }
39 | }
40 |
41 | [Flags]
42 | [JsonConverter(typeof(StringEnumConverter),typeof(FlagCaseNamingStrategy))]
43 | public enum msPKICertificateNameFlag : uint
44 | {
45 | ENROLLEE_SUPPLIES_SUBJECT = 0x00000001,
46 | ADD_EMAIL = 0x00000002,
47 | ADD_OBJ_GUID = 0x00000004,
48 | OLD_CERT_SUPPLIES_SUBJECT_AND_ALT_NAME = 0x00000008,
49 | ADD_DIRECTORY_PATH = 0x00000100,
50 | ENROLLEE_SUPPLIES_SUBJECT_ALT_NAME = 0x00010000,
51 | SUBJECT_ALT_REQUIRE_DOMAIN_DNS = 0x00400000,
52 | SUBJECT_ALT_REQUIRE_SPN = 0x00800000,
53 | SUBJECT_ALT_REQUIRE_DIRECTORY_GUID = 0x01000000,
54 | SUBJECT_ALT_REQUIRE_UPN = 0x02000000,
55 | SUBJECT_ALT_REQUIRE_EMAIL = 0x04000000,
56 | SUBJECT_ALT_REQUIRE_DNS = 0x08000000,
57 | SUBJECT_REQUIRE_DNS_AS_CN = 0x10000000,
58 | SUBJECT_REQUIRE_EMAIL = 0x20000000,
59 | SUBJECT_REQUIRE_COMMON_NAME = 0x40000000,
60 | SUBJECT_REQUIRE_DIRECTORY_PATH = 0x80000000,
61 | }
62 |
63 | [Flags]
64 | [JsonConverter(typeof(StringEnumConverter), typeof(FlagCaseNamingStrategy))]
65 | public enum msPKIPrivateKeyFlag : uint
66 | {
67 | REQUIRE_PRIVATE_KEY_ARCHIVAL = 0x00000001,
68 | EXPORTABLE_KEY = 0x00000010,
69 | STRONG_KEY_PROTECTION_REQUIRED = 0x00000020,
70 | REQUIRE_ALTERNATE_SIGNATURE_ALGORITHM = 0x00000040,
71 | REQUIRE_SAME_KEY_RENEWAL = 0x00000080,
72 | USE_LEGACY_PROVIDER = 0x00000100,
73 | ATTEST_NONE = 0x00000000,
74 | ATTEST_REQUIRED = 0x00002000,
75 | ATTEST_PREFERRED = 0x00001000,
76 | ATTESTATION_WITHOUT_POLICY = 0x00004000,
77 | EK_TRUST_ON_USE = 0x00000200,
78 | EK_VALIDATE_CERT = 0x00000400,
79 | EK_VALIDATE_KEY = 0x00000800,
80 | HELLO_LOGON_KEY = 0x00200000,
81 | }
82 |
83 | [Flags]
84 | [JsonConverter(typeof(StringEnumConverter), typeof(FlagCaseNamingStrategy))]
85 | public enum msPKIEnrollmentFlag : uint
86 | {
87 | NONE = 0x00000000,
88 | INCLUDE_SYMMETRIC_ALGORITHMS = 0x00000001,
89 | PEND_ALL_REQUESTS = 0x00000002,
90 | PUBLISH_TO_KRA_CONTAINER = 0x00000004,
91 | PUBLISH_TO_DS = 0x00000008,
92 | AUTO_ENROLLMENT_CHECK_USER_DS_CERTIFICATE = 0x00000010,
93 | AUTO_ENROLLMENT = 0x00000020,
94 | CT_FLAG_DOMAIN_AUTHENTICATION_NOT_REQUIRED = 0x80,
95 | PREVIOUS_APPROVAL_VALIDATE_REENROLLMENT = 0x00000040,
96 | USER_INTERACTION_REQUIRED = 0x00000100,
97 | ADD_TEMPLATE_NAME = 0x200,
98 | REMOVE_INVALID_CERTIFICATE_FROM_PERSONAL_STORE = 0x00000400,
99 | ALLOW_ENROLL_ON_BEHALF_OF = 0x00000800,
100 | ADD_OCSP_NOCHECK = 0x00001000,
101 | ENABLE_KEY_REUSE_ON_NT_TOKEN_KEYSET_STORAGE_FULL = 0x00002000,
102 | NOREVOCATIONINFOINISSUEDCERTS = 0x00004000,
103 | INCLUDE_BASIC_CONSTRAINTS_FOR_EE_CERTS = 0x00008000,
104 | ALLOW_PREVIOUS_APPROVAL_KEYBASEDRENEWAL_VALIDATE_REENROLLMENT = 0x00010000,
105 | ISSUANCE_POLICIES_FROM_REQUEST = 0x00020000,
106 | SKIP_AUTO_RENEWAL = 0x00040000,
107 | NO_SECURITY_EXTENSION = 0x00080000
108 | }
109 |
110 | public class msPKIExtendedKeyUsage
111 | {
112 | public string AnyPurpose = "2.5.29.37.0";
113 | public string ClientAuthentication = "1.3.6.1.5.5.7.3.2";
114 | public string PKINITClientAuthentication = "1.3.6.1.5.2.3.4";
115 | public string SmartcardLogon = "1.3.6.1.4.1.311.20.2.2";
116 | public string CertificateRequestAgent = "1.3.6.1.4.1.311.20.2.1";
117 | public string CertificateRequestAgentPolicy = "1.3.6.1.4.1.311.20.2.1";
118 | }
119 |
120 | [Flags]
121 | public enum CertificationAuthorityRights : uint
122 | {
123 | ManageCA = 1, // Administrator
124 | ManageCertificates = 2, // Officer
125 | Auditor = 4,
126 | Operator = 8,
127 | Read = 256,
128 | Enroll = 512,
129 | }
130 | public class OidConverter
131 | {
132 | static Dictionary OidLookup = new Dictionary();
133 |
134 | //Static constructor
135 | static OidConverter()
136 | {
137 | //https://www.pkisolutions.com/object-identifiers-oid-in-pki/
138 | OidLookup["1.3.6.1.4.1.311.76.6.1"] = "Windows Update";
139 | OidLookup["1.3.6.1.4.1.311.10.3.11"] = "Key Recovery";
140 | OidLookup["1.3.6.1.4.1.311.10.3.25"] = "Windows Third Party Application Component";
141 | OidLookup["1.3.6.1.4.1.311.21.6"] = "Key Recovery Agent";
142 | OidLookup["1.3.6.1.4.1.311.10.3.6"] = "Windows System Component Verification";
143 | OidLookup["1.3.6.1.4.1.311.61.4.1"] = "Early Launch Antimalware Drive";
144 | OidLookup["1.3.6.1.4.1.311.10.3.23"] = "Windows TCB Component";
145 | OidLookup["1.3.6.1.4.1.311.61.1.1"] = "Kernel Mode Code Signing";
146 | OidLookup["1.3.6.1.4.1.311.10.3.26"] = "Windows Software Extension Verification";
147 | OidLookup["2.23.133.8.3"] = "Attestation Identity Key Certificate";
148 | OidLookup["1.3.6.1.4.1.311.76.3.1"] = "Windows Store";
149 | OidLookup["1.3.6.1.4.1.311.10.6.1"] = "Key Pack Licenses";
150 | OidLookup["1.3.6.1.4.1.311.20.2.2"] = "Smart Card Logon";
151 | OidLookup["1.3.6.1.5.2.3.5"] = "KDC Authentication";
152 | OidLookup["1.3.6.1.5.5.7.3.7"] = "IP security use";
153 | OidLookup["1.3.6.1.4.1.311.10.3.8"] = "Embedded Windows System Component Verification";
154 | OidLookup["1.3.6.1.4.1.311.10.3.20"] = "Windows Kits Component";
155 | OidLookup["1.3.6.1.5.5.7.3.6"] = "IP security tunnel termination";
156 | OidLookup["1.3.6.1.4.1.311.10.3.5"] = "Windows Hardware Driver Verification";
157 | OidLookup["1.3.6.1.5.5.8.2.2"] = "IP security IKE intermediate";
158 | OidLookup["1.3.6.1.4.1.311.10.3.39"] = "Windows Hardware Driver Extended Verification";
159 | OidLookup["1.3.6.1.4.1.311.10.6.2"] = "License Server Verification";
160 | OidLookup["1.3.6.1.4.1.311.10.3.5.1"] = "Windows Hardware Driver Attested Verification";
161 | OidLookup["1.3.6.1.4.1.311.76.5.1"] = "Dynamic Code Generato";
162 | OidLookup["1.3.6.1.5.5.7.3.8"] = "Time Stamping";
163 | OidLookup["1.3.6.1.4.1.311.10.3.4.1"] = "File Recovery";
164 | OidLookup["1.3.6.1.4.1.311.2.6.1"] = "SpcRelaxedPEMarkerCheck";
165 | OidLookup["2.23.133.8.1"] = "Endorsement Key Certificate";
166 | OidLookup["1.3.6.1.4.1.311.2.6.2"] = "SpcEncryptedDigestRetryCount";
167 | OidLookup["1.3.6.1.4.1.311.10.3.4"] = "Encrypting File System";
168 | OidLookup["1.3.6.1.5.5.7.3.1"] = "Server Authentication";
169 | OidLookup["1.3.6.1.4.1.311.61.5.1"] = "HAL Extension";
170 | OidLookup["1.3.6.1.5.5.7.3.4"] = "Secure Email";
171 | OidLookup["1.3.6.1.5.5.7.3.5"] = "IP security end system";
172 | OidLookup["1.3.6.1.4.1.311.10.3.9"] = "Root List Signe";
173 | OidLookup["1.3.6.1.4.1.311.10.3.30"] = "Disallowed List";
174 | OidLookup["1.3.6.1.4.1.311.10.3.19"] = "Revoked List Signe";
175 | OidLookup["1.3.6.1.4.1.311.10.3.21"] = "Windows RT Verification";
176 | OidLookup["1.3.6.1.4.1.311.10.3.10"] = "Qualified Subordination";
177 | OidLookup["1.3.6.1.4.1.311.10.3.12"] = "Document Signing";
178 | OidLookup["1.3.6.1.4.1.311.10.3.24"] = "Protected Process Verification";
179 | OidLookup["1.3.6.1.4.1.311.80.1"] = "Document Encryption";
180 | OidLookup["1.3.6.1.4.1.311.10.3.22"] = "Protected Process Light Verification";
181 | OidLookup["1.3.6.1.4.1.311.21.19"] = "Directory Service Email Replication";
182 | OidLookup["1.3.6.1.4.1.311.21.5"] = "Private Key Archival";
183 | OidLookup["1.3.6.1.4.1.311.10.5.1"] = "Digital Rights";
184 | OidLookup["1.3.6.1.4.1.311.10.3.27"] = "Preview Build Signing";
185 | OidLookup["1.3.6.1.4.1.311.20.2.1"] = "Certificate Request Agent";
186 | OidLookup["2.23.133.8.2"] = "Platform Certificate";
187 | OidLookup["1.3.6.1.4.1.311.20.1"] = "CTL Usage";
188 | OidLookup["1.3.6.1.5.5.7.3.9"] = "OCSP Signing";
189 | OidLookup["1.3.6.1.5.5.7.3.3"] = "Code Signing";
190 | OidLookup["1.3.6.1.4.1.311.10.3.1"] = "Microsoft Trust List Signing";
191 | OidLookup["1.3.6.1.4.1.311.10.3.2"] = "Microsoft Time Stamping";
192 | OidLookup["1.3.6.1.4.1.311.76.8.1"] = "Microsoft Publishe";
193 | OidLookup["1.3.6.1.5.5.7.3.2"] = "Client Authentication";
194 | OidLookup["1.3.6.1.5.2.3.4"] = "PKIINIT Client Authentication";
195 | OidLookup["1.3.6.1.4.1.311.10.3.13"] = "Lifetime Signing";
196 | OidLookup["2.5.29.37.0"] = "Any Purpose";
197 | OidLookup["1.3.6.1.4.1.311.64.1.1"] = "Server Trust";
198 | OidLookup["1.3.6.1.4.1.311.10.3.7"] = "OEM Windows System Component Verification";
199 | }
200 |
201 | public List LookupOid(string[] oids)
202 | {
203 | List oidnames = new List { };
204 | if (oids != null)
205 | {
206 | foreach (string oid in oids)
207 | {
208 | if (OidLookup.ContainsKey(oid))
209 | {
210 | oidnames.Add(OidLookup[oid]);
211 | }
212 | else
213 | {
214 | oidnames.Add(oid);
215 | }
216 | }
217 | }
218 | return oidnames;
219 |
220 | }
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/SOAPHound.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Debug
7 | AnyCPU
8 | {33571B09-4E94-43CB-ABDC-0226D769E701}
9 | Exe
10 | SOAPHound
11 | SOAPHound
12 | v4.8
13 | 8.0
14 | 512
15 | true
16 | true
17 |
18 |
19 |
20 |
21 | AnyCPU
22 | true
23 | full
24 | false
25 | bin\Debug\
26 | DEBUG;TRACE
27 | prompt
28 | 4
29 |
30 |
31 | AnyCPU
32 | pdbonly
33 | true
34 | bin\Release\
35 | TRACE
36 | prompt
37 | 4
38 |
39 |
40 |
41 |
42 |
43 |
44 | packages\CommandLineParser.2.9.1\lib\net461\CommandLine.dll
45 |
46 |
47 | packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll
48 |
49 |
50 | packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
51 | True
52 | True
53 |
54 |
55 | packages\Newtonsoft.Json.13.0.2\lib\net45\Newtonsoft.Json.dll
56 |
57 |
58 |
59 |
60 | packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll
61 | True
62 | True
63 |
64 |
65 |
66 |
67 | packages\System.Console.4.3.0\lib\net46\System.Console.dll
68 | True
69 | True
70 |
71 |
72 |
73 | packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll
74 |
75 |
76 | packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll
77 | True
78 | True
79 |
80 |
81 |
82 |
83 | packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll
84 | True
85 | True
86 |
87 |
88 | packages\System.IO.4.3.0\lib\net462\System.IO.dll
89 | True
90 | True
91 |
92 |
93 | packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
94 | True
95 | True
96 |
97 |
98 |
99 | packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll
100 | True
101 | True
102 |
103 |
104 | packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll
105 | True
106 | True
107 |
108 |
109 | packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll
110 | True
111 | True
112 |
113 |
114 | packages\System.Linq.4.3.0\lib\net463\System.Linq.dll
115 | True
116 | True
117 |
118 |
119 | packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll
120 | True
121 | True
122 |
123 |
124 |
125 | packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll
126 | True
127 | True
128 |
129 |
130 | packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll
131 | True
132 | True
133 |
134 |
135 |
136 | packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll
137 | True
138 | True
139 |
140 |
141 | packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll
142 | True
143 | True
144 |
145 |
146 | packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll
147 | True
148 | True
149 |
150 |
151 | packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll
152 | True
153 | True
154 |
155 |
156 | packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll
157 | True
158 | True
159 |
160 |
161 |
162 | packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll
163 | True
164 | True
165 |
166 |
167 | packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
168 | True
169 | True
170 |
171 |
172 | packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll
173 | True
174 | True
175 |
176 |
177 | packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
178 | True
179 | True
180 |
181 |
182 |
183 | packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll
184 | True
185 | True
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 | packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll
194 | True
195 | True
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
--------------------------------------------------------------------------------
/hDNSRecord.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using System.Net;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.IO;
9 |
10 | namespace SOAPHound
11 | {
12 | class hDNSRecord
13 | {
14 | // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/f97756c9-3783-428b-9451-b376f877319a
15 | [StructLayout(LayoutKind.Sequential)]
16 | public struct DnssrvRpcRecord
17 | {
18 | public UInt16 wDataLength;
19 | public UInt16 wType;
20 | public UInt32 dwFlags;
21 | public UInt32 dwSerial;
22 | public UInt32 dwTtlSeconds;
23 | public UInt32 dwTimeStamp;
24 | public UInt32 dwReserved;
25 | }
26 |
27 | public static void ReadDNSObject(Byte[] arrObj)
28 | {
29 | try
30 | {
31 | IntPtr pObject = Marshal.AllocHGlobal(arrObj.Length);
32 | Marshal.Copy(arrObj, 0, pObject, arrObj.Length);
33 |
34 | DnssrvRpcRecord oRecord = (DnssrvRpcRecord)Marshal.PtrToStructure(pObject, typeof(DnssrvRpcRecord));
35 | IntPtr pData = (IntPtr)(pObject.ToInt64() + 24);
36 |
37 | if (oRecord.wType == 0)
38 | {
39 | Int64 iMSTS = (Marshal.ReadInt64(pData) / 10) / 1000;
40 | Console.WriteLine(" |_ DNS_RPC_RECORD_TS : " + (new DateTime(1601, 1, 1)).AddMilliseconds(iMSTS));
41 | }
42 | else if (oRecord.wType == 1)
43 | {
44 | byte[] bytes = BitConverter.GetBytes(Marshal.ReadInt32(pData));
45 | Console.WriteLine(" |_ DNS_RPC_RECORD_A : " + new IPAddress(bytes).ToString());
46 | }
47 | else if (oRecord.wType == 2 || oRecord.wType == 5 || oRecord.wType == 12)
48 | {
49 | Int16 iLen = Marshal.ReadByte(pData);
50 | Int16 iSeg = Marshal.ReadByte((IntPtr)(pData.ToInt64() + 1));
51 | IntPtr pDataPtr = (IntPtr)(pData.ToInt64() + 2);
52 | String sRecord = String.Empty;
53 | for (int i = 0; i < iSeg; i++)
54 | {
55 | Int16 iSegLen = Marshal.ReadByte(pDataPtr);
56 | sRecord += Marshal.PtrToStringAnsi((IntPtr)(pDataPtr.ToInt64() + 1), iSegLen);
57 | if (i != (iSeg - 1))
58 | {
59 | sRecord += ".";
60 | }
61 | pDataPtr = (IntPtr)(pDataPtr.ToInt64() + iSegLen + 1);
62 | }
63 | Console.WriteLine(" |_ DNS_RPC_RECORD_NODE_NAME : " + sRecord);
64 | }
65 | else if (oRecord.wType == 33)
66 | {
67 | Int16 iPrio = getInt16ToBigEndian(Marshal.ReadInt16(pData));
68 | Int16 iWeight = getInt16ToBigEndian(Marshal.ReadInt16((IntPtr)(pData.ToInt64() + 2)));
69 | Int16 iPort = getInt16ToBigEndian(Marshal.ReadInt16((IntPtr)(pData.ToInt64() + 4)));
70 | Int16 iSeg = Marshal.ReadByte((IntPtr)(pData.ToInt64() + 7));
71 | IntPtr pDataPtr = (IntPtr)(pData.ToInt64() + 8);
72 | String sRecord = String.Empty;
73 | for (int i = 0; i < iSeg; i++)
74 | {
75 | Int16 iSegLen = Marshal.ReadByte(pDataPtr);
76 | sRecord += Marshal.PtrToStringAnsi((IntPtr)(pDataPtr.ToInt64() + 1), iSegLen);
77 | if (i != (iSeg - 1))
78 | {
79 | sRecord += ".";
80 | }
81 | pDataPtr = (IntPtr)(pDataPtr.ToInt64() + iSegLen + 1);
82 | }
83 | Console.WriteLine(" |_ DNS_RPC_RECORD_SRV");
84 | Console.WriteLine(" |_ Priority : " + iPrio);
85 | Console.WriteLine(" |_ Weight : " + iWeight);
86 | Console.WriteLine(" |_ Port : " + iPort);
87 | Console.WriteLine(" |_ Name : " + sRecord);
88 | }
89 | else if (oRecord.wType == 6)
90 | {
91 | Int32 iSerial = getInt32ToBigEndian(Marshal.ReadInt32(pData));
92 | Int32 iRefresh = getInt32ToBigEndian(Marshal.ReadInt32((IntPtr)(pData.ToInt64() + 4)));
93 | Int32 iRetry = getInt32ToBigEndian(Marshal.ReadInt32((IntPtr)(pData.ToInt64() + 8)));
94 | Int32 iExpire = getInt32ToBigEndian(Marshal.ReadInt32((IntPtr)(pData.ToInt64() + 12)));
95 | Int32 iMinimumTtl = getInt32ToBigEndian(Marshal.ReadInt32((IntPtr)(pData.ToInt64() + 16)));
96 |
97 | Int16 iLen = Marshal.ReadByte((IntPtr)(pData.ToInt64() + 20));
98 | Int16 iSeg = Marshal.ReadByte((IntPtr)(pData.ToInt64() + 21));
99 | IntPtr pDataPtr = (IntPtr)(pData.ToInt64() + 22);
100 | String sNamePrimaryServer = String.Empty;
101 | for (int i = 0; i < iSeg; i++)
102 | {
103 | Int16 iSegLen = Marshal.ReadByte(pDataPtr);
104 | sNamePrimaryServer += Marshal.PtrToStringAnsi((IntPtr)(pDataPtr.ToInt64() + 1), iSegLen);
105 | if (i != (iSeg - 1))
106 | {
107 | sNamePrimaryServer += ".";
108 | }
109 | pDataPtr = (IntPtr)(pDataPtr.ToInt64() + iSegLen + 1);
110 | }
111 |
112 | iSeg = Marshal.ReadByte((IntPtr)(pData.ToInt64() + 21 + iLen));
113 | pDataPtr = (IntPtr)(pData.ToInt64() + 22 + iLen);
114 | String sZoneAdminEmail = String.Empty;
115 | for (int i = 0; i < iSeg; i++)
116 | {
117 | Int16 iSegLen = Marshal.ReadByte(pDataPtr);
118 | sZoneAdminEmail += Marshal.PtrToStringAnsi((IntPtr)(pDataPtr.ToInt64() + 1), iSegLen);
119 | if (i != (iSeg - 1))
120 | {
121 | sZoneAdminEmail += ".";
122 | }
123 | pDataPtr = (IntPtr)(pDataPtr.ToInt64() + iSegLen + 1);
124 | }
125 |
126 | Console.WriteLine(" |_ DNS_RPC_RECORD_SOA");
127 | Console.WriteLine(" |_ SerialNo : " + iSerial);
128 | Console.WriteLine(" |_ Refresh : " + iRefresh);
129 | Console.WriteLine(" |_ Retry : " + iRetry);
130 | Console.WriteLine(" |_ Expire : " + iExpire);
131 | Console.WriteLine(" |_ MinimumTtl : " + iMinimumTtl);
132 | Console.WriteLine(" |_ PrimaryServer : " + sNamePrimaryServer);
133 | Console.WriteLine(" |_ AdminEmail : " + sZoneAdminEmail);
134 | }
135 | else if (oRecord.wType == 28)
136 | {
137 | Byte[] bIPV6 = new byte[16];
138 | Marshal.Copy(pData, bIPV6, 0, 16);
139 | Console.WriteLine(" |_ DNS_RPC_RECORD_AAAA : " + new IPAddress(bIPV6).ToString());
140 | }
141 | else
142 | {
143 | Console.WriteLine(" |_ Unimplemented DNS Record Type ---> " + oRecord.wType);
144 | Console.WriteLine(" |_ DEBUG : " + BitConverter.ToString(arrObj).Replace("-", " "));
145 | }
146 |
147 | Marshal.FreeHGlobal(pObject);
148 | }
149 | catch (Exception ex)
150 | {
151 | Console.WriteLine(" |_ Failed to parse DNS entry..");
152 | if (ex.InnerException != null)
153 | {
154 | Console.WriteLine(" |_ " + ex.InnerException.Message);
155 | }
156 | else
157 | {
158 | Console.WriteLine(" |_ " + ex.Message);
159 | }
160 | }
161 | }
162 |
163 | public static void ReadandOutputDNSObject(Byte[] arrObj, string filepath)
164 | {
165 | try
166 | {
167 | IntPtr pObject = Marshal.AllocHGlobal(arrObj.Length);
168 | Marshal.Copy(arrObj, 0, pObject, arrObj.Length);
169 |
170 | DnssrvRpcRecord oRecord = (DnssrvRpcRecord)Marshal.PtrToStructure(pObject, typeof(DnssrvRpcRecord));
171 | IntPtr pData = (IntPtr)(pObject.ToInt64() + 24);
172 |
173 | if (oRecord.wType == 0)
174 | {
175 | Int64 iMSTS = (Marshal.ReadInt64(pData) / 10) / 1000;
176 | File.AppendAllText(filepath, "\r\n |_ DNS_RPC_RECORD_TS : " + (new DateTime(1601, 1, 1)).AddMilliseconds(iMSTS));
177 | }
178 | else if (oRecord.wType == 1)
179 | {
180 | byte[] bytes = BitConverter.GetBytes(Marshal.ReadInt32(pData));
181 | File.AppendAllText(filepath, "\r\n |_ DNS_RPC_RECORD_A : " + new IPAddress(bytes).ToString());
182 | }
183 | else if (oRecord.wType == 2 || oRecord.wType == 5 || oRecord.wType == 12)
184 | {
185 | Int16 iLen = Marshal.ReadByte(pData);
186 | Int16 iSeg = Marshal.ReadByte((IntPtr)(pData.ToInt64() + 1));
187 | IntPtr pDataPtr = (IntPtr)(pData.ToInt64() + 2);
188 | String sRecord = String.Empty;
189 | for (int i = 0; i < iSeg; i++)
190 | {
191 | Int16 iSegLen = Marshal.ReadByte(pDataPtr);
192 | sRecord += Marshal.PtrToStringAnsi((IntPtr)(pDataPtr.ToInt64() + 1), iSegLen);
193 | if (i != (iSeg - 1))
194 | {
195 | sRecord += ".";
196 | }
197 | pDataPtr = (IntPtr)(pDataPtr.ToInt64() + iSegLen + 1);
198 | }
199 | File.AppendAllText(filepath, "\r\n |_ DNS_RPC_RECORD_NODE_NAME : " + sRecord);
200 | }
201 | else if (oRecord.wType == 33)
202 | {
203 | Int16 iPrio = getInt16ToBigEndian(Marshal.ReadInt16(pData));
204 | Int16 iWeight = getInt16ToBigEndian(Marshal.ReadInt16((IntPtr)(pData.ToInt64() + 2)));
205 | Int16 iPort = getInt16ToBigEndian(Marshal.ReadInt16((IntPtr)(pData.ToInt64() + 4)));
206 | Int16 iSeg = Marshal.ReadByte((IntPtr)(pData.ToInt64() + 7));
207 | IntPtr pDataPtr = (IntPtr)(pData.ToInt64() + 8);
208 | String sRecord = String.Empty;
209 | for (int i = 0; i < iSeg; i++)
210 | {
211 | Int16 iSegLen = Marshal.ReadByte(pDataPtr);
212 | sRecord += Marshal.PtrToStringAnsi((IntPtr)(pDataPtr.ToInt64() + 1), iSegLen);
213 | if (i != (iSeg - 1))
214 | {
215 | sRecord += ".";
216 | }
217 | pDataPtr = (IntPtr)(pDataPtr.ToInt64() + iSegLen + 1);
218 | }
219 | File.AppendAllText(filepath, "\r\n |_ DNS_RPC_RECORD_SRV");
220 | File.AppendAllText(filepath, "\r\n |_ Priority : " + iPrio);
221 | File.AppendAllText(filepath, "\r\n |_ Weight : " + iWeight);
222 | File.AppendAllText(filepath, "\r\n |_ Port : " + iPort);
223 | File.AppendAllText(filepath, "\r\n |_ Name : " + sRecord);
224 | }
225 | else if (oRecord.wType == 6)
226 | {
227 | Int32 iSerial = getInt32ToBigEndian(Marshal.ReadInt32(pData));
228 | Int32 iRefresh = getInt32ToBigEndian(Marshal.ReadInt32((IntPtr)(pData.ToInt64() + 4)));
229 | Int32 iRetry = getInt32ToBigEndian(Marshal.ReadInt32((IntPtr)(pData.ToInt64() + 8)));
230 | Int32 iExpire = getInt32ToBigEndian(Marshal.ReadInt32((IntPtr)(pData.ToInt64() + 12)));
231 | Int32 iMinimumTtl = getInt32ToBigEndian(Marshal.ReadInt32((IntPtr)(pData.ToInt64() + 16)));
232 |
233 | Int16 iLen = Marshal.ReadByte((IntPtr)(pData.ToInt64() + 20));
234 | Int16 iSeg = Marshal.ReadByte((IntPtr)(pData.ToInt64() + 21));
235 | IntPtr pDataPtr = (IntPtr)(pData.ToInt64() + 22);
236 | String sNamePrimaryServer = String.Empty;
237 | for (int i = 0; i < iSeg; i++)
238 | {
239 | Int16 iSegLen = Marshal.ReadByte(pDataPtr);
240 | sNamePrimaryServer += Marshal.PtrToStringAnsi((IntPtr)(pDataPtr.ToInt64() + 1), iSegLen);
241 | if (i != (iSeg - 1))
242 | {
243 | sNamePrimaryServer += ".";
244 | }
245 | pDataPtr = (IntPtr)(pDataPtr.ToInt64() + iSegLen + 1);
246 | }
247 |
248 | iSeg = Marshal.ReadByte((IntPtr)(pData.ToInt64() + 21 + iLen));
249 | pDataPtr = (IntPtr)(pData.ToInt64() + 22 + iLen);
250 | String sZoneAdminEmail = String.Empty;
251 | for (int i = 0; i < iSeg; i++)
252 | {
253 | Int16 iSegLen = Marshal.ReadByte(pDataPtr);
254 | sZoneAdminEmail += Marshal.PtrToStringAnsi((IntPtr)(pDataPtr.ToInt64() + 1), iSegLen);
255 | if (i != (iSeg - 1))
256 | {
257 | sZoneAdminEmail += ".";
258 | }
259 | pDataPtr = (IntPtr)(pDataPtr.ToInt64() + iSegLen + 1);
260 | }
261 |
262 | File.AppendAllText(filepath, "\r\n |_ DNS_RPC_RECORD_SOA");
263 | File.AppendAllText(filepath, "\r\n |_ SerialNo : " + iSerial);
264 | File.AppendAllText(filepath, "\r\n |_ Refresh : " + iRefresh);
265 | File.AppendAllText(filepath, "\r\n |_ Retry : " + iRetry);
266 | File.AppendAllText(filepath, "\r\n |_ Expire : " + iExpire);
267 | File.AppendAllText(filepath, "\r\n |_ MinimumTtl : " + iMinimumTtl);
268 | File.AppendAllText(filepath, "\r\n |_ PrimaryServer : " + sNamePrimaryServer);
269 | File.AppendAllText(filepath, "\r\n |_ AdminEmail : " + sZoneAdminEmail);
270 | }
271 | else if (oRecord.wType == 28)
272 | {
273 | Byte[] bIPV6 = new byte[16];
274 | Marshal.Copy(pData, bIPV6, 0, 16);
275 | File.AppendAllText(filepath, "\r\n |_ DNS_RPC_RECORD_AAAA : " + new IPAddress(bIPV6).ToString());
276 | }
277 | else
278 | {
279 | File.AppendAllText(filepath, "\r\n |_ Unimplemented DNS Record Type ---> " + oRecord.wType);
280 | File.AppendAllText(filepath, "\r\n |_ DEBUG : " + BitConverter.ToString(arrObj).Replace("-", " "));
281 | }
282 |
283 | Marshal.FreeHGlobal(pObject);
284 | }
285 | catch (Exception ex)
286 | {
287 | File.AppendAllText(filepath, "\r\n |_ Failed to parse DNS entry..");
288 | if (ex.InnerException != null)
289 | {
290 | File.AppendAllText(filepath, "\r\n |_ " + ex.InnerException.Message);
291 | }
292 | else
293 | {
294 | File.AppendAllText(filepath, "\r\n |_ " + ex.Message);
295 | }
296 | }
297 | }
298 | public static Int16 getInt16ToBigEndian(Int16 iInput)
299 | {
300 | byte[] aBytes = BitConverter.GetBytes(iInput);
301 | Array.Reverse(aBytes);
302 | return BitConverter.ToInt16(aBytes, 0);
303 | }
304 |
305 | public static Int32 getInt32ToBigEndian(Int32 iInput)
306 | {
307 | byte[] aBytes = BitConverter.GetBytes(iInput);
308 | Array.Reverse(aBytes);
309 | return BitConverter.ToInt32(aBytes, 0);
310 | }
311 | }
312 | }
313 |
--------------------------------------------------------------------------------
/Processors/ACLProcessor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.DirectoryServices;
5 | using System.Security.AccessControl;
6 | using System.Security.Principal;
7 | using SOAPHound.Enums;
8 | using SOAPHound.ADWS;
9 | using SOAPHound.OutputTypes;
10 | using SOAPHound;
11 | using System.Linq;
12 |
13 | namespace SOAPHound.Processors
14 | {
15 | public static class ACLProcessor
16 | {
17 |
18 | public static IEnumerable parseAces(ActiveDirectorySecurity NTSecurityDescriptor, Label objectType, bool hasLaps)
19 | {
20 |
21 | if (NTSecurityDescriptor != null)
22 | {
23 | // Get owner
24 | var ownerSid = NTSecurityDescriptor.GetOwner(typeof(SecurityIdentifier));
25 | if (ownerSid != null)
26 | {
27 | var resolvedOwner = ADWSUtils.ResolveIDAndType(ownerSid.ToString());
28 | if (resolvedOwner != null)
29 | yield return new Ace
30 | {
31 | PrincipalType = resolvedOwner.ObjectType,
32 | PrincipalSID = resolvedOwner.ObjectIdentifier,
33 | RightName = EdgeNames.Owns,
34 | IsInherited = false,
35 | };
36 | }
37 |
38 | foreach (ActiveDirectoryAccessRule rule in NTSecurityDescriptor.GetAccessRules(true, true, typeof(SecurityIdentifier)))
39 | {
40 |
41 | if (rule.IdentityReference.ToString().StartsWith("S-1-5-21"))
42 | {
43 | var aceRights = rule.ActiveDirectoryRights;
44 | var aceType = rule.ObjectType.ToString().ToLower();
45 | var inherited = rule.IsInherited;
46 | var resolvedPrincipal = ADWSUtils.ResolveIDAndType(rule.IdentityReference.ToString());
47 |
48 | if (objectType == Label.CA)
49 | {
50 | var rights = (CertificationAuthorityRights)rule.ActiveDirectoryRights;
51 | if (((rights & CertificationAuthorityRights.ManageCA) == CertificationAuthorityRights.ManageCA))
52 | yield return new Ace
53 | {
54 | PrincipalType = resolvedPrincipal.ObjectType,
55 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
56 | IsInherited = inherited,
57 | RightName = EdgeNames.ManageCA,
58 | };
59 |
60 | if (((rights & CertificationAuthorityRights.ManageCertificates) == CertificationAuthorityRights.ManageCertificates))
61 | yield return new Ace
62 | {
63 | PrincipalType = resolvedPrincipal.ObjectType,
64 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
65 | IsInherited = inherited,
66 | RightName = EdgeNames.ManageCertificates,
67 | };
68 | if (((rights & CertificationAuthorityRights.Auditor) == CertificationAuthorityRights.Auditor))
69 | yield return new Ace
70 | {
71 | PrincipalType = resolvedPrincipal.ObjectType,
72 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
73 | IsInherited = inherited,
74 | RightName = EdgeNames.Auditor,
75 | };
76 | if (((rights & CertificationAuthorityRights.Operator) == CertificationAuthorityRights.Operator))
77 | yield return new Ace
78 | {
79 | PrincipalType = resolvedPrincipal.ObjectType,
80 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
81 | IsInherited = inherited,
82 | RightName = EdgeNames.Operator,
83 | };
84 | }
85 |
86 |
87 | //GenericAll applies to every object
88 | if (aceRights.HasFlag(ActiveDirectoryRights.GenericAll))
89 | {
90 | if (aceType == ACEGuids.AllGuid || aceType == "")
91 | yield return new Ace
92 | {
93 | PrincipalType = resolvedPrincipal.ObjectType,
94 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
95 | IsInherited = inherited,
96 | RightName = EdgeNames.GenericAll
97 | };
98 | //This is a special case. If we don't continue here, every other ACE will match because GenericAll includes all other permissions
99 | continue;
100 | }
101 | //WriteDACL and WriteOwner are always useful no matter what the object type is as well because they enable all other attacks
102 | if (aceRights.HasFlag(ActiveDirectoryRights.WriteDacl))
103 | yield return new Ace
104 | {
105 | PrincipalType = resolvedPrincipal.ObjectType,
106 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
107 | IsInherited = inherited,
108 | RightName = EdgeNames.WriteDacl,
109 | };
110 |
111 | if (aceRights.HasFlag(ActiveDirectoryRights.WriteOwner))
112 | yield return new Ace
113 | {
114 | PrincipalType = resolvedPrincipal.ObjectType,
115 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
116 | IsInherited = inherited,
117 | RightName = EdgeNames.WriteOwner,
118 | };
119 |
120 | //Allows a principal to add itself to a group and no one else
121 | if (aceRights.HasFlag(ActiveDirectoryRights.Self) &&
122 | !aceRights.HasFlag(ActiveDirectoryRights.WriteProperty) &&
123 | !aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) && objectType == Label.Group &&
124 | aceType == ACEGuids.WriteMember)
125 | yield return new Ace
126 | {
127 | PrincipalType = resolvedPrincipal.ObjectType,
128 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
129 | IsInherited = inherited,
130 | RightName = EdgeNames.AddSelf
131 | };
132 |
133 | //Process object type specific ACEs. Extended rights apply to users, domains, and computers
134 | if (aceRights.HasFlag(ActiveDirectoryRights.ExtendedRight))
135 | {
136 | if (objectType == Label.Domain)
137 | {
138 | if (aceType == ACEGuids.DSReplicationGetChanges)
139 | yield return new Ace
140 | {
141 | PrincipalType = resolvedPrincipal.ObjectType,
142 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
143 | IsInherited = inherited,
144 | RightName = EdgeNames.GetChanges
145 | };
146 | else if (aceType == ACEGuids.DSReplicationGetChangesAll)
147 | yield return new Ace
148 | {
149 | PrincipalType = resolvedPrincipal.ObjectType,
150 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
151 | IsInherited = inherited,
152 | RightName = EdgeNames.GetChangesAll
153 | };
154 | else if (aceType == ACEGuids.DSReplicationGetChangesInFilteredSet)
155 | yield return new Ace
156 | {
157 | PrincipalType = resolvedPrincipal.ObjectType,
158 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
159 | IsInherited = inherited,
160 | RightName = EdgeNames.GetChangesInFilteredSet
161 | };
162 | else if (aceType == ACEGuids.AllGuid || aceType == "")
163 | yield return new Ace
164 | {
165 | PrincipalType = resolvedPrincipal.ObjectType,
166 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
167 | IsInherited = inherited,
168 | RightName = EdgeNames.AllExtendedRights
169 | };
170 | }
171 | else if (objectType == Label.User)
172 | {
173 | if (aceType == ACEGuids.UserForceChangePassword)
174 | yield return new Ace
175 | {
176 | PrincipalType = resolvedPrincipal.ObjectType,
177 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
178 | IsInherited = inherited,
179 | RightName = EdgeNames.ForceChangePassword
180 | };
181 | else if (aceType == ACEGuids.AllGuid || aceType == "")
182 | yield return new Ace
183 | {
184 | PrincipalType = resolvedPrincipal.ObjectType,
185 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
186 | IsInherited = inherited,
187 | RightName = EdgeNames.AllExtendedRights
188 | };
189 | }
190 | else if (objectType == Label.Computer)
191 | {
192 | //ReadLAPSPassword is only applicable if the computer actually has LAPS. Check the world readable property ms-mcs-admpwdexpirationtime
193 | if (hasLaps)
194 | {
195 | if (aceType == ACEGuids.AllGuid || aceType == "")
196 | yield return new Ace
197 | {
198 | PrincipalType = resolvedPrincipal.ObjectType,
199 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
200 | IsInherited = inherited,
201 | RightName = EdgeNames.AllExtendedRights
202 | };
203 | /* to be checked and fixed
204 | else if (mappedGuid is "ms-mcs-admpwd")
205 | yield return new Ace
206 | {
207 | PrincipalType = resolvedPrincipal.ObjectType,
208 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
209 | IsInherited = inherited,
210 | RightName = EdgeNames.ReadLAPSPassword
211 | }; */
212 | }
213 | }
214 | }
215 |
216 | //GenericWrite encapsulates WriteProperty, so process them in tandem to avoid duplicate edges
217 | if (aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) ||
218 | aceRights.HasFlag(ActiveDirectoryRights.WriteProperty))
219 | {
220 | if (objectType == Label.User || objectType == Label.Group || objectType == Label.Computer || objectType == Label.GPO)
221 | if (aceType == ACEGuids.AllGuid || aceType == "")
222 | yield return new Ace
223 | {
224 | PrincipalType = resolvedPrincipal.ObjectType,
225 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
226 | IsInherited = inherited,
227 | RightName = EdgeNames.GenericWrite
228 | };
229 |
230 | if (objectType == Label.User && aceType == ACEGuids.WriteSPN)
231 | yield return new Ace
232 | {
233 | PrincipalType = resolvedPrincipal.ObjectType,
234 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
235 | IsInherited = inherited,
236 | RightName = EdgeNames.WriteSPN
237 | };
238 | else if (objectType == Label.Computer && aceType == ACEGuids.WriteAllowedToAct)
239 | yield return new Ace
240 | {
241 | PrincipalType = resolvedPrincipal.ObjectType,
242 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
243 | IsInherited = inherited,
244 | RightName = EdgeNames.AddAllowedToAct
245 | };
246 | else if (objectType == Label.Computer && aceType == ACEGuids.UserAccountRestrictions && !resolvedPrincipal.ObjectIdentifier.EndsWith("-512"))
247 | yield return new Ace
248 | {
249 | PrincipalType = resolvedPrincipal.ObjectType,
250 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
251 | IsInherited = inherited,
252 | RightName = EdgeNames.WriteAccountRestrictions
253 | };
254 | else if (objectType == Label.Group && aceType == ACEGuids.WriteMember)
255 | yield return new Ace
256 | {
257 | PrincipalType = resolvedPrincipal.ObjectType,
258 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
259 | IsInherited = inherited,
260 | RightName = EdgeNames.AddMember
261 | };
262 | else if ((objectType == Label.User || objectType == Label.Computer) && aceType == ACEGuids.AddKeyPrincipal)
263 | yield return new Ace
264 | {
265 | PrincipalType = resolvedPrincipal.ObjectType,
266 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
267 | IsInherited = inherited,
268 | RightName = EdgeNames.AddKeyCredentialLink
269 | };
270 | }
271 |
272 | //Enrollemnt flag for PKI
273 | if (aceType == ACEGuids.Enrollment)
274 | yield return new Ace
275 | {
276 | PrincipalType = resolvedPrincipal.ObjectType,
277 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
278 | IsInherited = inherited,
279 | RightName = EdgeNames.Enroll,
280 |
281 | };
282 | if (aceType == ACEGuids.AutoEnrollment)
283 | yield return new Ace
284 | {
285 | PrincipalType = resolvedPrincipal.ObjectType,
286 | PrincipalSID = resolvedPrincipal.ObjectIdentifier,
287 | IsInherited = inherited,
288 | RightName = EdgeNames.AutoEnroll,
289 |
290 | };
291 |
292 | }
293 | }
294 |
295 | }
296 |
297 | }
298 |
299 |
300 | }
301 | }
--------------------------------------------------------------------------------
/ADWS/ADWSConnector.cs:
--------------------------------------------------------------------------------
1 | using System.ServiceModel;
2 | using System.ServiceModel.Channels;
3 | using System.Xml.Serialization;
4 | using System.Xml;
5 | using System.Xml.Linq;
6 | using System.ServiceModel.Description;
7 | using System.Globalization;
8 | using System.Security.Principal;
9 | using System.DirectoryServices;
10 | using System;
11 | using System.Linq;
12 | using System.Collections.Generic;
13 | using System.IO;
14 | using System.Threading.Tasks;
15 | using System.Net;
16 | using System.Security.Cryptography.X509Certificates;
17 | using System.Text.RegularExpressions;
18 |
19 | namespace SOAPHound.ADWS
20 | {
21 | internal class ADWSConnector
22 | {
23 | string BaseUri { get; set; }
24 | NetworkCredential Credentials { get; set; }
25 |
26 | NetTcpBinding Binding { get; set; }
27 | MessageVersion Version { get; set; }
28 | public ADWSConnector(string Host, NetworkCredential Credentials)
29 | {
30 | UriBuilder uriBuilder = new UriBuilder();
31 | uriBuilder.Scheme = "net.tcp";
32 | uriBuilder.Host = Host;
33 | uriBuilder.Port = 9389;
34 | this.BaseUri = uriBuilder.ToString();
35 |
36 | this.Binding = new NetTcpBinding();
37 |
38 | this.Binding.OpenTimeout = new TimeSpan(0, 10, 0);
39 | this.Binding.CloseTimeout = new TimeSpan(0, 10, 0);
40 | this.Binding.SendTimeout = new TimeSpan(0, 10, 0);
41 | this.Binding.ReceiveTimeout = new TimeSpan(0, 10, 0);
42 | this.Binding.MaxBufferSize = 1073741824;
43 | this.Binding.MaxReceivedMessageSize = 1073741824;
44 | this.Binding.ReaderQuotas.MaxDepth = 64;
45 | this.Binding.ReaderQuotas.MaxArrayLength = 2147483647;
46 | this.Binding.ReaderQuotas.MaxStringContentLength = 2147483647;
47 | this.Binding.ReaderQuotas.MaxNameTableCharCount = 2147483647;
48 | this.Binding.ReaderQuotas.MaxBytesPerRead = 2147483647;
49 | EnvelopeVersion envelopeVersion = EnvelopeVersion.Soap12;
50 | AddressingVersion addressingVersion = AddressingVersion.WSAddressing10;
51 | this.Version = MessageVersion.CreateVersion(envelopeVersion, addressingVersion);
52 | this.Credentials = Credentials;
53 | }
54 |
55 | static XmlReader XmlReaderFromString(string xml)
56 | {
57 | return XmlReader.Create(new StringReader(xml));
58 | }
59 |
60 | static XDocument MessageToXDocument(Message message)
61 | {
62 | return XDocument.Parse(ReplaceHexadecimalSymbols(message.ToString()));
63 | }
64 |
65 | static string ReplaceHexadecimalSymbols(string txt)
66 | {
67 | string r = "[\x00-\x08\x0B\x0C\x0E-\x1F\x26]";
68 | return Regex.Replace(txt, r, "", RegexOptions.Compiled);
69 | }
70 |
71 | public EndpointAddress GetEndpointAddress(string path)
72 | {
73 | return new EndpointAddress(this.BaseUri + path);
74 | }
75 |
76 | public void UpdateCredentials(ClientCredentials c)
77 | {
78 | c.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
79 | c.Windows.ClientCredential = this.Credentials;
80 | }
81 |
82 | public static string ConvertLdapNamingContextToDomain(string ldapContext)
83 | {
84 | if (string.IsNullOrEmpty(ldapContext))
85 | {
86 | return string.Empty;
87 | }
88 | var components = ldapContext.Split(',');
89 | var domainComponents = components.Select(c => c.Replace("DC=", "")).ToArray();
90 | return string.Join(".", domainComponents);
91 | }
92 |
93 | public ADInfo GetADInfo()
94 | {
95 | ADInfo adInfo = new ADInfo();
96 | var endpointAddress = GetEndpointAddress("ActiveDirectoryWebServices/Windows/Resource");
97 |
98 | var resourceClient = new ADWS.ResourceClient(this.Binding, endpointAddress);
99 | UpdateCredentials(resourceClient.ClientCredentials);
100 |
101 | var rcRequest = Message.CreateMessage(Version, "http://schemas.xmlsoap.org/ws/2004/09/transfer/Get");
102 | MessageHeader hdr = MessageHeader.CreateHeader("instance", "http://schemas.microsoft.com/2008/1/ActiveDirectory", "ldap:389");
103 | MessageHeader hdr2 = MessageHeader.CreateHeader("objectReferenceProperty", "http://schemas.microsoft.com/2008/1/ActiveDirectory", "11111111-1111-1111-1111-111111111111");
104 |
105 | rcRequest.Headers.Add(hdr);
106 | rcRequest.Headers.Add(hdr2);
107 |
108 | Message resp = resourceClient.GetAsync(rcRequest).Result;
109 | var getResponse = MessageToXDocument(resp);
110 | string defaultNamingContext = getResponse
111 | .Descendants(XName.Get("defaultNamingContext", "http://schemas.microsoft.com/2008/1/ActiveDirectory/Data"))
112 | .Descendants(XName.Get("value", "http://schemas.microsoft.com/2008/1/ActiveDirectory"))
113 | .FirstOrDefault()
114 | .Value;
115 |
116 | adInfo.DefaultNamingContext = defaultNamingContext;
117 | adInfo.DomainName = ConvertLdapNamingContextToDomain(defaultNamingContext);
118 |
119 | return adInfo;
120 | }
121 |
122 | public List Enumerate(string ldapBase, string ldapFilter, List properties, int batchSize = 1000)
123 | {
124 | List list = new List();
125 | var endpointAddress = new System.ServiceModel.EndpointAddress(this.BaseUri + "ActiveDirectoryWebServices/Windows/Enumeration");
126 |
127 | var searchClient = new ADWS.SearchClient(this.Binding, endpointAddress);
128 | UpdateCredentials(searchClient.ClientCredentials);
129 |
130 | var enumerateRequest = new EnumerateRequest
131 | {
132 | Filter = new EnumerateRequestFilter
133 | {
134 | LdapQuery = new LdapQuery
135 | {
136 | QueryFilter = ldapFilter,
137 | BaseObject = ldapBase,
138 | Scope = "Subtree"
139 | }
140 | },
141 | Selection = new EnumerateRequestSelection
142 | {
143 | SelectionProperties = properties.Select(s => "d:" + s).ToList()
144 | }
145 | };
146 |
147 | var Request = Message.CreateMessage(Version, "http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate", XmlReaderFromString(ObjectToXml(enumerateRequest)));
148 | MessageHeader hdr = MessageHeader.CreateHeader("instance", "http://schemas.microsoft.com/2008/1/ActiveDirectory", "ldap:389");
149 |
150 | Request.Headers.Add(hdr);
151 |
152 | Message resp = searchClient.EnumerateAsync(Request).Result;
153 |
154 | var enumerateResponse = MessageToXDocument(resp);
155 | string enumerationContext = enumerateResponse
156 | .Descendants(XName.Get("EnumerationContext", "http://schemas.xmlsoap.org/ws/2004/09/enumeration"))
157 | .FirstOrDefault()?
158 | .Value;
159 | if (enumerationContext == null)
160 | {
161 | throw new Exception("EnumerationContext could not be extracted from Enumerate response. This could be because your domain does not use LAPS and you are running without the --nolaps option.");
162 | }
163 |
164 | var pullRequest = new PullSearchResultsRequest
165 | {
166 | EnumerationContext = enumerationContext,
167 | MaxElements = batchSize,
168 | Controls = new Controls
169 | {
170 | Control = new List
171 | {
172 | new Control
173 | {
174 | Type = "1.2.840.113556.1.4.801",
175 | ControlValue = "MIQAAAADAgEH"
176 | }
177 | }
178 | }
179 | };
180 |
181 | var adObjects = new List();
182 | bool endOfSequence = false;
183 | while (!endOfSequence)
184 | {
185 |
186 | var pullRequestMessage = Message.CreateMessage(Version, "http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull", XmlReaderFromString(ObjectToXml(pullRequest)));
187 |
188 | pullRequestMessage.Headers.Add(hdr);
189 | Message resp2 = searchClient.PullAsync(pullRequestMessage).Result;
190 | var pullResponse = MessageToXDocument(resp2);
191 | adObjects.AddRange(ExtractADObjectsFromResponse(pullResponse));
192 | endOfSequence = pullResponse
193 | .Descendants(XName.Get("EndOfSequence", "http://schemas.xmlsoap.org/ws/2004/09/enumeration"))
194 | .Count() > 0;
195 | }
196 | return adObjects;
197 | }
198 | private static ActiveDirectorySecurity ParseActiveDirectorySecurity(string value)
199 | {
200 | byte[] data = Convert.FromBase64String(value);
201 | ActiveDirectorySecurity sd = new ActiveDirectorySecurity();
202 | sd.SetSecurityDescriptorBinaryForm(data);
203 | return sd;
204 | }
205 |
206 | private static X509Certificate2Collection ParseX509Certificate2Collection(string[] propertyValues)
207 | {
208 | X509Certificate2Collection collection = new X509Certificate2Collection();
209 | if (propertyValues == null)
210 | {
211 | return collection;
212 | }
213 | foreach (var propertyValue in propertyValues)
214 | {
215 | try
216 | {
217 | byte[] data = Convert.FromBase64String(propertyValue);
218 | collection.Add(new X509Certificate2(data));
219 | }
220 | catch (Exception)
221 | {
222 |
223 | }
224 | }
225 | return collection;
226 | }
227 |
228 | private static SecurityIdentifier[] ParseSecurityIdentifierList(string[] propertyValues)
229 | {
230 | List collection = new List();
231 | if (propertyValues == null)
232 | {
233 | return collection.ToArray();
234 | }
235 | foreach (var propertyValue in propertyValues)
236 | {
237 | try
238 | {
239 | byte[] data = Convert.FromBase64String(propertyValue);
240 | collection.Add(new SecurityIdentifier(data, 0));
241 | }
242 | catch (Exception)
243 | {
244 |
245 | }
246 | }
247 | return collection.ToArray();
248 | }
249 | private static List ExtractADObjectsFromResponse(XDocument pullResponse)
250 | {
251 | XNamespace addata = "http://schemas.microsoft.com/2008/1/ActiveDirectory/Data";
252 | XNamespace ad = "http://schemas.microsoft.com/2008/1/ActiveDirectory";
253 | XNamespace wsen = "http://schemas.xmlsoap.org/ws/2004/09/enumeration";
254 |
255 |
256 | var entries = new List>();
257 | var arrayKeys = new List { "member", "msDS-AllowedToDelegateTo", "pKIExtendedKeyUsage", "servicePrincipalName", "certificateTemplates", "cACertificate", "sIDHistory" };
258 |
259 | var adObjects = new List { };
260 |
261 | foreach (var element in pullResponse.Descendants(wsen + "Items").Elements())
262 | {
263 | var adobject = new ADObject();
264 | adobject.Class = element.Name.LocalName.ToLowerInvariant();
265 | foreach (var property in element.Elements())
266 | {
267 | var propertyName = property.Name.LocalName;
268 | var propertyValue = property.Element(ad + "value").Value;
269 | string[] propertyValues = null;
270 | if (arrayKeys.Contains(propertyName))
271 | {
272 | propertyValues = property.Elements(ad + "value").Select(v => v.Value).ToArray();
273 | }
274 | switch (propertyName)
275 | {
276 | case "class":
277 | adobject.Class = propertyValue;
278 | break;
279 | case "adminCount":
280 | adobject.AdminCount = int.Parse(propertyValue);
281 | break;
282 | case "cACertificate":
283 | adobject.CACertificate = ParseX509Certificate2Collection(propertyValues);
284 | break;
285 | case "certificateTemplates":
286 | adobject.CertificateTemplates = propertyValues;
287 | break;
288 | case "description":
289 | adobject.Description = propertyValue;
290 | break;
291 | case "displayName":
292 | adobject.DisplayName = propertyValue;
293 | break;
294 | case "distinguishedName":
295 | adobject.DistinguishedName = propertyValue;
296 | break;
297 | case "dNSHostName":
298 | adobject.DNSHostName = propertyValue;
299 | break;
300 | case "cn":
301 | adobject.Cn = propertyValue;
302 | break;
303 | case "dnsRecord":
304 | adobject.DnsRecord = Convert.FromBase64String(propertyValue);
305 | break;
306 | case "ms-DS-MachineAccountQuota":
307 | adobject.DSMachineAccountQuota = int.Parse(propertyValue);
308 | break;
309 | case "gPCFileSysPath":
310 | adobject.GPCFileSysPath = propertyValue;
311 | break;
312 | case "isDeleted":
313 | adobject.IsDeleted = propertyValue;
314 | break;
315 | case "gPLink":
316 | adobject.GPLink = propertyValue;
317 | break;
318 | case "gPOptions":
319 | adobject.GPOptions = int.Parse(propertyValue);
320 | break;
321 | case "lastLogon":
322 | adobject.LastLogon = FromLongToDateTime(long.Parse(propertyValue));
323 | break;
324 | case "lastLogonTimestamp":
325 | adobject.LastLogonTimestamp = FromLongToDateTime(long.Parse(propertyValue));
326 | break;
327 | case "member":
328 | adobject.Member = propertyValues;
329 | break;
330 | case "msDS-AllowedToActOnBehalfOfOtherIdentity":
331 | adobject.MsDSAllowedToActOnBehalfOfOtherIdentity = ParseActiveDirectorySecurity(propertyValue);
332 | break;
333 | case "msDS-AllowedToDelegateTo":
334 | adobject.MsDSAllowedToDelegateTo = propertyValues;
335 | break;
336 | case "msDS-Behavior-Version":
337 | adobject.FunctionalLevel = int.Parse(propertyValue);
338 | break;
339 | case "ms-Mcs-AdmPwdExpirationTime":
340 | adobject.MsMCSAdmPwdExpirationTime = long.Parse(propertyValue);
341 | break;
342 | case "msPKI-Certificate-Name-Flag":
343 | adobject.MsPKICertificateNameFlag = int.Parse(propertyValue);
344 | break;
345 | case "msPKI-Minimal-Key-Size":
346 | adobject.MsPKIMinimalKeySize = int.Parse(propertyValue);
347 | break;
348 | case "msPKI-Enrollment-Flag":
349 | adobject.MsPKIEnrollmentFlag = int.Parse(propertyValue);
350 | break;
351 | case "msPKI-Private-Key-Flag":
352 | adobject.MsPKIPrivateKeyFlag = int.Parse(propertyValue);
353 | break;
354 | case "name":
355 | adobject.Name = propertyValue;
356 | break;
357 | case "nTSecurityDescriptor":
358 | adobject.NTSecurityDescriptor = ParseActiveDirectorySecurity(propertyValue);
359 | break;
360 | case "objectGUID":
361 | adobject.ObjectGUID = new Guid(Convert.FromBase64String(propertyValue));
362 | break;
363 | case "objectSid":
364 | adobject.ObjectSid = new SecurityIdentifier(Convert.FromBase64String(propertyValue), 0);
365 | break;
366 | case "operatingSystem":
367 | adobject.OperatingSystem = propertyValue;
368 | break;
369 | case "pKIExtendedKeyUsage":
370 | adobject.PKIExtendedKeyUsage = propertyValues;
371 | break;
372 | case "primaryGroupID":
373 | adobject.PrimaryGroupID = int.Parse(propertyValue);
374 | break;
375 | case "pwdLastSet":
376 | adobject.PwdLastSet = FromLongToDateTime(long.Parse(propertyValue));
377 | break;
378 | case "sAMAccountName":
379 | adobject.SAMAccountName = propertyValue;
380 | break;
381 | case "scriptPath":
382 | adobject.ScriptPath = propertyValue;
383 | break;
384 | case "securityIdentifier":
385 | adobject.SecurityIdentifier = new SecurityIdentifier(Convert.FromBase64String(propertyValue), 0);
386 | break;
387 | case "servicePrincipalName":
388 | adobject.ServicePrincipalName = propertyValues;
389 | break;
390 | case "sIDHistory":
391 | adobject.SIDHistory = ParseSecurityIdentifierList(propertyValues);
392 | break;
393 | case "trustAttributes":
394 | adobject.TrustAttributes = int.Parse(propertyValue);
395 | break;
396 | case "trustDirection":
397 | adobject.TrustDirection = int.Parse(propertyValue);
398 | break;
399 | case "userAccountControl":
400 | adobject.UserAccountControl = int.Parse(propertyValue);
401 | break;
402 | case "whenCreated":
403 | adobject.WhenCreated = DateTime.ParseExact(propertyValue, "yyyyMMddHHmmss.f'Z'", CultureInfo.InvariantCulture);
404 | break;
405 | case "mail":
406 | adobject.Email = propertyValue;
407 | break;
408 | case "title":
409 | adobject.Title = propertyValue;
410 | break;
411 | case "homeDirectory":
412 | adobject.HomeDirectory = propertyValue;
413 | break;
414 | case "userPassword":
415 | adobject.UserPassword = propertyValue;
416 | break;
417 | case "unixUserPassword":
418 | adobject.UnixUserPassword = propertyValue;
419 | break;
420 | case "unicodePassword":
421 | adobject.UnicodePassword = propertyValue;
422 | break;
423 | case "msSFU30Password":
424 | adobject.MsSFU30Password = propertyValue;
425 | break;
426 | case "pKIExpirationPeriod":
427 | adobject.PKIExpirationPeriod = Convert.FromBase64String(propertyValue);
428 | break;
429 | case "pKIOverlapPeriod":
430 | adobject.PKIOverlapPeriod = Convert.FromBase64String(propertyValue);
431 | break;
432 | default:
433 | break;
434 | }
435 | }
436 | adObjects.Add(adobject);
437 | }
438 | return adObjects;
439 | }
440 |
441 | private static string ObjectToXml(T enumerate)
442 | {
443 | string XmlData;
444 | var serializer = new XmlSerializer(typeof(T));
445 | using (var writer = new StringWriter())
446 | {
447 | using (var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
448 | {
449 | var namespaces = new XmlSerializerNamespaces();
450 | namespaces.Add("d", "http://schemas.microsoft.com/2008/1/ActiveDirectory/Data");
451 | serializer.Serialize(xmlWriter, enumerate, namespaces);
452 | XmlData = writer.ToString();
453 | }
454 | }
455 |
456 | return XmlData;
457 | }
458 |
459 | private static DateTime FromLongToDateTime(long value)
460 | {
461 | try
462 | {
463 | return DateTime.FromFileTime(value);
464 | }
465 | catch
466 | {
467 | return DateTime.MinValue;
468 | }
469 | }
470 | }
471 |
472 | public class ADInfo
473 | {
474 | public string DefaultNamingContext { get; set; }
475 | public string DomainName { get; set; }
476 | }
477 |
478 | [XmlRoot("Enumerate", Namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration")]
479 | public class EnumerateRequest
480 | {
481 | [XmlElement("Filter")]
482 | public EnumerateRequestFilter Filter { get; set; }
483 |
484 | [XmlElement("Selection", Namespace = "http://schemas.microsoft.com/2008/1/ActiveDirectory")]
485 | public EnumerateRequestSelection Selection { get; set; }
486 | }
487 |
488 | public class EnumerateRequestFilter
489 | {
490 | [XmlAttribute("Dialect")]
491 | public string Dialect { get; set; }
492 |
493 | [XmlElement("LdapQuery", Namespace = "http://schemas.microsoft.com/2008/1/ActiveDirectory/Dialect/LdapQuery")]
494 | public LdapQuery LdapQuery { get; set; }
495 |
496 | public EnumerateRequestFilter()
497 | {
498 | Dialect = "http://schemas.microsoft.com/2008/1/ActiveDirectory/Dialect/LdapQuery";
499 | }
500 | }
501 |
502 | public class LdapQuery
503 | {
504 | [XmlElement("Filter")]
505 | public string QueryFilter { get; set; }
506 |
507 | [XmlElement("BaseObject")]
508 | public string BaseObject { get; set; }
509 |
510 | [XmlElement("Scope")]
511 | public string Scope { get; set; }
512 | }
513 |
514 | public class EnumerateRequestSelection
515 | {
516 | [XmlAttribute("Dialect")]
517 | public string Dialect { get; set; }
518 |
519 | [XmlElement("SelectionProperty")]
520 | public List SelectionProperties { get; set; }
521 |
522 | public EnumerateRequestSelection()
523 | {
524 | Dialect = "http://schemas.microsoft.com/2008/1/ActiveDirectory/Dialect/XPath-Level-1";
525 | SelectionProperties = new List();
526 | }
527 | }
528 |
529 | [XmlRoot(ElementName = "Pull", Namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration")]
530 | public class PullSearchResultsRequest
531 | {
532 | [XmlElement(ElementName = "EnumerationContext")]
533 | public string EnumerationContext { get; set; }
534 |
535 | [XmlElement(ElementName = "MaxElements")]
536 | public int MaxElements { get; set; }
537 |
538 | [XmlElement(ElementName = "controls", Namespace = "http://schemas.microsoft.com/2008/1/ActiveDirectory")]
539 | public Controls Controls { get; set; }
540 | }
541 |
542 | public class Controls
543 | {
544 | [XmlElement(ElementName = "control")]
545 | public List Control { get; set; }
546 | }
547 |
548 | public class Control
549 | {
550 | [XmlAttribute(AttributeName = "type")]
551 | public string Type { get; set; }
552 |
553 | [XmlElement(ElementName = "controlValue")]
554 | public string ControlValue { get; set; }
555 | }
556 |
557 |
558 |
559 | }
560 |
--------------------------------------------------------------------------------