Skip to content

Commit 25ff12c

Browse files
feat: add organization footprinting and asset discovery display
- Added detailed display of organization footprinting results showing domains, IP ranges, ASNs, and subsidiaries - Implemented comprehensive asset discovery results view with categorized assets (subdomains, IPs, services, URLs) - Added vulnerability test coverage display showing authentication and API security testing status - Enhanced results visualization with color-coded output and priority indicators for high-value assets - Improved progress
1 parent 2dbac62 commit 25ff12c

File tree

11 files changed

+5575
-61
lines changed

11 files changed

+5575
-61
lines changed

cmd/orchestrator_main.go

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ package cmd
44
import (
55
"context"
66
"fmt"
7+
"strings"
78
"time"
89

910
"github.com/CodeMonkeyCybersecurity/shells/internal/core"
11+
"github.com/CodeMonkeyCybersecurity/shells/internal/discovery"
1012
"github.com/CodeMonkeyCybersecurity/shells/internal/logger"
1113
"github.com/CodeMonkeyCybersecurity/shells/internal/orchestrator"
1214
"github.com/CodeMonkeyCybersecurity/shells/internal/validation"
15+
"github.com/CodeMonkeyCybersecurity/shells/pkg/correlation"
1316
"github.com/CodeMonkeyCybersecurity/shells/pkg/types"
1417
"github.com/fatih/color"
1518
"github.com/spf13/cobra"
@@ -83,6 +86,16 @@ func runIntelligentOrchestrator(ctx context.Context, target string, cmd *cobra.C
8386
return fmt.Errorf("orchestrator execution failed: %w", err)
8487
}
8588

89+
// Display organization footprinting results if available
90+
if result.OrganizationInfo != nil {
91+
displayOrganizationFootprinting(result.OrganizationInfo)
92+
}
93+
94+
// Display asset discovery results if available
95+
if len(result.DiscoveredAssets) > 0 {
96+
displayAssetDiscoveryResults(result.DiscoveredAssets, result.DiscoverySession)
97+
}
98+
8699
// Display results
87100
displayOrchestratorResults(result, config)
88101

@@ -211,6 +224,9 @@ func displayOrchestratorResults(result *orchestrator.BugBountyResult, config orc
211224
fmt.Println()
212225
}
213226

227+
// Display vulnerability test coverage
228+
displayTestCoverage(result)
229+
214230
// Display findings by severity using modularized display functions
215231
if len(result.Findings) > 0 {
216232
log.Info("═══ Findings by Severity ═══", "component", "orchestrator_main")
@@ -272,5 +288,354 @@ func saveOrchestratorReport(result *orchestrator.BugBountyResult, filename strin
272288
return fmt.Errorf("report export not yet implemented")
273289
}
274290

291+
// displayOrganizationFootprinting displays organization footprinting results
292+
func displayOrganizationFootprinting(org *correlation.Organization) {
293+
if org == nil || org.Name == "" {
294+
return
295+
}
296+
297+
fmt.Println()
298+
color.Cyan("═══ Phase 0: Organization Footprinting ═══")
299+
fmt.Println()
300+
301+
// Organization info
302+
fmt.Printf(" Organization: %s\n", color.GreenString(org.Name))
303+
if org.Confidence > 0 {
304+
confidencePct := org.Confidence * 100
305+
confidenceColor := color.GreenString
306+
if confidencePct < 50 {
307+
confidenceColor = color.YellowString
308+
}
309+
fmt.Printf(" Confidence: %s\n", confidenceColor("%.1f%%", confidencePct))
310+
}
311+
312+
// Related domains
313+
if len(org.Domains) > 0 {
314+
fmt.Printf("\n ✓ Found %s related domains:\n", color.GreenString("%d", len(org.Domains)))
315+
displayLimit := 10
316+
for i, domain := range org.Domains {
317+
if i < displayLimit {
318+
fmt.Printf(" • %s\n", domain)
319+
}
320+
}
321+
if len(org.Domains) > displayLimit {
322+
fmt.Printf(" %s\n", color.CyanString("... and %d more", len(org.Domains)-displayLimit))
323+
}
324+
}
325+
326+
// IP ranges
327+
if len(org.IPRanges) > 0 {
328+
fmt.Printf("\n ✓ Found %s IP ranges:\n", color.GreenString("%d", len(org.IPRanges)))
329+
for _, ipRange := range org.IPRanges {
330+
fmt.Printf(" • %s\n", ipRange)
331+
}
332+
}
333+
334+
// ASNs
335+
if len(org.ASNs) > 0 {
336+
fmt.Printf("\n ✓ Found %s ASNs:\n", color.GreenString("%d", len(org.ASNs)))
337+
for _, asn := range org.ASNs {
338+
fmt.Printf(" • %s\n", asn)
339+
}
340+
}
341+
342+
// Certificates
343+
if len(org.Certificates) > 0 {
344+
fmt.Printf("\n ✓ Found %s SSL certificates\n", color.GreenString("%d", len(org.Certificates)))
345+
}
346+
347+
// Subsidiaries
348+
if len(org.Subsidiaries) > 0 {
349+
fmt.Printf("\n ✓ Found %s subsidiaries:\n", color.GreenString("%d", len(org.Subsidiaries)))
350+
for _, sub := range org.Subsidiaries {
351+
fmt.Printf(" • %s\n", sub)
352+
}
353+
}
354+
355+
// Sources
356+
if len(org.Sources) > 0 {
357+
fmt.Printf("\n Sources: %s\n", color.CyanString(strings.Join(org.Sources, ", ")))
358+
}
359+
360+
fmt.Println()
361+
}
362+
363+
// displayAssetDiscoveryResults displays detailed asset discovery results
364+
func displayAssetDiscoveryResults(assets []*discovery.Asset, session *discovery.DiscoverySession) {
365+
if len(assets) == 0 {
366+
return
367+
}
368+
369+
fmt.Println()
370+
color.Cyan("═══ Phase 1: Asset Discovery ═══")
371+
fmt.Println()
372+
373+
// Group assets by type
374+
assetsByType := make(map[discovery.AssetType][]*discovery.Asset)
375+
for _, asset := range assets {
376+
assetsByType[asset.Type] = append(assetsByType[asset.Type], asset)
377+
}
378+
379+
// Display subdomains
380+
if subdomains := assetsByType[discovery.AssetTypeSubdomain]; len(subdomains) > 0 {
381+
fmt.Printf(" ✓ Discovered %s subdomains:\n", color.GreenString("%d", len(subdomains)))
382+
displayLimit := 15
383+
for i, asset := range subdomains {
384+
if i < displayLimit {
385+
priority := ""
386+
if asset.Priority >= 80 { // High priority assets
387+
priority = color.RedString(" [HIGH VALUE]")
388+
}
389+
fmt.Printf(" • %s%s\n", asset.Value, priority)
390+
}
391+
}
392+
if len(subdomains) > displayLimit {
393+
fmt.Printf(" %s\n", color.CyanString("... and %d more", len(subdomains)-displayLimit))
394+
}
395+
fmt.Println()
396+
}
397+
398+
// Display IPs with open ports
399+
if ips := assetsByType[discovery.AssetTypeIP]; len(ips) > 0 {
400+
fmt.Printf(" ✓ Found %s IP addresses:\n", color.GreenString("%d", len(ips)))
401+
for i, asset := range ips {
402+
if i < 10 { // Show first 10
403+
ports := ""
404+
if p, ok := asset.Metadata["open_ports"]; ok {
405+
ports = fmt.Sprintf(" - Ports: %s", p)
406+
}
407+
fmt.Printf(" • %s%s\n", asset.Value, ports)
408+
}
409+
}
410+
if len(ips) > 10 {
411+
fmt.Printf(" %s\n", color.CyanString("... and %d more", len(ips)-10))
412+
}
413+
fmt.Println()
414+
}
415+
416+
// Display services with versions
417+
if services := assetsByType[discovery.AssetTypeService]; len(services) > 0 {
418+
fmt.Printf(" ✓ Found %s services:\n", color.GreenString("%d", len(services)))
419+
for i, asset := range services {
420+
if i < 10 { // Show first 10
421+
version := ""
422+
if v, ok := asset.Metadata["version"]; ok {
423+
version = fmt.Sprintf(" (%s)", v)
424+
}
425+
port := ""
426+
if p, ok := asset.Metadata["port"]; ok {
427+
port = fmt.Sprintf(":%s", p)
428+
}
429+
serviceName := asset.Value
430+
if name, ok := asset.Metadata["service_name"]; ok {
431+
serviceName = name
432+
}
433+
fmt.Printf(" • %s%s - %s%s\n", asset.IP, port, serviceName, version)
434+
}
435+
}
436+
if len(services) > 10 {
437+
fmt.Printf(" %s\n", color.CyanString("... and %d more", len(services)-10))
438+
}
439+
fmt.Println()
440+
}
441+
442+
// Display URLs/endpoints
443+
if urls := assetsByType[discovery.AssetTypeURL]; len(urls) > 0 {
444+
fmt.Printf(" ✓ Found %s URLs:\n", color.GreenString("%d", len(urls)))
445+
for i, asset := range urls {
446+
if i < 8 { // Show first 8
447+
fmt.Printf(" • %s\n", asset.Value)
448+
}
449+
}
450+
if len(urls) > 8 {
451+
fmt.Printf(" %s\n", color.CyanString("... and %d more", len(urls)-8))
452+
}
453+
fmt.Println()
454+
}
455+
456+
// Display technologies detected
457+
techSet := make(map[string]bool)
458+
for _, asset := range assets {
459+
for _, tech := range asset.Technology {
460+
techSet[tech] = true
461+
}
462+
}
463+
if len(techSet) > 0 {
464+
techs := make([]string, 0, len(techSet))
465+
for tech := range techSet {
466+
techs = append(techs, tech)
467+
}
468+
fmt.Printf(" ✓ Technologies detected: %s\n\n", color.CyanString(strings.Join(techs, ", ")))
469+
}
470+
471+
// High-value asset summary
472+
if session != nil && session.HighValueAssets > 0 {
473+
fmt.Printf(" %s Found %s high-value assets (login pages, admin panels, APIs)\n\n",
474+
color.RedString("⚠️"),
475+
color.RedString("%d", session.HighValueAssets),
476+
)
477+
}
478+
479+
fmt.Println()
480+
}
481+
482+
// displayTestCoverage shows what vulnerability tests were run and their results
483+
func displayTestCoverage(result *orchestrator.BugBountyResult) {
484+
fmt.Println()
485+
color.Cyan("═══ Phase 3: Vulnerability Testing ═══")
486+
fmt.Println()
487+
488+
// Authentication Testing
489+
fmt.Printf(" %s Authentication Testing:\n", color.CyanString("🔐"))
490+
if authPhase, ok := result.PhaseResults["auth"]; ok {
491+
if authPhase.Status == "completed" {
492+
if authPhase.Findings > 0 {
493+
fmt.Printf(" • Tested SAML/OAuth2/WebAuthn: %s (%d findings)\n",
494+
color.RedString("✗ Vulnerabilities found"), authPhase.Findings)
495+
} else {
496+
fmt.Printf(" • Tested SAML/OAuth2/WebAuthn: %s\n",
497+
color.GreenString("✓ No issues found"))
498+
}
499+
} else if authPhase.Status == "skipped" {
500+
fmt.Printf(" • %s (no authentication endpoints discovered)\n",
501+
color.YellowString("⊘ Not applicable"))
502+
}
503+
} else {
504+
fmt.Printf(" • %s (authentication testing disabled)\n",
505+
color.YellowString("⊘ Skipped"))
506+
}
507+
508+
// API Security Testing
509+
fmt.Printf("\n %s API Security Testing:\n", color.CyanString("🔌"))
510+
511+
// GraphQL
512+
if graphqlPhase, ok := result.PhaseResults["graphql"]; ok {
513+
if graphqlPhase.Status == "completed" {
514+
if graphqlPhase.Findings > 0 {
515+
fmt.Printf(" • GraphQL introspection: %s (%d findings)\n",
516+
color.RedString("✗ Issues found"), graphqlPhase.Findings)
517+
} else {
518+
endpointCount := 1 // Default
519+
if graphqlPhase.Findings == 0 {
520+
fmt.Printf(" • GraphQL introspection: %s\n",
521+
color.GreenString("✓ Tested %d endpoint, no issues", endpointCount))
522+
}
523+
}
524+
}
525+
} else {
526+
fmt.Printf(" • GraphQL testing: %s (no GraphQL endpoints found)\n",
527+
color.YellowString("⊘ Not applicable"))
528+
}
529+
530+
// REST API
531+
if restapiPhase, ok := result.PhaseResults["rest_api"]; ok {
532+
if restapiPhase.Status == "completed" {
533+
if restapiPhase.Findings > 0 {
534+
fmt.Printf(" • REST API security: %s (%d findings)\n",
535+
color.RedString("✗ Issues found"), restapiPhase.Findings)
536+
} else {
537+
fmt.Printf(" • REST API security: %s\n",
538+
color.GreenString("✓ No issues found"))
539+
}
540+
}
541+
} else {
542+
fmt.Printf(" • REST API testing: %s (API testing disabled)\n",
543+
color.YellowString("⊘ Skipped"))
544+
}
545+
546+
// Access Control Testing
547+
fmt.Printf("\n %s Access Control Testing:\n", color.CyanString("🔒"))
548+
549+
// IDOR
550+
if idorPhase, ok := result.PhaseResults["idor"]; ok {
551+
if idorPhase.Status == "completed" {
552+
if idorPhase.Findings > 0 {
553+
fmt.Printf(" • IDOR testing: %s (%d findings)\n",
554+
color.RedString("✗ Vulnerabilities found"), idorPhase.Findings)
555+
} else {
556+
fmt.Printf(" • IDOR testing: %s\n",
557+
color.GreenString("✓ No issues found"))
558+
}
559+
}
560+
} else {
561+
fmt.Printf(" • IDOR testing: %s (no suitable endpoints found)\n",
562+
color.YellowString("⊘ Not applicable"))
563+
}
564+
565+
// SCIM
566+
if scimPhase, ok := result.PhaseResults["scim"]; ok {
567+
if scimPhase.Status == "completed" {
568+
if scimPhase.Findings > 0 {
569+
fmt.Printf(" • SCIM vulnerabilities: %s (%d findings)\n",
570+
color.RedString("✗ Issues found"), scimPhase.Findings)
571+
} else {
572+
fmt.Printf(" • SCIM vulnerabilities: %s\n",
573+
color.GreenString("✓ No issues found"))
574+
}
575+
}
576+
} else {
577+
fmt.Printf(" • SCIM testing: %s (no SCIM endpoints found)\n",
578+
color.YellowString("⊘ Not applicable"))
579+
}
580+
581+
// Service Fingerprinting
582+
fmt.Printf("\n %s Service Fingerprinting:\n", color.CyanString("🔍"))
583+
if nmapPhase, ok := result.PhaseResults["nmap"]; ok {
584+
if nmapPhase.Status == "completed" {
585+
hostsScanned := 1 // Default
586+
if nmapPhase.Findings > 0 {
587+
fmt.Printf(" • Nmap scan: %s (%d hosts, %d services found)\n",
588+
color.GreenString("✓ Completed"), hostsScanned, nmapPhase.Findings)
589+
} else {
590+
fmt.Printf(" • Nmap scan: %s (%d host)\n",
591+
color.GreenString("✓ Completed"), hostsScanned)
592+
}
593+
} else if nmapPhase.Status == "failed" {
594+
fmt.Printf(" • Nmap scan: %s\n",
595+
color.RedString("✗ Failed"))
596+
if nmapPhase.Error != "" {
597+
fmt.Printf(" Error: %s\n", color.RedString(nmapPhase.Error))
598+
}
599+
}
600+
} else {
601+
fmt.Printf(" • Nmap scan: %s (service fingerprinting disabled)\n",
602+
color.YellowString("⊘ Skipped"))
603+
}
604+
605+
// Nuclei Scanning
606+
if nucleiPhase, ok := result.PhaseResults["nuclei"]; ok {
607+
if nucleiPhase.Status == "completed" {
608+
if nucleiPhase.Findings > 0 {
609+
fmt.Printf(" • Nuclei CVE scan: %s (%d findings)\n",
610+
color.RedString("✗ Vulnerabilities found"), nucleiPhase.Findings)
611+
} else {
612+
fmt.Printf(" • Nuclei CVE scan: %s\n",
613+
color.GreenString("✓ No CVEs found"))
614+
}
615+
}
616+
} else {
617+
fmt.Printf(" • Nuclei scanning: %s (nuclei not installed)\n",
618+
color.YellowString("⊘ Skipped"))
619+
}
620+
621+
// Summary
622+
fmt.Println()
623+
totalCategories := 0
624+
testedCategories := 0
625+
626+
for _, phase := range []string{"auth", "graphql", "rest_api", "idor", "scim", "nmap", "nuclei"} {
627+
totalCategories++
628+
if pr, ok := result.PhaseResults[phase]; ok && pr.Status == "completed" {
629+
testedCategories++
630+
}
631+
}
632+
633+
fmt.Printf(" Summary: %s/%s test categories executed, %s total findings\n\n",
634+
color.CyanString("%d", testedCategories),
635+
color.CyanString("%d", totalCategories),
636+
color.GreenString("%d", result.TotalFindings),
637+
)
638+
}
639+
275640
// Note: Helper functions (colorStatus, colorSeverity, displayTopFindings, etc.)
276641
// are now in display_helpers.go to avoid duplication across commands

0 commit comments

Comments
 (0)