@@ -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]
14131442func 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
37733727func printASRHelp () {
0 commit comments