diff --git a/.gitignore b/.gitignore index af9d074..1f787e0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,4 @@ # vendor/ # do not upload convert result file -.json \ No newline at end of file +*.json \ No newline at end of file diff --git a/README.md b/README.md index 2a0aeec..21408c1 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ import ( func main() { readable := true srcFilePath := "nmap-service-probes" - probes, err := parser.ParseNmap(srcFilePath) + probes, err := parser.ParseNmapServiceProbe(srcFilePath) if err != nil { panic(err) } @@ -88,7 +88,7 @@ var ( ErrRecRsp = errors.New("Error receiving response") ) -func ServiceDetect(host string, port int, probe parser.Probe) (serviceName string, info parser.VInfo, err error) { +func ServiceDetect(host string, port int, probe *parser.Probe) (serviceName string, info *parser.VInfo, err error) { addr := net.JoinHostPort(host, strconv.Itoa(port)) conn, err := net.Dial(strings.ToLower(probe.Protocol), addr) if err != nil { @@ -150,7 +150,7 @@ func ServiceDetect(host string, port int, probe parser.Probe) (serviceName strin func main() { srcFilePath := "nmap-service-probes" - probes, err := parser.ParseNmap(srcFilePath) + probes, err := parser.ParseNmapServiceProbe(srcFilePath) if err != nil { panic(err) } @@ -159,21 +159,21 @@ func main() { port := 6379 serviceName := "" - info := parser.VInfo{} + info := parser.NewVInfo() for _, probe := range probes { serviceNameTmp, infoTmp, err := ServiceDetect(host, port, probe) if err != nil { continue } - if serviceNameTmp != "" && !infoTmp.IsVInfoEmpty() { + if serviceNameTmp != "" && !infoTmp.IsEmpty() { serviceName = serviceNameTmp info = infoTmp } } - if serviceName != "" && !info.IsVInfoEmpty() { + if serviceName != "" && !info.IsEmpty() { fmt.Println(serviceName, info) } else { fmt.Println("no match service!") diff --git a/go.mod b/go.mod index b1c5995..a046b89 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/pkg/errors v0.9.1 - github.com/randolphcyg/cpe v1.0.4 + github.com/randolphcyg/cpe v1.0.6 github.com/stretchr/testify v1.8.2 ) diff --git a/go.sum b/go.sum index 5c6e876..cb2f786 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,10 @@ github.com/randolphcyg/cpe v1.0.3 h1:Gy91SlIWvPPqIMF8gDq3tnhde6noO/AyKeeyLHp/tio github.com/randolphcyg/cpe v1.0.3/go.mod h1:R+J264JmgNtHkgKkYS12TQNgp819+OnwFUJuZUkoyio= github.com/randolphcyg/cpe v1.0.4 h1:yt8reQ+oLlw4bFAcRk9t2zLGE1/11RptF0AL0XT7s2s= github.com/randolphcyg/cpe v1.0.4/go.mod h1:R+J264JmgNtHkgKkYS12TQNgp819+OnwFUJuZUkoyio= +github.com/randolphcyg/cpe v1.0.5 h1:WNIKWlLI4uFPLRbSiuPsS8MH4QBJC+kePdTSuZ9Ppds= +github.com/randolphcyg/cpe v1.0.5/go.mod h1:R+J264JmgNtHkgKkYS12TQNgp819+OnwFUJuZUkoyio= +github.com/randolphcyg/cpe v1.0.6 h1:6/jVTznDzniah986/31eGAy+STpkObmeSZ2wEQrtKP0= +github.com/randolphcyg/cpe v1.0.6/go.mod h1:R+J264JmgNtHkgKkYS12TQNgp819+OnwFUJuZUkoyio= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/parser.go b/parser.go index 1a13879..37663ea 100644 --- a/parser.go +++ b/parser.go @@ -11,43 +11,65 @@ import ( "github.com/randolphcyg/cpe" ) -// VInfo version info, include six optional fields and CPE -type VInfo struct { - VendorProductName string `json:"vendorProductName"` - Version string `json:"version"` - Info string `json:"info"` - Hostname string `json:"hostname"` - OperatingSystem string `json:"operatingSystem"` - DeviceType string `json:"deviceType"` - Cpe []cpe.CPE `json:"cpe"` +type IProbe interface { + IsEmpty() bool } -type Match struct { - Pattern string `json:"pattern"` - Name string `json:"name"` - PatternFlag string `json:"patternFlag"` - VersionInfo VInfo `json:"versionInfo"` +type IParser interface { + IsEmpty() bool } +// Probe nmap service probe type Probe struct { Protocol string `json:"protocol"` ProbeName string `json:"probeName"` - ProbeString string `json:"probeString"` - Ports []string `json:"ports"` - SslPorts []string `json:"sslPorts"` - TcpWrappedMs string `json:"tcpWrappedMs"` - TotalWaitMs string `json:"totalWaitMs"` - Rarity string `json:"rarity"` - Fallback string `json:"fallback"` - Matches []Match `json:"matches"` + ProbeString string `json:"probeString,omitempty"` + Ports []string `json:"ports,omitempty"` + SslPorts []string `json:"sslPorts,omitempty"` + TcpWrappedMs string `json:"tcpWrappedMs,omitempty"` + TotalWaitMs string `json:"totalWaitMs,omitempty"` + Rarity string `json:"rarity,omitempty"` + Fallback string `json:"fallback,omitempty"` + Matches []*Match `json:"matches"` +} + +// Match nmap service probe match rule +type Match struct { + Pattern string `json:"pattern"` + Name string `json:"name"` + PatternFlag string `json:"patternFlag,omitempty"` + VersionInfo *VInfo `json:"versionInfo,omitempty"` } -func (x Probe) IsProbeEmpty() bool { - return reflect.DeepEqual(x, Probe{}) +// VInfo version info, include six optional fields and CPE +type VInfo struct { + VendorProductName string `json:"vendorProductName,omitempty"` + Version string `json:"version,omitempty"` + Info string `json:"info,omitempty"` + Hostname string `json:"hostname,omitempty"` + OperatingSystem string `json:"operatingSystem,omitempty"` + DeviceType string `json:"deviceType,omitempty"` + Cpe []*cpe.CPE `json:"cpe,omitempty"` } -func (v VInfo) IsVInfoEmpty() bool { - return reflect.DeepEqual(v, VInfo{}) +func NewProbe() *Probe { + return &Probe{} +} + +func (x *Probe) IsEmpty() bool { + return reflect.DeepEqual(x, &Probe{}) +} + +func NewMatch() *Match { + return &Match{} +} + +func NewVInfo() *VInfo { + return &VInfo{} +} + +func (v *VInfo) IsEmpty() bool { + return reflect.DeepEqual(v, &VInfo{}) } func handleVInfoField(src, flagStr string) (string, string, error) { @@ -72,13 +94,12 @@ func handleVInfoField(src, flagStr string) (string, string, error) { return ret, srcRet, nil } -type fieldInfo struct { - field string - set func(string) -} - -func HandleVInfo(src string) (vInfo VInfo, err error) { - fields := []fieldInfo{ +func HandleVInfo(src string) (vInfo *VInfo, err error) { + vInfo = NewVInfo() + fields := []struct { + field string + set func(string) + }{ {"p/", func(v string) { vInfo.VendorProductName = v }}, {"v/", func(v string) { vInfo.Version = v }}, {"i/", func(v string) { vInfo.Info = v }}, @@ -102,13 +123,11 @@ func HandleVInfo(src string) (vInfo VInfo, err error) { // CPE handle logic cpeSrcStr := "" - cpeFlag := "cpe:/" - isCpe22FlagInSrc := strings.Index(src, cpeFlag) + isCpe22FlagInSrc := strings.Index(src, cpe.FlagCpe22) if isCpe22FlagInSrc != -1 { cpeSrcStr = src[isCpe22FlagInSrc : len(src)-1] } else { - cpeFlag = "cpe:2.3" - isCpe23FlagInSrc := strings.LastIndex(src, cpeFlag) + isCpe23FlagInSrc := strings.LastIndex(src, cpe.FlagCpe23) if isCpe23FlagInSrc != -1 { cpeSrcStr = src[isCpe23FlagInSrc : len(src)-1] } @@ -121,14 +140,15 @@ func HandleVInfo(src string) (vInfo VInfo, err error) { if err != nil { continue } - vInfo.Cpe = append(vInfo.Cpe, *cRet) + vInfo.Cpe = append(vInfo.Cpe, cRet) } } return } -func ParseMatch(line string) (m Match, err error) { +func ParseMatch(line string) (m *Match, err error) { + m = NewMatch() line = strings.TrimSpace(line) line = strings.Replace(line, "\n", "", -1) matchSeg := strings.SplitN(line, " ", 3) @@ -137,7 +157,7 @@ func ParseMatch(line string) (m Match, err error) { pattern := regxSeg[1] patternFlag := "" - var versionInfo VInfo + versionInfo := NewVInfo() if len(regxSeg) >= 3 { versionInfoSeg := strings.SplitN(regxSeg[2], " ", 2) @@ -148,7 +168,7 @@ func ParseMatch(line string) (m Match, err error) { if len(versionInfoSeg) >= 2 { tmp, errVInfo := HandleVInfo(versionInfoSeg[1]) if err != nil { - m = Match{ + m = &Match{ Pattern: pattern, Name: name, PatternFlag: patternFlag, @@ -160,7 +180,7 @@ func ParseMatch(line string) (m Match, err error) { } } - m = Match{ + m = &Match{ Pattern: pattern, Name: name, PatternFlag: patternFlag, @@ -170,7 +190,7 @@ func ParseMatch(line string) (m Match, err error) { return } -func ParseNmap(srcFilePath string) (probes []Probe, err error) { +func ParseNmapServiceProbe(srcFilePath string) (probes []*Probe, err error) { // Open the nmap-service-probes file file, err := os.Open(srcFilePath) if err != nil { @@ -178,10 +198,10 @@ func ParseNmap(srcFilePath string) (probes []Probe, err error) { } defer file.Close() - probes = make([]Probe, 0, 200) + probes = make([]*Probe, 0, 200) // Create an empty probe to hold current probe being parsed - var currentProbe Probe + currentProbe := NewProbe() // Create a scanner to read the file line by line; Loop through each line of the file for scanner := bufio.NewScanner(file); scanner.Scan(); { @@ -201,18 +221,7 @@ func ParseNmap(srcFilePath string) (probes []Probe, err error) { probes = append(probes, currentProbe) } // Create a new probe with the name and default values - currentProbe = Probe{ - ProbeName: "", - Protocol: "", - ProbeString: "", - Ports: nil, - SslPorts: nil, - TcpWrappedMs: "", - TotalWaitMs: "", - Rarity: "", - Fallback: "", - Matches: nil, - } + currentProbe = NewProbe() lineSeg := strings.SplitN(line, " ", 4) if lineSeg[1] != "TCP" && lineSeg[1] != "UDP" { // unsupported protocol @@ -245,7 +254,7 @@ func ParseNmap(srcFilePath string) (probes []Probe, err error) { } // Append the last probe to the slice of probes - if currentProbe.IsProbeEmpty() { + if currentProbe.IsEmpty() { return } @@ -266,9 +275,9 @@ func UnquoteRawString(rawStr string) (string, error) { } // FillVersionInfoFields Replace the versionInfo and CPE placeholder elements with the matched real values -func FillVersionInfoFields(src [][]byte, match Match) VInfo { +func FillVersionInfoFields(src [][]byte, match *Match) *VInfo { versionInfo := match.VersionInfo - tmpVerInfo := VInfo{ + tmpVerInfo := &VInfo{ VendorProductName: FillHelperFuncOrVariable(versionInfo.VendorProductName, src), Version: FillHelperFuncOrVariable(versionInfo.Version, src), Info: FillHelperFuncOrVariable(versionInfo.Info, src), @@ -280,7 +289,7 @@ func FillVersionInfoFields(src [][]byte, match Match) VInfo { if len(versionInfo.Cpe) > 0 { for _, c := range versionInfo.Cpe { - tmpCPE := cpe.CPE{ + tmpCPE := &cpe.CPE{ Version: FillHelperFuncOrVariable(c.Version, src), Language: FillHelperFuncOrVariable(c.Language, src), Vendor: FillHelperFuncOrVariable(c.Vendor, src), diff --git a/tests/parser_test.go b/tests/parser_test.go index b6d13b5..74d4850 100644 --- a/tests/parser_test.go +++ b/tests/parser_test.go @@ -11,14 +11,19 @@ import ( "github.com/randolphcyg/nmap-parser" ) +func TestVInfoIsEmpty(t *testing.T) { + vInfo := parser.VInfo{} + assert.Equal(t, true, vInfo.IsEmpty()) +} + func TestHandleVersionInfo(t *testing.T) { _, err := parser.HandleVInfo("match activesync m|^.\\0\\x01\\0[^\\0]\\0[^\\0]\\0[^\\0]\\0[^\\0]\\0[^\\0]\\0.*\\0\\0\\0$|s p/Microsoft ActiveSync/ o/Windows/ cpe:/a:microsoft:activesync/ cpe:/o:microsoft:windows/a") assert.ErrorIs(t, err, nil) } -func TestParseNmap(t *testing.T) { +func TestParseNmapServiceProbe(t *testing.T) { srcFilePath := "nmap-service-probes" - probes, err := parser.ParseNmap(srcFilePath) + probes, err := parser.ParseNmapServiceProbe(srcFilePath) if err != nil { panic(err) } @@ -30,10 +35,10 @@ func TestParseNmap(t *testing.T) { fmt.Println(len(string(probesJSON))) } -func TestParseNmapAndToJson(t *testing.T) { +func TestParseNmapServiceProbeToJson(t *testing.T) { readable := true srcFilePath := "nmap-service-probes" - probes, err := parser.ParseNmap(srcFilePath) + probes, err := parser.ParseNmapServiceProbe(srcFilePath) if err != nil { panic(err) } diff --git a/tests/service_detect_test.go b/tests/service_detect_test.go index ce78dbd..7fd9f43 100644 --- a/tests/service_detect_test.go +++ b/tests/service_detect_test.go @@ -21,7 +21,7 @@ var ( ErrRecRsp = errors.New("Error receiving response") ) -func ServiceDetect(host string, port int, probe parser.Probe) (serviceName string, info parser.VInfo, err error) { +func ServiceDetect(host string, port int, probe *parser.Probe) (serviceName string, info *parser.VInfo, err error) { addr := net.JoinHostPort(host, strconv.Itoa(port)) conn, err := net.Dial(strings.ToLower(probe.Protocol), addr) if err != nil { @@ -83,7 +83,7 @@ func ServiceDetect(host string, port int, probe parser.Probe) (serviceName strin func TestServiceDetect(t *testing.T) { srcFilePath := "nmap-service-probes" - probes, err := parser.ParseNmap(srcFilePath) + probes, err := parser.ParseNmapServiceProbe(srcFilePath) if err != nil { panic(err) } @@ -92,21 +92,21 @@ func TestServiceDetect(t *testing.T) { targetPort := 6379 serviceName := "" - info := parser.VInfo{} + info := parser.NewVInfo() for _, probe := range probes { serviceNameTmp, infoTmp, err := ServiceDetect(host, targetPort, probe) if err != nil { continue } - if serviceNameTmp != "" && !infoTmp.IsVInfoEmpty() { + if serviceNameTmp != "" && !infoTmp.IsEmpty() { serviceName = serviceNameTmp info = infoTmp } } - if serviceName != "" && !info.IsVInfoEmpty() { + if serviceName != "" && !info.IsEmpty() { assert.Equal(t, "redis", serviceName) assert.Equal(t, "Redis key-value store", info.VendorProductName) } else {