diff --git a/CHANGELOG.txt b/CHANGELOG.txt
@@ -1,5 +1,6 @@
## v0.0.7
+- fix(scan): open ports nerva cannot fingerprint no longer vanish from portscan.json ~ @djnnvx
- chore(deps): bump dependencies ~ @djnnvx
- refactor(core): dead code deletion, simplification ~ @djnnvx
- fix(osint): gau init errors for one provider dont bring everything down ~ @djnnvx
diff --git a/scan/portscan.go b/scan/portscan.go
@@ -7,6 +7,7 @@ import (
"net"
"net/netip"
"os"
+ "sort"
"strconv"
"strings"
"sync"
@@ -117,6 +118,7 @@ func (p *PortScanner) tcpScan(host string, ports []int) []int {
}(port)
}
wg.Wait()
+ sort.Ints(open)
return open
}
@@ -158,7 +160,7 @@ func (p *PortScanner) fingerprint(host string, openPorts []int) []PortResult {
return fallbackResults(openPorts)
}
- results := make([]PortResult, 0, len(nervaResults))
+ enriched := make(map[int]PortResult, len(nervaResults))
for _, r := range nervaResults {
var findings []SecurityFinding
for _, f := range r.SecurityFindings {
@@ -169,13 +171,26 @@ func (p *PortScanner) fingerprint(host string, openPorts []int) []PortResult {
Evidence: f.Evidence,
})
}
- results = append(results, PortResult{
+ enriched[r.Port] = PortResult{
Port: r.Port,
Proto: "tcp",
Service: r.Protocol,
Version: r.Version,
Findings: findings,
- })
+ }
+ }
+
+ return mergePortResults(openPorts, enriched)
+}
+
+func mergePortResults(openPorts []int, enriched map[int]PortResult) []PortResult {
+ results := make([]PortResult, len(openPorts))
+ for i, port := range openPorts {
+ if r, ok := enriched[port]; ok {
+ results[i] = r
+ continue
+ }
+ results[i] = PortResult{Port: port, Proto: "tcp"}
}
return results
}
diff --git a/scan/portscan_test.go b/scan/portscan_test.go
@@ -0,0 +1,69 @@
+package scan
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestMergePortResultsKeepsUnfingerprintedPorts(t *testing.T) {
+ open := []int{22, 80, 443}
+ enriched := map[int]PortResult{
+ 22: {Port: 22, Proto: "tcp", Service: "ssh"},
+ 80: {Port: 80, Proto: "tcp", Service: "http", Version: "OpenBSD httpd"},
+ }
+
+ got := mergePortResults(open, enriched)
+
+ if len(got) != len(open) {
+ t.Fatalf("dropped open ports: got %d results, want %d (%v)", len(got), len(open), got)
+ }
+ want := []PortResult{
+ {Port: 22, Proto: "tcp", Service: "ssh"},
+ {Port: 80, Proto: "tcp", Service: "http", Version: "OpenBSD httpd"},
+ {Port: 443, Proto: "tcp"},
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %+v, want %+v", got, want)
+ }
+}
+
+func TestMergePortResultsNoFingerprints(t *testing.T) {
+ got := mergePortResults([]int{8080}, map[int]PortResult{})
+ want := []PortResult{{Port: 8080, Proto: "tcp"}}
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %+v, want %+v", got, want)
+ }
+}
+
+func TestParsePorts(t *testing.T) {
+ tests := []struct {
+ spec string
+ want []int
+ wantErr bool
+ }{
+ {spec: "22,80,443", want: []int{22, 80, 443}},
+ {spec: "80-83", want: []int{80, 81, 82, 83}},
+ {spec: "22, 100-102", want: []int{22, 100, 101, 102}},
+ {spec: "443-443", want: []int{443}},
+ {spec: "100-80", wantErr: true},
+ {spec: "http", wantErr: true},
+ {spec: "1-x", wantErr: true},
+ }
+
+ for _, tt := range tests {
+ got, err := parsePorts(tt.spec)
+ if tt.wantErr {
+ if err == nil {
+ t.Errorf("parsePorts(%q): want error, got %v", tt.spec, got)
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf("parsePorts(%q): unexpected error %v", tt.spec, err)
+ continue
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("parsePorts(%q) = %v, want %v", tt.spec, got, tt.want)
+ }
+ }
+}
| | |