Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ The key incantations are:

`-n` Disables computer discovery, takes a comma-separated list of hosts or input file to do share and file discovery on. Note if supplying a file, the input needs to be a path so C:\targets.txt or .\targets.txt as an example.

`-k` Excludes specific hosts from scanning. Accepts a comma-separated list of hostnames, IPs, and/or CIDR ranges (e.g. `-k 10.1.2.0/24,192.168.5.10`), or a path to a file containing one entry per line. Hostnames are resolved to IPs at startup. CIDR ranges are matched against each computer's resolved IP at scan time.

`-q` Queries AD for computers, then only scans those resolving within the specified CIDRs. Comma-separated. e.g. `-q 10.1.2.0/24,10.1.4.0/24`. Can be combined with `-k` to further exclude specific hosts.

`-y` TSV-formats the output.

`-b` Skips the LAIM rules that will find less-interesting stuff, tune it with a number between 0 and 3.
Expand Down
1 change: 1 addition & 0 deletions SnaffCore/Config/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public partial class Options
public string ComputerTargetsLdapFilter { get; set; } = "(objectClass=computer)";
public string ComputerExclusionFile { get; set; }
public List<string> ComputerExclusions { get; set; } = new List<string>();
public List<string> CidrInclusions { get; set; } = new List<string>();
public bool ScanSysvol { get; set; } = true;
public bool ScanNetlogon { get; set; } = true;
public bool ScanFoundShares { get; set; } = true;
Expand Down
55 changes: 55 additions & 0 deletions SnaffCore/NetworkUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Net;
using System.Text.RegularExpressions;

namespace SnaffCore
{
public static class NetworkUtils
{
// Matches network/prefix notation e.g. 10.1.2.0/24 or 2001:db8::/32
public static readonly Regex CidrRegex = new Regex(@"^\S+/\d+$", RegexOptions.Compiled);
/// <summary>
/// Checks whether an IP address falls within a CIDR range.
/// Supports both IPv4 and IPv6.
/// </summary>
public static bool IsInCidr(IPAddress address, string cidr)
{
// Split "network/prefixLength" e.g. "10.1.2.0/24" -> ["10.1.2.0", "24"]
string[] parts = cidr.Split('/');
if (parts.Length != 2 || !int.TryParse(parts[1], out int prefixLength))
return false;
if (!IPAddress.TryParse(parts[0], out IPAddress network))
return false;

// GetAddressBytes() returns big-endian bytes: 4 for IPv4, 16 for IPv6
// Mismatched lengths means comparing IPv4 against IPv6 or vice versa
byte[] addrBytes = address.GetAddressBytes();
byte[] netBytes = network.GetAddressBytes();

if (addrBytes.Length != netBytes.Length)
return false;

// Check all bytes that are fully covered by the prefix
// e.g. /24 covers 3 full bytes, /20 covers 2 full bytes
int fullBytes = prefixLength / 8;
int remainingBits = prefixLength % 8;

for (int i = 0; i < fullBytes; i++)
{
if (addrBytes[i] != netBytes[i])
return false;
}

// Check the partial byte (if the prefix doesn't fall on a byte boundary)
// e.g. /20 has 4 remaining bits: mask = 11110000 = 0xF0
// We only compare the masked bits, ignoring the host portion
if (remainingBits > 0)
{
byte mask = (byte)(0xFF << (8 - remainingBits));
if ((addrBytes[fullBytes] & mask) != (netBytes[fullBytes] & mask))
return false;
}

return true;
}
}
}
78 changes: 72 additions & 6 deletions SnaffCore/SnaffCon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class SnaffCon
private static TreeWalker TreeWalker;
private static FileScanner FileScanner;


private AdData _adData = AdData.AdDataInstance;

