Skip to content

Commit dad7d75

Browse files
committed
feat: Add HostsFile option to the zerodays module and refine host counting logic for vulnerability checks.
1 parent bb5c226 commit dad7d75

9 files changed

Lines changed: 390 additions & 305 deletions

File tree

AutoSubRecon

Lines changed: 0 additions & 1 deletion
This file was deleted.

cmd/autoar/main.go

Lines changed: 139 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,13 +1408,51 @@ func generateKeyhackCommand(t db.KeyhackTemplate, apiKey string) string {
14081408
return strings.Join(curlParts, " ")
14091409
}
14101410

1411+
func printZerodaysHelp() {
1412+
fmt.Println("\033[1;34mZerodays Scanner - Vulnerability Scanning Tool\033[0m")
1413+
fmt.Println("\n\033[1mUsage:\033[0m")
1414+
fmt.Println(" autoar zerodays scan [targets] [options]")
1415+
fmt.Println(" autoar zerodays [targets] [options] (shorthand)")
1416+
1417+
fmt.Println("\n\033[1mTargets (at least one required):\033[0m")
1418+
fmt.Println(" -d, --domain <domain> Scan a root domain (e.g., example.com)")
1419+
fmt.Println(" -s, --subdomain <url> Scan a single subdomain/URL (e.g., api.example.com)")
1420+
fmt.Println(" -f, --file <file> Scan hosts from a domains file")
1421+
fmt.Println(" -l, --live-hosts <file> Scan hosts from a live hosts file")
1422+
fmt.Println(" <domain> Direct shorthand for -d <domain>")
1423+
1424+
fmt.Println("\n\033[1mOptions:\033[0m")
1425+
fmt.Println(" -t, --threads <n> Number of concurrent threads (default: 100)")
1426+
fmt.Println(" --cve <cve_id> Specific CVE to check (can be specified multiple times)")
1427+
fmt.Println(" Supported: CVE-2025-55182 (React2Shell), CVE-2025-14847 (MongoDB)")
1428+
fmt.Println(" --dos-test Enable DoS testing for React2Shell")
1429+
fmt.Println(" --enable-source-exposure Enable source exposure check for React2Shell")
1430+
fmt.Println(" --mongodb-host <host> Explicit host for MongoDB check")
1431+
fmt.Println(" --mongodb-port <port> Explicit port for MongoDB check (default: 27017)")
1432+
fmt.Println(" --silent Output only vulnerable targets")
1433+
fmt.Println(" help, -h, --help Show this help message")
1434+
1435+
fmt.Println("\n\033[1mExamples:\033[0m")
1436+
fmt.Println(" autoar zerodays principal.com")
1437+
fmt.Println(" autoar zerodays -l subs.txt --silent")
1438+
fmt.Println(" autoar zerodays scan -s https://vulnerable-api.com --dos-test")
1439+
}
1440+
14111441
// handleZerodaysCommand handles CLI zerodays scan
1412-
// Usage: zerodays scan -d <domain> | -s <subdomain> | -f <domains_file> [-t <threads>] [--cve <cve>] [--dos-test] [--enable-source-exposure] [--mongodb-host <host>] [--mongodb-port <port>] [--silent]
14131442
func handleZerodaysCommand(args []string) error {
1414-
if len(args) == 0 || args[0] != "scan" {
1415-
return fmt.Errorf("usage: zerodays scan -d <domain> | -s <subdomain> | -f <domains_file> [-t <threads>] [--cve <cve>] [--dos-test] [--enable-source-exposure] [--mongodb-host <host>] [--mongodb-port <port>] [--silent]")
1443+
if len(args) == 0 || args[0] == "help" || args[0] == "-h" || args[0] == "--help" {
1444+
printZerodaysHelp()
1445+
return nil
1446+
}
1447+
1448+
// Make "scan" keyword optional
1449+
if args[0] == "scan" {
1450+
args = args[1:]
1451+
if len(args) == 0 {
1452+
printZerodaysHelp()
1453+
return nil
1454+
}
14161455
}
1417-
args = args[1:]
14181456

14191457
var domain, subdomain, domainsFile string
14201458
threads := 100
@@ -1440,12 +1478,13 @@ func handleZerodaysCommand(args []string) error {
14401478
subdomain = strings.TrimSuffix(subdomain, "/")
14411479
i++
14421480
}
1443-
case "-f", "--file":
1481+
case "-f", "--file", "-l", "--live-hosts":
14441482
if i+1 < len(args) {
14451483
domainsFile = args[i+1]
14461484
i++
14471485
}
14481486
case "-t", "--threads":
1487+
// ... (rest of the flags unchanged)
14491488
if i+1 < len(args) {
14501489
if t, err := strconv.Atoi(args[i+1]); err == nil {
14511490
threads = t
@@ -1475,214 +1514,119 @@ func handleZerodaysCommand(args []string) error {
14751514
}
14761515
case "--silent":
14771516
silent = true
1517+
default:
1518+
// If it doesn't start with "-" and we don't have a domain yet, treat it as a domain shorthand
1519+
if !strings.HasPrefix(args[i], "-") && domain == "" && subdomain == "" && domainsFile == "" {
1520+
domain = args[i]
1521+
}
14781522
}
14791523
}
14801524

1481-
// Validate that exactly one input method is provided
1482-
inputCount := 0
1483-
if domain != "" {
1484-
inputCount++
1485-
}
1486-
if subdomain != "" {
1487-
inputCount++
1488-
}
1489-
if domainsFile != "" {
1490-
inputCount++
1525+
// Validate input
1526+
if domain == "" && subdomain == "" && domainsFile == "" {
1527+
fmt.Println("\033[1;31mError: Either domain, subdomain, or hosts file must be provided\033[0m\n")
1528+
printZerodaysHelp()
1529+
return fmt.Errorf("missing target")
14911530
}
14921531

1493-
if inputCount == 0 {
1494-
return fmt.Errorf("either -d <domain>, -s <subdomain>, or -f <domains_file> must be provided")
1495-
}
1496-
if inputCount > 1 {
1497-
return fmt.Errorf("cannot use -d, -s, and -f together - use only one")
1532+
// Prepare zerodays options
1533+
zerodaysOpts := zerodays.Options{
1534+
Domain: domain,
1535+
Subdomain: subdomain,
1536+
HostsFile: domainsFile,
1537+
Threads: threads,
1538+
DOSTest: dosTest,
1539+
EnableSourceExposure: enableSourceExposure,
1540+
Silent: silent,
1541+
CVEs: cves,
1542+
MongoDBHost: mongoDBHost,
1543+
MongoDBPort: mongoDBPort,
14981544
}
14991545

1500-
var domains []string
1501-
var isSubdomainMode bool
1502-
1546+
// Determine output directory name
1547+
targetName := ""
15031548
if subdomain != "" {
1504-
// Single subdomain mode
1505-
domains = []string{subdomain}
1506-
isSubdomainMode = true
1549+
targetName = subdomain
15071550
} else if domain != "" {
1508-
// Single domain mode
1509-
domains = []string{domain}
1510-
isSubdomainMode = false
1551+
targetName = domain
15111552
} else {
1512-
// Read domains from file
1513-
file, err := os.Open(domainsFile)
1514-
if err != nil {
1515-
return fmt.Errorf("failed to open domains file: %w", err)
1516-
}
1517-
defer file.Close()
1518-
1519-
scanner := bufio.NewScanner(file)
1520-
for scanner.Scan() {
1521-
line := strings.TrimSpace(scanner.Text())
1522-
if line != "" && !strings.HasPrefix(line, "#") {
1523-
domains = append(domains, line)
1524-
}
1525-
}
1526-
if err := scanner.Err(); err != nil {
1527-
return fmt.Errorf("error reading domains file: %w", err)
1528-
}
1529-
if len(domains) == 0 {
1530-
return fmt.Errorf("no valid domains found in file")
1531-
}
1532-
isSubdomainMode = false // File mode defaults to domain mode
1553+
targetName = "file_scan_" + filepath.Base(domainsFile)
15331554
}
15341555

1535-
// Set silent mode for file scanning
1536-
os.Setenv("AUTOAR_SILENT", "true")
1537-
defer os.Unsetenv("AUTOAR_SILENT")
1556+
// Set silent mode for file scanning if many targets are expected
1557+
if domainsFile != "" {
1558+
os.Setenv("AUTOAR_SILENT", "true")
1559+
}
15381560

1539-
// Print initial status (only if not silent)
1561+
// Print initial status
15401562
if !silent {
1541-
if isSubdomainMode {
1542-
fmt.Fprintf(os.Stderr, "Starting zerodays scan for subdomain: %s\n", subdomain)
1543-
} else {
1544-
fmt.Fprintf(os.Stderr, "Starting zerodays scan for %d domain(s)...\n", len(domains))
1545-
}
1546-
if len(cves) > 0 {
1547-
fmt.Fprintf(os.Stderr, "CVEs to check: %s\n", strings.Join(cves, ", "))
1563+
if domainsFile != "" {
1564+
fmt.Fprintf(os.Stderr, "Starting zerodays batch scan for hosts in: %s\n", domainsFile)
15481565
} else {
1549-
fmt.Fprintf(os.Stderr, "CVEs to check: All (CVE-2025-55182, CVE-2025-14847)\n")
1550-
}
1551-
if len(domains) > 1 {
1552-
fmt.Fprintf(os.Stderr, "Processing sequentially...\n\n")
1566+
fmt.Fprintf(os.Stderr, "Starting zerodays scan for: %s\n", targetName)
15531567
}
15541568
}
15551569

1556-
// Process each domain
1557-
for idx, targetDomain := range domains {
1558-
// Print progress immediately (only if not silent)
1559-
if !silent && len(domains) > 1 {
1560-
fmt.Fprintf(os.Stderr, "[%d/%d] Processing %s...\n", idx+1, len(domains), targetDomain)
1561-
}
1570+
// Run zerodays scan
1571+
result, err := zerodays.Run(zerodaysOpts)
1572+
if err != nil {
1573+
return fmt.Errorf("zerodays scan failed: %w", err)
1574+
}
15621575

1563-
// Prepare zerodays options
1564-
zerodaysOpts := zerodays.Options{
1565-
Threads: threads,
1566-
DOSTest: dosTest,
1567-
EnableSourceExposure: enableSourceExposure,
1568-
Silent: silent,
1569-
CVEs: cves,
1570-
MongoDBHost: mongoDBHost,
1571-
MongoDBPort: mongoDBPort,
1572-
}
1576+
// Save results
1577+
resultsDir := os.Getenv("AUTOAR_RESULTS_DIR")
1578+
if resultsDir == "" {
1579+
resultsDir = "new-results"
1580+
}
1581+
outputDir, _ := filepath.Abs(filepath.Join(resultsDir, targetName, "zerodays"))
1582+
1583+
if !silent {
1584+
fmt.Printf("[INFO] Saving results to %s...\n", outputDir)
1585+
}
1586+
1587+
if err := zerodays.SaveResults(result, outputDir); err != nil {
1588+
fmt.Fprintf(os.Stderr, "[WARN] Failed to save zerodays results: %v\n", err)
1589+
}
15731590

1574-
// Set Domain or Subdomain based on input mode
1575-
if isSubdomainMode {
1576-
zerodaysOpts.Subdomain = targetDomain
1577-
} else {
1578-
// For file mode, try to detect if it's a subdomain
1579-
parts := strings.Split(targetDomain, ".")
1580-
if len(parts) > 2 {
1581-
zerodaysOpts.Subdomain = targetDomain
1582-
} else {
1583-
zerodaysOpts.Domain = targetDomain
1584-
}
1591+
// Print summary output
1592+
if silent {
1593+
// Silent mode: output vulnerable hosts
1594+
for _, vuln := range result.React2ShellVulns {
1595+
fmt.Printf("%s [React2Shell:%s]\n", vuln.URL, vuln.Type)
15851596
}
1586-
1587-
// Run zerodays scan
1588-
result, err := zerodays.Run(zerodaysOpts)
1589-
if err != nil {
1590-
if !silent {
1591-
fmt.Printf("[%d/%d] %s: ERROR - %v\n", idx+1, len(domains), targetDomain, err)
1597+
for _, vuln := range result.MongoDBVulns {
1598+
if vuln.Vulnerable {
1599+
fmt.Printf("%s:%d [MongoDB:CVE-2025-14847]\n", vuln.Host, vuln.Port)
15921600
}
1593-
continue
1594-
}
1595-
1596-
// Save results (always save, even in silent mode)
1597-
resultsDir := os.Getenv("AUTOAR_RESULTS_DIR")
1598-
if resultsDir == "" {
1599-
resultsDir = "new-results"
1600-
}
1601-
outputDir := filepath.Join(resultsDir, targetDomain, "zerodays")
1602-
// Always save results, regardless of silent mode
1603-
// Log save attempt even in silent mode for debugging
1604-
if !silent {
1605-
fmt.Printf("[%d/%d] %s: Saving results to %s...\n", idx+1, len(domains), targetDomain, outputDir)
16061601
}
1607-
if err := zerodays.SaveResults(result, outputDir); err != nil {
1608-
// Always log save errors, even in silent mode (this is critical)
1609-
fmt.Fprintf(os.Stderr, "[WARN] Failed to save zerodays results to %s: %v\n", outputDir, err)
1610-
} else {
1611-
// Verify files were created (even in silent mode)
1612-
react2ShellFile := filepath.Join(outputDir, "react2shell-cve-2025-55182.txt")
1613-
mongoFile := filepath.Join(outputDir, "mongodb-cve-2025-14847.txt")
1614-
jsonFile := filepath.Join(outputDir, "zerodays-results.json")
1615-
1616-
// Check all expected files
1617-
filesExist := true
1618-
if _, err := os.Stat(react2ShellFile); err != nil {
1619-
fmt.Fprintf(os.Stderr, "[WARN] React2Shell results file not found: %s (error: %v)\n", react2ShellFile, err)
1620-
filesExist = false
1621-
}
1622-
if _, err := os.Stat(mongoFile); err != nil {
1623-
fmt.Fprintf(os.Stderr, "[WARN] MongoDB results file not found: %s (error: %v)\n", mongoFile, err)
1624-
filesExist = false
1625-
}
1626-
if _, err := os.Stat(jsonFile); err != nil {
1627-
fmt.Fprintf(os.Stderr, "[WARN] Zerodays JSON file not found: %s (error: %v)\n", jsonFile, err)
1628-
filesExist = false
1629-
}
1630-
1631-
if filesExist && !silent {
1632-
fmt.Printf("[%d/%d] %s: Results saved to %s\n", idx+1, len(domains), targetDomain, outputDir)
1633-
}
1634-
}
1635-
1636-
// Print output
1637-
if len(result.React2ShellVulns) > 0 || len(result.MongoDBVulns) > 0 {
1638-
if silent {
1639-
// Silent mode: output vulnerable hosts
1640-
for _, vuln := range result.React2ShellVulns {
1641-
fmt.Printf("%s [React2Shell:%s]\n", vuln.URL, vuln.Type)
1642-
}
1643-
for _, vuln := range result.MongoDBVulns {
1644-
if vuln.Vulnerable {
1645-
fmt.Printf("%s:%d [MongoDB:CVE-2025-14847]\n", vuln.Host, vuln.Port)
1646-
}
1647-
}
1648-
} else {
1649-
// Verbose mode: show full details
1650-
fmt.Printf("[%d/%d] %s: %d hosts scanned, %d vulnerable:\n", idx+1, len(domains), targetDomain, result.TotalHostsScanned, result.TotalVulnerable)
1651-
for _, vuln := range result.React2ShellVulns {
1652-
fmt.Printf(" - %s [React2Shell:%s] - %s\n", vuln.URL, vuln.Type, vuln.Severity)
1653-
}
1654-
for _, vuln := range result.MongoDBVulns {
1655-
if vuln.Vulnerable {
1656-
fmt.Printf(" - %s:%d [MongoDB:CVE-2025-14847] - Leaked %d bytes\n", vuln.Host, vuln.Port, len(vuln.LeakedData))
1657-
}
1658-
}
1659-
}
1660-
} else {
1661-
if !silent {
1662-
fmt.Printf("[%d/%d] %s: %d hosts scanned\n", idx+1, len(domains), targetDomain, result.TotalHostsScanned)
1602+
} else {
1603+
// Verbose mode: show full details
1604+
fmt.Printf("\n[+] Scan completed for %s\n", targetName)
1605+
fmt.Printf("[INFO] Total hosts scanned: %d\n", result.TotalHostsScanned)
1606+
fmt.Printf("[INFO] Total vulnerable hosts found: %d\n", result.TotalVulnerable)
1607+
1608+
if len(result.React2ShellVulns) > 0 {
1609+
fmt.Println("\n🔴 React2Shell (CVE-2025-55182) Findings:")
1610+
for _, vuln := range result.React2ShellVulns {
1611+
fmt.Printf(" - %s [%s] - %s\n", vuln.URL, vuln.Type, vuln.Severity)
16631612
}
16641613
}
1665-
1666-
// Cleanup domain directory after each domain scan
1667-
// BUT: Skip cleanup if running from subdomain/domain workflow (they need the files)
1668-
// Check if we're being called from another workflow by checking for workflow environment variables
1669-
isWorkflowRun := os.Getenv("AUTOAR_CURRENT_CHANNEL_ID") != "" || os.Getenv("AUTOAR_CURRENT_SCAN_ID") != ""
1670-
// Also skip cleanup for subdomain mode (when -s flag is used) as files are needed by workflow
1671-
if !isWorkflowRun && !isSubdomainMode {
1672-
if err := cleanupDomainDirectoryForCLI(targetDomain); err != nil {
1673-
if !silent {
1674-
fmt.Printf("[WARN] Failed to cleanup domain directory for %s: %v\n", targetDomain, err)
1614+
1615+
if len(result.MongoDBVulns) > 0 {
1616+
vulnFound := false
1617+
for _, vuln := range result.MongoDBVulns {
1618+
if vuln.Vulnerable {
1619+
if !vulnFound {
1620+
fmt.Println("\n🔴 MongoDB (CVE-2025-14847) Findings:")
1621+
vulnFound = true
1622+
}
1623+
fmt.Printf(" - %s:%d - VULNERABLE (leaked %d bytes)\n", vuln.Host, vuln.Port, len(vuln.LeakedData))
16751624
}
16761625
}
1677-
} else if !silent {
1678-
// Log that we're skipping cleanup
1679-
if isWorkflowRun {
1680-
fmt.Printf("[DEBUG] Skipping cleanup (workflow run detected)\n")
1681-
} else if isSubdomainMode {
1682-
fmt.Printf("[DEBUG] Skipping cleanup (subdomain mode - files needed by workflow)\n")
1683-
}
16841626
}
1627+
16851628
}
1629+
fmt.Printf("\n[OK] Detailed results saved to: %s\n", outputDir)
16861630

16871631
return nil
16881632
}
@@ -3767,7 +3711,17 @@ func handleASRCommand(args []string) error {
37673711
Resolvers: resolvers,
37683712
}
37693713

3770-
return asrmod.Run(context.Background(), opts)
3714+
err := asrmod.Run(context.Background(), opts)
3715+
if err == nil {
3716+
resultsDir := "new-results"
3717+
if d := os.Getenv("AUTOAR_RESULTS_DIR"); d != "" {
3718+
resultsDir = d
3719+
}
3720+
absPath, _ := filepath.Abs(filepath.Join(resultsDir, domain))
3721+
fmt.Printf("\n\x1b[32m[OK] ASR scan completed successfully!\x1b[0m\n")
3722+
fmt.Printf("\x1b[34m[INFO] Results saved to: %s\x1b[0m\n", absPath)
3723+
}
3724+
return err
37713725
}
37723726

37733727
func printASRHelp() {

0 commit comments

Comments
 (0)