diff --git a/cmd/main.go b/cmd/main.go index bf88f05..fbdd1c6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,10 +1,13 @@ package main import ( + "encoding/json" "flag" "fmt" + "html/template" "log" "os" + "strings" "time" "github.com/zcyberseclab/zscan/pkg/stage" @@ -25,6 +28,7 @@ func main() { censysAPIKey := flag.String("censys-api-key", "", "Censys API Key") censysSecret := flag.String("censys-secret", "", "Censys API Secret") versionFlag := flag.Bool("version", false, "Show version information") + outputFormat := flag.String("output", "", "Output format: json, html, or md") flag.Parse() if *versionFlag { @@ -69,6 +73,138 @@ func main() { log.Printf("Error printing results: %v", err) } + // 处理输出格式 + if *outputFormat != "" { + if err := saveResults(results, *outputFormat); err != nil { + log.Printf("Error saving results: %v", err) + } else { + log.Printf("Results saved in %s format", *outputFormat) + } + } + duration := time.Since(startTime) log.Printf("\nScan completed in: %v\n", duration) } + +func saveResults(results []stage.Node, format string) error { + switch strings.ToLower(format) { + case "json": + return saveJSON(results) + case "html": + return saveHTML(results) + case "md": + return saveMarkdown(results) + default: + return fmt.Errorf("unsupported output format: %s", format) + } +} + +func saveJSON(results []stage.Node) error { + data, err := json.MarshalIndent(results, "", " ") + if err != nil { + return err + } + return os.WriteFile("zscan_results.json", data, 0644) +} + +func saveHTML(results []stage.Node) error { + tmpl := ` + + + ZScan Results + + + +

ZScan Results

+ {{range .}} +
+

IP: {{.IP}}

+ {{if .OS}}

OS: {{.OS}}

{{end}} + {{if .Tags}}

Tags: {{join .Tags ", "}}

{{end}} +

Ports:

+ {{range .Ports}} +
+

Port: {{.Port}} ({{.Protocol}})

+ {{if .Banner}}

Banner: {{.Banner}}

{{end}} + {{if .Version}}

Version: {{.Version}}

{{end}} +
+ {{end}} + {{if .Vulnerabilities}} +

Vulnerabilities:

+ {{range .Vulnerabilities}} +
+

CVE: {{.CVEID}}

+

Severity: {{.Severity}}

+
+ {{end}} + {{end}} +
+ {{end}} + +` + + funcMap := template.FuncMap{ + "join": strings.Join, + } + + t, err := template.New("results").Funcs(funcMap).Parse(tmpl) + if err != nil { + return err + } + + file, err := os.Create("zscan_results.html") + if err != nil { + return err + } + defer file.Close() + + return t.Execute(file, results) +} + +func saveMarkdown(results []stage.Node) error { + var md strings.Builder + + md.WriteString("# ZScan Results\n\n") + + for _, node := range results { + md.WriteString(fmt.Sprintf("## IP: %s\n\n", node.IP)) + + if node.OS != "" { + md.WriteString(fmt.Sprintf("**OS:** %s\n\n", node.OS)) + } + + if len(node.Tags) > 0 { + md.WriteString(fmt.Sprintf("**Tags:** %s\n\n", strings.Join(node.Tags, ", "))) + } + + md.WriteString("### Ports\n\n") + for _, port := range node.Ports { + md.WriteString(fmt.Sprintf("#### Port %d (%s)\n\n", port.Port, port.Protocol)) + if port.Banner != "" { + md.WriteString(fmt.Sprintf("- Banner: `%s`\n", port.Banner)) + } + if port.Version != "" { + md.WriteString(fmt.Sprintf("- Version: %s\n", port.Version)) + } + md.WriteString("\n") + } + + if len(node.Vulnerabilities) > 0 { + md.WriteString("### ⚠️ Vulnerabilities\n\n") + for _, vuln := range node.Vulnerabilities { + md.WriteString(fmt.Sprintf("- **%s** (Severity: %s)\n", + vuln.CVEID, vuln.Severity)) + } + md.WriteString("\n") + } + + md.WriteString("---\n\n") + } + + return os.WriteFile("zscan_results.md", []byte(md.String()), 0644) +}