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
41 changes: 27 additions & 14 deletions ReadMe.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
<div align=center>

<pre>

███████╗██╗ ██╗██████╗ ██████╗ ██████╗ ███╗ ███╗██╗███╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗
██╔════╝██║ ██║██╔══██╗██╔══██╗██╔═══██╗████╗ ████║██║████╗ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗
███████╗██║ ██║██████╔╝██║ ██║██║ ██║██╔████╔██║██║██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝
╚════██║██║ ██║██╔══██╗██║ ██║██║ ██║██║╚██╔╝██║██║██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗
███████║╚██████╔╝██████╔╝██████╔╝╚██████╔╝██║ ╚═╝ ██║██║██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║
╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝

</pre>

![GitHub Actions CI](https://github.com/Stratus-Security/Subdominator/workflows/CI/badge.svg)
![GitHub all releases](https://img.shields.io/github/downloads/Stratus-Security/Subdominator/total)

# Subdominator 🚀
</div>

## 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
Expand All @@ -25,7 +38,7 @@ Or to quickly check a single domain, run:
Subdominator -d sub.example.com
```

## Options 🎛️
## Options
```
-d, --domain <domain> A single domain to check
-l, --list <list> A list of domains to check (line delimited)
Expand Down Expand Up @@ -61,25 +74,25 @@ 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
```
Comment on lines +77 to 81

## Demo
The tool running across 1000 passively gathered subdomains:
![Demo](https://raw.githubusercontent.com/Stratus-Security/Subdominator/master/Demo.gif)

## Benchmark 📊
## Benchmark
A benchmark was run across ~100,000 subdomains to compare performance with other popular tools
| Tool | Threads | Time Taken |
|--------------|---------|--------------------|
| **Subdominator** | 50 | 19 minutes, 8 seconds |
| 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.
Expand All @@ -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 | ✅ | ❌ | ❌ |
Expand All @@ -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.
Expand Down Expand Up @@ -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 |
| Zoho Forms India | Vulnerable |
3 changes: 2 additions & 1 deletion Subdominator.Tests/FingerprintTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public async Task Setup()
}

[TestMethod]

public void ShouldNotHaveDuplicateServices()
{
Assert.IsTrue(
Expand Down Expand Up @@ -79,4 +80,4 @@ public async Task ShouldExcludeEdgeCaseFingerprints()
var filteredFingerprints = await hijack.GetFingerprintsAsync(true);
Assert.IsFalse(filteredFingerprints.Any(f => f.Status.ToLower() == "edge case"));
}
}
}
20 changes: 20 additions & 0 deletions Subdominator.Tests/MatcherTests.cs
Original file line number Diff line number Diff line change
@@ -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."));
Comment on lines +9 to +10
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"));
}
}
4 changes: 2 additions & 2 deletions Subdominator/Models/Fingerprint.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;

namespace Subdominator;

Expand All @@ -15,7 +15,7 @@ public class Fingerprint

[JsonPropertyName("fingerprint")]
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
public List<string> FingerprintTexts { get; set; }
public List<string> FingerprintTexts { get; set; } = new();

[JsonPropertyName("http_status")]
public int? HttpStatus { get; set; }
Expand Down
3 changes: 3 additions & 0 deletions Subdominator/Properties/Assemblyinfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Subdominator.Tests")]
52 changes: 47 additions & 5 deletions Subdominator/SubdomainHijack.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using DnsClient;
using DnsClient;
using Microsoft.Extensions.Configuration;
using Nager.PublicSuffix;
using Nager.PublicSuffix.RuleProviders.CacheProviders;
Expand All @@ -8,6 +8,7 @@
using System.Collections.Concurrent;
using System.Text;
using System.Text.Json;
using System.Net;

namespace Subdominator;

Expand Down Expand Up @@ -400,9 +401,9 @@ public async Task<TakeoverResult> 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)
Expand Down Expand Up @@ -462,7 +463,7 @@ public async Task<TakeoverResult> 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);
}
Expand Down Expand Up @@ -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');
}
}
Loading