diff --git a/cmd/nessusCsvSrv.go b/cmd/nessusCsvSrv.go index 4a7f341..875234c 100644 --- a/cmd/nessusCsvSrv.go +++ b/cmd/nessusCsvSrv.go @@ -16,21 +16,20 @@ limitations under the License. package cmd import ( - "fmt" - "github.com/pafussell/gophermap/parser" "github.com/spf13/cobra" ) -// nessusCsvSrvCmd represents the nessusCsvSrv command var nessusCsvSrvCmd = &cobra.Command{ Use: "nessus-csv-srv", Short: "read the Nessus csv output and print out services found by the \"Service Detection\" plugin", Long: ``, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("nessus-csv-srv called") - p := parser.New(args[0], nil) - p.NessusPrettyServicesCSV() + RunE: func(cmd *cobra.Command, args []string) error { + p := parser.New(filePath, nil, Verbose) + if err := p.NessusPrettyServicesCSV(); err != nil { + return err + } + return nil }, } @@ -38,5 +37,4 @@ var filePath string func init() { rootCmd.AddCommand(nessusCsvSrvCmd) - nessusCsvSrvCmd.Flags().StringVarP(&filePath, "PATH", "f", "", "Nessus CSV file to parse") } diff --git a/cmd/nessusCsvWeb.go b/cmd/nessusCsvWeb.go index c812689..19c5420 100644 --- a/cmd/nessusCsvWeb.go +++ b/cmd/nessusCsvWeb.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "github.com/pafussell/gophermap/parser" "github.com/spf13/cobra" ) @@ -12,12 +10,12 @@ var nessusCsvWebCmd = &cobra.Command{ Short: "Read the Nessus csv output and print out all detected web servers", Long: ``, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("nessus-csv-web called") - p := parser.New(args[0], nil) + p := parser.New(filePath, nil, Verbose) p.NessusPrettyWebCSV() }, } func init() { rootCmd.AddCommand(nessusCsvWebCmd) + nessusCsvSrvCmd.Flags().StringVarP(&filePath, "PATH", "f", "", "Nessus CSV file to parse") } diff --git a/cmd/nessusXml.go b/cmd/nessusXml.go index 78acd32..ac3a7c9 100644 --- a/cmd/nessusXml.go +++ b/cmd/nessusXml.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "github.com/pafussell/gophermap/parser" "github.com/spf13/cobra" ) @@ -12,8 +10,7 @@ var nessusXmlCmd = &cobra.Command{ Short: "Read the Nessus xml output and print out services found by the \"Service Detection\" plugin", Long: ``, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("nessus-xml called") - p := parser.New(args[0], nil) + p := parser.New(filePath, nil, Verbose) p.NessusPrettyServiceXML() }, } diff --git a/cmd/nessusXmlHigh.go b/cmd/nessusXmlHigh.go index 256e1d2..3afc6d7 100644 --- a/cmd/nessusXmlHigh.go +++ b/cmd/nessusXmlHigh.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "github.com/pafussell/gophermap/parser" "github.com/spf13/cobra" ) @@ -12,8 +10,7 @@ var nessusXmlHighCmd = &cobra.Command{ Short: "parse high/crit vulns from nessus xml", Long: ``, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("nessus-xml-high called") - p := parser.New(args[0], nil) + p := parser.New(filePath, nil, Verbose) p.NessusPrettyHighCritXML() }, } diff --git a/cmd/nmap.go b/cmd/nmap.go index b05b56e..a708af1 100644 --- a/cmd/nmap.go +++ b/cmd/nmap.go @@ -14,7 +14,7 @@ var nmapCmd = &cobra.Command{ Long: ``, Run: func(cmd *cobra.Command, args []string) { fmt.Printf("nmap called, file = %v\n", args[0]) - p := parser.New(args[0], nil) + p := parser.New(filePath, nil, Verbose) p.NmapPrettyPrint() }, } diff --git a/cmd/root.go b/cmd/root.go index 1956de9..027567a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,37 +1,25 @@ package cmd import ( - "fmt" "os" "github.com/spf13/cobra" ) -var cfgFile string - -// rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "gophermap", Short: "A parser for various network and vulnerability tools and formats", Long: `A parser for Nessus, NMap and Rumble scanner results file in various formats`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, } -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} +var Verbose bool func init() { - cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") } -// initConfig reads in config file and ENV variables if set. -func initConfig() { +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } } diff --git a/cmd/rumble.go b/cmd/rumble.go index 79cc07d..fb8a2db 100644 --- a/cmd/rumble.go +++ b/cmd/rumble.go @@ -14,7 +14,7 @@ var rumbleCmd = &cobra.Command{ a lot of version data in banners in the JSON blob. Thus the dedicated format. `, Run: func(cmd *cobra.Command, args []string) { fmt.Println("rumble called") - p := parser.New(args[0], nil) + p := parser.New(filePath, nil, Verbose) p.RumblePrettyPrint() }, } diff --git a/parser/parser.go b/parser/parser.go index 26ec12a..e255e77 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -17,14 +17,15 @@ import ( type Parser struct { FilePath string - Logger io.Writer + Writer io.Writer + Verbose bool } -func New(file string, out io.Writer) (p *Parser) { +func New(file string, out io.Writer, verbose bool) (p *Parser) { if out != nil { - p = &Parser{FilePath: file, Logger: out} + p = &Parser{FilePath: file, Writer: out, Verbose: verbose} } else { - p = &Parser{FilePath: file, Logger: os.Stdout} + p = &Parser{FilePath: file, Writer: os.Stdout, Verbose: verbose} } return } @@ -32,13 +33,11 @@ func New(file string, out io.Writer) (p *Parser) { func (p *Parser) getNmapParser(w io.Writer) (*gn.NmapRun, error) { fb, err := ioutil.ReadFile(p.FilePath) if err != nil { - fmt.Fprintf(w, "Error opening scan file: %v\n", p.FilePath) return nil, err } n, err := gn.Parse(fb) if err != nil { - fmt.Fprintf(w, "Error parsing NMap file\n") return nil, err } return n, nil @@ -47,13 +46,11 @@ func (p *Parser) getNmapParser(w io.Writer) (*gn.NmapRun, error) { func (p *Parser) getNessusParser(w io.Writer) (*gne.NessusData, error) { fb, err := ioutil.ReadFile(p.FilePath) if err != nil { - fmt.Fprintf(w, "Error opening Nessus file: %v\n", p.FilePath) return nil, err } n, err := gne.Parse(fb) if err != nil { - fmt.Fprintf(w, "Error parsing Nessus file\n") return nil, err } return n, nil @@ -62,7 +59,6 @@ func (p *Parser) getNessusParser(w io.Writer) (*gne.NessusData, error) { func (p *Parser) getCsvRecords(w io.Writer) ([][]string, error) { fb, err := ioutil.ReadFile(p.FilePath) if err != nil { - fmt.Fprintf(w, "Error opening Nessus file: %v\n", p.FilePath) return nil, err } br := bytes.NewReader(fb) @@ -72,21 +68,46 @@ func (p *Parser) getCsvRecords(w io.Writer) ([][]string, error) { return records, err } +func (p *Parser) verboseNmapDump(np *gn.NmapRun) { + fmt.Fprintf(p.Writer, "Host count: %v\n", len(np.Hosts)) + fmt.Fprintf(p.Writer, "Scanner: %v\n", np.Scanner) + fmt.Fprintf(p.Writer, "Profile name: %v\n", np.ProfileName) + fmt.Fprintf(p.Writer, "Scan start time: %v\n", np.Start) +} + +func (p *Parser) verboseNessusDump(n *gne.NessusData) { + fmt.Fprintf(p.Writer, "Host count: %v\n", len(n.Report.ReportHosts)) + fmt.Fprintf(p.Writer, "Report name: %v\n", n.Report.Name) +} + +func (p *Parser) verboseNessusCsvDump(rr [][]string) { + if len(rr) > 0 { + fmt.Fprintf(p.Writer, "CSV record count: %v\n", len(rr)) + fmt.Fprintf(p.Writer, "CSV column count: %v\n", len(rr[0])) + } else { + fmt.Fprintf(p.Writer, "No rows found\n") + } +} + // NmapPrettyPrint consumes NMap XML and prints // formatted table of enumerated services func (p *Parser) NmapPrettyPrint() (err error) { - np, err := p.getNmapParser(p.Logger) + np, err := p.getNmapParser(p.Writer) if err != nil { return err } + if p.Verbose { + p.verboseNmapDump(np) + } + // In my previous nmap parser I built a lot more logic into output options I would like to add next // eg. ouput live hosts, output just a selected port for _, host := range np.Hosts { for _, ip := range host.Addresses { for _, port := range host.Ports { // fmt.Println("| ", ip.Addr, " | ", port.PortId, " | ", port.Service.Product, port.Service.Version) - fmt.Fprintf(p.Logger, "| %18s | %8s | %6s | %-22s %-8s |\n", ip.Addr, strconv.Itoa(port.PortId), port.Protocol, port.Service.Product, port.Service.Version) + fmt.Fprintf(p.Writer, "| %18s | %8s | %6s | %-22s %-8s |\n", ip.Addr, strconv.Itoa(port.PortId), port.Protocol, port.Service.Product, port.Service.Version) } } @@ -98,11 +119,15 @@ func (p *Parser) NmapPrettyPrint() (err error) { // NessusPrettyServiceXML does a pretty print of nessus data // and takes in the .nessus style file func (p *Parser) NessusPrettyServiceXML() (err error) { - np, err := p.getNessusParser(p.Logger) + np, err := p.getNessusParser(p.Writer) if err != nil { return err } + if p.Verbose { + p.verboseNessusDump(np) + } + // we might need some better logic here. Right now we just look for the plugin named // Service Detection and output the plugin output with the port. Might be other plugins // we want to add that have good data. @@ -110,7 +135,7 @@ func (p *Parser) NessusPrettyServiceXML() (err error) { for _, item := range host.ReportItems { // change this...need to range over host properties to get tag == ip if item.PluginName == "Service Detection" && item.PluginOutput[0:17] != "The service close" { - fmt.Fprintf(p.Logger, "| %18s | %8s | %-10s| %-32s |\n", host.Name, strconv.Itoa(item.Port), item.SvcName, item.PluginOutput[0:28]) + fmt.Fprintf(p.Writer, "| %18s | %8s | %-10s| %-32s |\n", host.Name, strconv.Itoa(item.Port), item.SvcName, item.PluginOutput[0:28]) } } } @@ -120,11 +145,15 @@ func (p *Parser) NessusPrettyServiceXML() (err error) { // NessusPrettyHighCritXML does a pretty print of nessus data // and takes in the .nessus style file; it prints all high and crit level findings func (p *Parser) NessusPrettyHighCritXML() (err error) { - np, err := p.getNessusParser(p.Logger) + np, err := p.getNessusParser(p.Writer) if err != nil { return err } + if p.Verbose { + p.verboseNessusDump(np) + } + // we might need some better logic here. Right now we just look for the plugin named // Service Detection and output the plugin output with the port. Might be other plugins // we want to add that have good data. @@ -132,7 +161,7 @@ func (p *Parser) NessusPrettyHighCritXML() (err error) { for _, item := range host.ReportItems { // change this...need to range over host properties to get tag == ip if item.Severity == 3 || item.Severity == 4 { - fmt.Fprintf(p.Logger, "| %18s | %8s | %-28s \n", host.Name, strconv.Itoa(item.Port), item.PluginName) + fmt.Fprintf(p.Writer, "| %18s | %8s | %-28s \n", host.Name, strconv.Itoa(item.Port), item.PluginName) } } @@ -143,14 +172,18 @@ func (p *Parser) NessusPrettyHighCritXML() (err error) { // NessusPrettyServicesCSV consumes an nessus csv and // prints out service and IP func (p *Parser) NessusPrettyServicesCSV() (err error) { - records, err := p.getCsvRecords(p.Logger) + records, err := p.getCsvRecords(p.Writer) if err != nil { return err } + if p.Verbose { + p.verboseNessusCsvDump(records) + } + for row := 0; row < len(records); row++ { if records[row][7] == "Service Detection" { - fmt.Fprintf(p.Logger, "| %14s | %8s | %22s\n", records[row][4], records[row][6], records[row][12]) + fmt.Fprintf(p.Writer, "| %14s | %8s | %22s\n", records[row][4], records[row][6], records[row][12]) } } return @@ -159,17 +192,21 @@ func (p *Parser) NessusPrettyServicesCSV() (err error) { // NessusPrettyWebCSV consumes an nessus csv and // prints out service and IP func (p *Parser) NessusPrettyWebCSV() (err error) { - records, err := p.getCsvRecords(p.Logger) + records, err := p.getCsvRecords(p.Writer) if err != nil { return err } + if p.Verbose { + p.verboseNessusCsvDump(records) + } + for row := 0; row < len(records); row++ { if records[row][7] == "HTTP Server Type and Version" { re := regexp.MustCompile("\\n") input := records[row][12] input = re.ReplaceAllString(input, " ") - fmt.Fprintf(p.Logger, "| %14s | %8s | %22s\n", records[row][4], records[row][6], input) + fmt.Fprintf(p.Writer, "| %14s | %8s | %22s\n", records[row][4], records[row][6], input) } } return @@ -178,11 +215,15 @@ func (p *Parser) NessusPrettyWebCSV() (err error) { // RumblePrettyPrint is for parsing // Rumble scans in nmap xml format func (p *Parser) RumblePrettyPrint() (err error) { - n, err := p.getNmapParser(p.Logger) + n, err := p.getNmapParser(p.Writer) if err != nil { return err } + if p.Verbose { + p.verboseNmapDump(n) + } + for _, host := range n.Hosts { for _, ip := range host.Addresses { fmt.Println("|--------------|-------------------|--------------|------------------|") @@ -192,16 +233,16 @@ func (p *Parser) RumblePrettyPrint() (err error) { } else { for _, port := range host.Ports { if port.Service.Product != "" { - fmt.Fprintf(p.Logger, "| %18s | %8s | %6s | %-22s %-8s |\n", ip.Addr, strconv.Itoa(port.PortId), port.Protocol, port.Service.Product, port.Service.Version) + fmt.Fprintf(p.Writer, "| %18s | %8s | %6s | %-22s %-8s |\n", ip.Addr, strconv.Itoa(port.PortId), port.Protocol, port.Service.Product, port.Service.Version) continue } m := make(map[string]string) b := []byte(port.Scripts[0].Output) if err := json.Unmarshal(b, &m); err != nil { - fmt.Fprintf(p.Logger, "Error parsing embedded JSON\n") + fmt.Fprintf(p.Writer, "Error parsing embedded JSON\n") } if banner, exists := m["banner"]; exists { - fmt.Fprintf(p.Logger, "| %18s | %8s | %6s | %-22s |\n", ip.Addr, strconv.Itoa(port.PortId), port.Protocol, banner) + fmt.Fprintf(p.Writer, "| %18s | %8s | %6s | %-22s |\n", ip.Addr, strconv.Itoa(port.PortId), port.Protocol, banner) } } } diff --git a/parser/parser_test.go b/parser/parser_test.go index 1db3b9c..6d96c2e 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -7,7 +7,7 @@ import ( func TestNmapPrettyPrintBadInputPath(t *testing.T) { buffer := &bytes.Buffer{} - p := New("../test/testfiles/doesnotexist", buffer) + p := New("../test/testfiles/doesnotexist", buffer, false) err := p.NmapPrettyPrint() if err == nil {