private DateTime StartTime { get; set; }
Expand Down Expand Up @@ -288,11 +289,19 @@ public void PrepDomainUserRules()
private void ShareDiscovery(string[] computerTargets)
{
Mq.Info("Starting to look for readable shares...");
int excludedCount = 0;
int cidrFilteredCount = 0;
bool hasCidrInclusions = MyOptions.CidrInclusions.Count > 0;
foreach (string computer in computerTargets)
{
if (CheckExclusions(computer))
{
// skip any that are in the exclusion list
excludedCount++;
continue;
}
if (hasCidrInclusions && !CheckInclusions(computer))
{
cidrFilteredCount++;
continue;
}
// Perform reverse lookup if the computer is an IP address
Expand Down Expand Up @@ -334,6 +343,16 @@ private void ShareDiscovery(string[] computerTargets)
}
});
}
int postExclusion = computerTargets.Length - excludedCount;
int included = postExclusion - cidrFilteredCount;
if (excludedCount > 0)
{
Mq.Info("Excluded " + excludedCount + " of " + computerTargets.Length + " computers (exclusion list or DNS resolution failure).");
}
if (cidrFilteredCount > 0)
{
Mq.Info("CIDR target filter: kept " + included + " of " + postExclusion + " remaining computers.");
}
Mq.Info("Created all sharefinder tasks.");
}

Expand All @@ -348,24 +367,25 @@ private bool CheckExclusions(string computer)
// check if it's an IP already
if (isIP(computer))
{
// check if it's in the exclusions list
if (MyOptions.ComputerExclusions.Contains(computer))
IPAddress addr = IPAddress.Parse(computer);
// check if it's in the exclusions list or falls within an excluded CIDR
if (MyOptions.ComputerExclusions.Contains(computer) || IsInAnyCidr(addr))
{
// if so, skip it
Mq.Degub("Excluded " + computer);
return true;
}
}
else {
else {
try
{
// resolve it
IPHostEntry result = Dns.GetHostEntry(computer);
// handle multiple IPs in response
foreach (IPAddress ipAddress in result.AddressList)
{
// if any of them is in the exclusion list
if (MyOptions.ComputerExclusions.Contains(ipAddress.ToString()))
// if any of them is in the exclusion list or falls within an excluded CIDR
if (MyOptions.ComputerExclusions.Contains(ipAddress.ToString()) || IsInAnyCidr(ipAddress))
{
Mq.Degub("Excluded " + computer + " at " + ipAddress);

Expand All @@ -388,6 +408,52 @@ private bool CheckExclusions(string computer)
return false;
}

private bool IsInAnyCidr(IPAddress address)
{
foreach (string entry in MyOptions.ComputerExclusions)
{
if (NetworkUtils.CidrRegex.IsMatch(entry) && NetworkUtils.IsInCidr(address, entry))
return true;
}
return false;
}

private bool CheckInclusions(string computer)
{
// Returns true if the computer resolves to an IP within any of the CIDR inclusions
try
{
IPAddress[] addresses;
if (isIP(computer))
{
addresses = new[] { IPAddress.Parse(computer) };
}
else
{
addresses = Dns.GetHostEntry(computer).AddressList;
}

foreach (IPAddress addr in addresses)
{
foreach (string cidr in MyOptions.CidrInclusions)
{
if (NetworkUtils.IsInCidr(addr, cidr))
{
Mq.Degub("CIDR included " + computer + " at " + addr);
return true;
}
}
}
}
catch (Exception ex)
{
Mq.Degub(ex.Message);
}

Mq.Degub("CIDR filtered out " + computer);
return false;
}

private void FileDiscovery(string[] pathTargets)
{
foreach (string pathTarget in pathTargets)
Expand Down
1 change: 1 addition & 0 deletions SnaffCore/SnaffCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
<Compile Include="FileScan\FileScanner.cs" />
<Compile Include="TreeWalk\TreeWalker.cs" />
<Compile Include="SnaffCon.cs" />
<Compile Include="NetworkUtils.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Nett.Coma">
Expand Down
Loading