├── 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 | ![logo](soaphound-logo.png) 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 | --------------------------------------------------------------------------------