feat<st>: subdomain takeover checks

This commit is contained in:
vmfunc
2024-10-12 22:52:27 +02:00
parent 56516e28e2
commit a2f2a51701
4 changed files with 183 additions and 1 deletions
+1
View File
@@ -31,6 +31,7 @@ _(__ )_ / _ __/
- 📦 CMS detection
- 🔍 HTTP Header Analysis
- ☁️ C3 Misconfiguration Scanner
- 🔍 Subdomain Takeover Checks
## Contributing and support
+2
View File
@@ -28,6 +28,7 @@ type Settings struct {
CMS bool
Headers bool
CloudStorage bool
SubdomainTakeover bool
}
const (
@@ -69,6 +70,7 @@ func Parse() *Settings {
flagSet.BoolVar(&settings.CMS, "cms", false, "Enable CMS detection"),
flagSet.BoolVar(&settings.Headers, "headers", false, "Enable HTTP Header Analysis"),
flagSet.BoolVar(&settings.CloudStorage, "c3", false, "Enable C3 Misconfiguration Scan"),
flagSet.BoolVar(&settings.SubdomainTakeover, "st", false, "Enable Subdomain Takeover Check"),
)
flagSet.CreateGroup("runtime", "Runtime",
+154
View File
@@ -0,0 +1,154 @@
package scan
import (
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
"os"
"sync"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/styles"
"github.com/dropalldatabases/sif/pkg/logger"
)
type SubdomainTakeoverResult struct {
Subdomain string `json:"subdomain"`
Vulnerable bool `json:"vulnerable"`
Service string `json:"service,omitempty"`
}
func SubdomainTakeover(url string, dnsResults []string, timeout time.Duration, threads int, logdir string) ([]SubdomainTakeoverResult, error) {
fmt.Println(styles.Separator.Render("🔍 Starting " + styles.Status.Render("Subdomain Takeover Vulnerability Check") + "..."))
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "Subdomain Takeover Vulnerability Check"); err != nil {
log.Errorf("Error creating log file: %v", err)
return nil, err
}
}
subdomainlog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "Subdomain Takeover 🔍",
})
client := &http.Client{
Timeout: timeout,
}
var wg sync.WaitGroup
wg.Add(threads)
resultsChan := make(chan SubdomainTakeoverResult, len(dnsResults))
for thread := 0; thread < threads; thread++ {
go func(thread int) {
defer wg.Done()
for i, subdomain := range dnsResults {
if i%threads != thread {
continue
}
vulnerable, service := checkSubdomainTakeover(subdomain, client)
result := SubdomainTakeoverResult{
Subdomain: subdomain,
Vulnerable: vulnerable,
Service: service,
}
resultsChan <- result
if vulnerable {
subdomainlog.Warnf("Potential subdomain takeover: %s (%s)", styles.Highlight.Render(subdomain), service)
if logdir != "" {
logger.Write(sanitizedURL, logdir, fmt.Sprintf("Potential subdomain takeover: %s (%s)\n", subdomain, service))
}
} else {
subdomainlog.Infof("Subdomain not vulnerable: %s", subdomain)
}
}
}(thread)
}
go func() {
wg.Wait()
close(resultsChan)
}()
var results []SubdomainTakeoverResult
for result := range resultsChan {
results = append(results, result)
}
return results, nil
}
func checkSubdomainTakeover(subdomain string, client *http.Client) (bool, string) {
resp, err := client.Get("http://" + subdomain)
if err != nil {
if strings.Contains(err.Error(), "no such host") {
// Check if CNAME exists
cname, err := net.LookupCNAME(subdomain)
if err == nil && cname != "" {
return true, "Dangling CNAME"
}
}
return false, ""
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
bodyString := string(body)
// Check for common takeover signatures in the response
signatures := map[string]string{
"GitHub Pages": "There isn't a GitHub Pages site here.",
"Heroku": "No such app",
"Shopify": "Sorry, this shop is currently unavailable.",
"Tumblr": "There's nothing here.",
"WordPress": "Do you want to register *.wordpress.com?",
"Amazon S3": "The specified bucket does not exist",
"Bitbucket": "Repository not found",
"Ghost": "The thing you were looking for is no longer here, or never was",
"Pantheon": "The gods are wise, but do not know of the site which you seek.",
"Fastly": "Fastly error: unknown domain",
"Zendesk": "Help Center Closed",
"Teamwork": "Oops - We didn't find your site.",
"Helpjuice": "We could not find what you're looking for.",
"Helpscout": "No settings were found for this company:",
"Cargo": "If you're moving your domain away from Cargo you must make this configuration through your registrar's DNS control panel.",
"Uservoice": "This UserVoice subdomain is currently available!",
"Surge": "project not found",
"Intercom": "This page is reserved for artistic dogs.",
"Webflow": "The page you are looking for doesn't exist or has been moved.",
"Kajabi": "The page you were looking for doesn't exist.",
"Thinkific": "You may have mistyped the address or the page may have moved.",
"Tave": "Sorry, this page is no longer available.",
"Wishpond": "https://www.wishpond.com/404?campaign=true",
"Aftership": "Oops.</h2><p class=\"text-muted text-tight\">The page you're looking for doesn't exist.",
"Aha": "There is no portal here ... sending you back to Aha!",
"Brightcove": "<p class=\"bc-gallery-error-code\">Error Code: 404</p>",
"Bigcartel": "<h1>Oops! We couldn&#8217;t find that page.</h1>",
"Activecompaign": "alt=\"LIGHTTPD - fly light.\"",
"Compaignmonitor": "Double check the URL or <a href=\"mailto:help@createsend.com",
"Acquia": "The site you are looking for could not be found.",
"Proposify": "If you need immediate assistance, please contact <a href=\"mailto:support@proposify.biz",
"Simplebooklet": "We can't find this <a href=\"https://simplebooklet.com",
"Getresponse": "With GetResponse Landing Pages, lead generation has never been easier",
"Vend": "Looks like you've traveled too far into cyberspace.",
"Jetbrains": "is not a registered InCloud YouTrack.",
"Azure": "404 Web Site not found.",
}
for service, signature := range signatures {
if strings.Contains(bodyString, signature) {
return true, service
}
}
return false, ""
}
+26 -1
View File
@@ -116,13 +116,28 @@ func (app *App) Run() error {
}
}
var dnsResults []string
if app.settings.Dnslist != "none" {
result, err := scan.Dnslist(app.settings.Dnslist, url, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running dns scan: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"dnslist", result})
dnsResults = result // Store the DNS results
}
// Only run subdomain takeover check if DNS scan is enabled
if app.settings.SubdomainTakeover {
result, err := scan.SubdomainTakeover(url, dnsResults, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running Subdomain Takeover Vulnerability Check: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"subdomain_takeover", result})
}
}
} else if app.settings.SubdomainTakeover {
log.Warnf("Subdomain Takeover check is enabled but DNS scan is disabled. Skipping Subdomain Takeover check.")
}
if app.settings.Ports != "none" {
@@ -193,6 +208,16 @@ func (app *App) Run() error {
}
}
if app.settings.SubdomainTakeover {
// Pass the dnsResults to the SubdomainTakeover function
result, err := scan.SubdomainTakeover(url, dnsResults, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running Subdomain Takeover Vulnerability Check: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"subdomain_takeover", result})
}
}
if app.settings.ApiMode {
result := UrlResult{
Url: url,
@@ -216,4 +241,4 @@ func (app *App) Run() error {
}
return nil
}
}