diff --git a/ReadMe.md b/ReadMe.md
index f629dc6..6e579fc 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -1,21 +1,34 @@
+
+
+
+
+███████╗██╗ ██╗██████╗ ██████╗ ██████╗ ███╗ ███╗██╗███╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗
+██╔════╝██║ ██║██╔══██╗██╔══██╗██╔═══██╗████╗ ████║██║████╗ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗
+███████╗██║ ██║██████╔╝██║ ██║██║ ██║██╔████╔██║██║██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝
+╚════██║██║ ██║██╔══██╗██║ ██║██║ ██║██║╚██╔╝██║██║██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗
+███████║╚██████╔╝██████╔╝██████╔╝╚██████╔╝██║ ╚═╝ ██║██║██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║
+╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
+
+
+


-# Subdominator 🚀
+
## Welcome to the Subdominator Club!
-Meet **Subdominator**, your new favourite CLI tool for detecting subdomain takeovers. It's designed to be fast, accurate, and dependable, offering [a significant improvement over other available tools](https://www.stratussecurity.com/post/the-ultimate-subdomain-takeover-tool).
+- Meet **Subdominator**, your new favourite CLI tool for detecting subdomain takeovers. It's designed to be fast, accurate, and dependable, offering [a significant improvement over other available tools](https://www.stratussecurity.com/post/the-ultimate-subdomain-takeover-tool).
-🔍 Precision and speed are our goal. Subdominator delivers better results without the wait, see the benchmark and feature comparison below for details.
+- Precision and speed are our goal. Subdominator delivers better results without the wait, see the benchmark and feature comparison below for details.
-## Installing 🛠️
-To quickly, get up and running, you can download the latest release for [windows](https://github.com/Stratus-Security/Subdominator/releases/latest/download/Subdominator.exe) or [linux](https://github.com/Stratus-Security/Subdominator/releases/latest/download/Subdominator).
+## Installing
+- To quickly, get up and running, you can download the latest release for [windows](https://github.com/Stratus-Security/Subdominator/releases/latest/download/Subdominator.exe) or [linux](https://github.com/Stratus-Security/Subdominator/releases/latest/download/Subdominator).
Alternatively, download it via CLI (remove .exe for linux version):
```bash
wget https://github.com/Stratus-Security/Subdominator/releases/latest/download/Subdominator.exe
```
-## Quick Start 🚦
+## Quick Start
To quickly check a list of domains, simply run:
```bash
Subdominator -l subdomains.txt -o takeovers.txt
@@ -25,7 +38,7 @@ Or to quickly check a single domain, run:
Subdominator -d sub.example.com
```
-## Options 🎛️
+## Options
```
-d, --domain A single domain to check
-l, --list A list of domains to check (line delimited)
@@ -61,17 +74,17 @@ For example, this shows the same vulnerable domain and another non-vulnerable do
[-] www.stratussecurity.com
```
-Finally, if a domain is vulnerable and passes validation with the --validation flag, it will be prepended with a ✅.
+Finally, if a domain is vulnerable and passes validation with the --validation flag, it will be prepended with a .
These domains have been validated to be vulnerable with the services directly, not just the fingerprint. For example:
```
-✅ [Microsoft Azure] example.stratussecurity.com - CNAME: stratus-cdn-stg.azureedge.net
+ [Microsoft Azure] example.stratussecurity.com - CNAME: stratus-cdn-stg.azureedge.net
```
## Demo
The tool running across 1000 passively gathered subdomains:

-## Benchmark 📊
+## Benchmark
A benchmark was run across ~100,000 subdomains to compare performance with other popular tools
| Tool | Threads | Time Taken |
|--------------|---------|--------------------|
@@ -79,7 +92,7 @@ A benchmark was run across ~100,000 subdomains to compare performance with other
| Subjack | 50 | 2 hours, 30 minutes, 2 seconds |
| Subdover | 50 | 2 hours, 33 minutes, 27 seconds |
-## Key Features 🔥
+## Key Features
- **Advanced DNS Matching**: Supports DNS matching for CNAME, A, and AAAA records.
- **Recursive DNS Queries**: Performs in-depth queries to enhance accuracy and reduce false positives.
- **Intelligent Domain Matching**: Uses a custom `public_suffix_list.dat` for more effective domain matching.
@@ -89,7 +102,7 @@ A benchmark was run across ~100,000 subdomains to compare performance with other
- **Comprehensive Detection**: Capable of identifying takeovers missed by other tools.
- **Validation**: Dynamic takeover validation modules to check beyond fingerprints.
-## Feature Comparison 🥊
+## Feature Comparison
| Feature | Subdominator | Subjack | Subdover |
|----------------------------------|--------------|---------|----------|
| Advanced DNS Matching | ✅ | ❌ | ❌ |
@@ -104,7 +117,7 @@ A benchmark was run across ~100,000 subdomains to compare performance with other
| Fingerprints | 97 | 35 | 80 |
## Contributions
-Got a suggestion, fingerprint, or want to chip in? We're all ears! Open a PR or issue – this will keep subdominator on top! 😄
+Got a suggestion, fingerprint, or want to chip in? We're all ears! Open a PR or issue – this will keep subdominator on top!
## Fingerprints
The fingerprints and services are dynamically pulled from the [CanITakeOverXYZ repo](https://github.com/EdOverflow/can-i-take-over-xyz) as a source of truth. To fill in the gaps and correct incorrect fingerprints, this tool also has its own [custom fingerprints list](https://github.com/Stratus-Security/Subdominator/blob/master/Subdominator/custom_fingerprints.json) which is used in conjunction.
@@ -209,4 +222,4 @@ Below is the current list of services supported, to ignore edge cases use the `-
| Wufoo | Vulnerable |
| Zendesk | Edge case |
| Zoho Forms | Vulnerable |
-| Zoho Forms India | Vulnerable |
\ No newline at end of file
+| Zoho Forms India | Vulnerable |
diff --git a/Subdominator.Tests/FingerprintTests.cs b/Subdominator.Tests/FingerprintTests.cs
index 136a3aa..256de3e 100644
--- a/Subdominator.Tests/FingerprintTests.cs
+++ b/Subdominator.Tests/FingerprintTests.cs
@@ -17,6 +17,7 @@ public async Task Setup()
}
[TestMethod]
+
public void ShouldNotHaveDuplicateServices()
{
Assert.IsTrue(
@@ -79,4 +80,4 @@ public async Task ShouldExcludeEdgeCaseFingerprints()
var filteredFingerprints = await hijack.GetFingerprintsAsync(true);
Assert.IsFalse(filteredFingerprints.Any(f => f.Status.ToLower() == "edge case"));
}
-}
\ No newline at end of file
+}
diff --git a/Subdominator.Tests/MatcherTests.cs b/Subdominator.Tests/MatcherTests.cs
new file mode 100644
index 0000000..d99bfe0
--- /dev/null
+++ b/Subdominator.Tests/MatcherTests.cs
@@ -0,0 +1,20 @@
+namespace Subdominator.Tests;
+
+[TestClass]
+public class MatcherTests
+{
+ [TestMethod]
+ public void FingerprintMatchingShouldBeCaseInsensitiveAndNormalizeDnsValues()
+ {
+ Assert.IsTrue(Subdominator.SubdomainHijack.MatchesFingerprintValue("Foo.Example.COM.", "example.com"));
+ Assert.IsTrue(Subdominator.SubdomainHijack.MatchesFingerprintValue("151.101.12.34", "151.101."));
+ Assert.IsFalse(Subdominator.SubdomainHijack.MatchesFingerprintValue("foo.example.com", "bar.example.com"));
+ }
+
+ [TestMethod]
+ public void FingerprintBodyMatchingShouldIgnoreCase()
+ {
+ Assert.IsTrue(Subdominator.SubdomainHijack.ContainsFingerprintText("You Have Found A Missing Link", "missing link"));
+ Assert.IsFalse(Subdominator.SubdomainHijack.ContainsFingerprintText("No matching string here", "missing link"));
+ }
+}
diff --git a/Subdominator/Models/Fingerprint.cs b/Subdominator/Models/Fingerprint.cs
index 98e2871..ec1a5ee 100644
--- a/Subdominator/Models/Fingerprint.cs
+++ b/Subdominator/Models/Fingerprint.cs
@@ -1,4 +1,4 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
namespace Subdominator;
@@ -15,7 +15,7 @@ public class Fingerprint
[JsonPropertyName("fingerprint")]
[JsonConverter(typeof(SingleOrArrayConverter))]
- public List FingerprintTexts { get; set; }
+ public List FingerprintTexts { get; set; } = new();
[JsonPropertyName("http_status")]
public int? HttpStatus { get; set; }
diff --git a/Subdominator/Properties/Assemblyinfo.cs b/Subdominator/Properties/Assemblyinfo.cs
new file mode 100644
index 0000000..74a21a3
--- /dev/null
+++ b/Subdominator/Properties/Assemblyinfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Subdominator.Tests")]
diff --git a/Subdominator/SubdomainHijack.cs b/Subdominator/SubdomainHijack.cs
index 5e618cd..1653801 100644
--- a/Subdominator/SubdomainHijack.cs
+++ b/Subdominator/SubdomainHijack.cs
@@ -1,4 +1,4 @@
-using DnsClient;
+using DnsClient;
using Microsoft.Extensions.Configuration;
using Nager.PublicSuffix;
using Nager.PublicSuffix.RuleProviders.CacheProviders;
@@ -8,6 +8,7 @@
using System.Collections.Concurrent;
using System.Text;
using System.Text.Json;
+using System.Net;
namespace Subdominator;
@@ -400,9 +401,9 @@ public async Task IsDomainVulnerable(string domain, bool validat
// Check an individual fingerprint against and domain name and dns records
public async Task<(bool, MatchedRecord, MatchedLocation)> IsFingerprintVulnerable(Fingerprint fingerprint, CnameResolutionResult dns, string domain)
{
- var isCnameMatch = dns.Cnames.Any(r => fingerprint.Cnames.Any(r.Contains));
- var isAMatch = dns.A.Any(r => fingerprint.ARecords.Any(r.Contains));
- var isAaaaMatch = dns.AAAA.Any(r => fingerprint.AAAARecords.Any(r.Contains));
+ var isCnameMatch = dns.Cnames.Any(r => fingerprint.Cnames.Any(fingerprintValue => MatchesFingerprintValue(r, fingerprintValue)));
+ var isAMatch = dns.A.Any(r => fingerprint.ARecords.Any(fingerprintValue => MatchesFingerprintValue(r, fingerprintValue)));
+ var isAaaaMatch = dns.AAAA.Any(r => fingerprint.AAAARecords.Any(fingerprintValue => MatchesFingerprintValue(r, fingerprintValue)));
var matchedRecord = MatchedRecord.None;
if (isCnameMatch)
@@ -462,7 +463,7 @@ public async Task IsDomainVulnerable(string domain, bool validat
{
foreach (var fingerprintMatch in fingerprint.FingerprintTexts)
{
- if (!string.IsNullOrWhiteSpace(fingerprintMatch) && responseBody.Contains(fingerprintMatch))
+ if (ContainsFingerprintText(responseBody, fingerprintMatch))
{
return (true, matchedRecord, MatchedLocation.HttpBody);
}
@@ -707,4 +708,45 @@ private bool IsDomainRegistered(string whoisResponse)
}
}
}
+
+ internal static bool MatchesFingerprintValue(string actualValue, string fingerprintValue)
+ {
+ if (string.IsNullOrWhiteSpace(actualValue) || string.IsNullOrWhiteSpace(fingerprintValue))
+ {
+ return false;
+ }
+
+ var normalizedActual = NormalizeDnsValue(actualValue);
+ var normalizedFingerprint = NormalizeDnsValue(fingerprintValue);
+
+ if (LooksLikeIpPrefix(normalizedFingerprint))
+ {
+ return normalizedActual.StartsWith(normalizedFingerprint, StringComparison.OrdinalIgnoreCase);
+ }
+
+ if (normalizedFingerprint.Contains('.'))
+ {
+ return normalizedActual.Equals(normalizedFingerprint, StringComparison.OrdinalIgnoreCase)
+ || normalizedActual.EndsWith($".{normalizedFingerprint}", StringComparison.OrdinalIgnoreCase);
+ }
+
+ return normalizedActual.Contains(normalizedFingerprint, StringComparison.OrdinalIgnoreCase);
+ }
+
+ internal static bool ContainsFingerprintText(string responseBody, string fingerprintMatch)
+ {
+ return !string.IsNullOrWhiteSpace(responseBody)
+ && !string.IsNullOrWhiteSpace(fingerprintMatch)
+ && responseBody.Contains(fingerprintMatch, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static string NormalizeDnsValue(string value)
+ {
+ return value.Trim().TrimEnd('.');
+ }
+
+ private static bool LooksLikeIpPrefix(string value)
+ {
+ return value.All(character => char.IsDigit(character) || character is '.' or ':' or 'x' or 'X');
+ }
}