Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/output/stats/stats.go
2070 views
1
// Package stats provides a stats tracker for tracking Status Codes,
2
// Errors & WAF detection events.
3
//
4
// It is wrapped and called by output.Writer interface.
5
package stats
6
7
import (
8
_ "embed"
9
"fmt"
10
"sort"
11
"strconv"
12
"sync/atomic"
13
14
"github.com/logrusorgru/aurora"
15
"github.com/projectdiscovery/nuclei/v3/pkg/output/stats/waf"
16
mapsutil "github.com/projectdiscovery/utils/maps"
17
)
18
19
// Tracker is a stats tracker instance for nuclei scans
20
type Tracker struct {
21
// counters for various stats
22
statusCodes *mapsutil.SyncLockMap[string, *atomic.Int32]
23
errorCodes *mapsutil.SyncLockMap[string, *atomic.Int32]
24
wafDetected *mapsutil.SyncLockMap[string, *atomic.Int32]
25
26
// internal stuff
27
wafDetector *waf.WafDetector
28
}
29
30
// NewTracker creates a new Tracker instance.
31
func NewTracker() *Tracker {
32
return &Tracker{
33
statusCodes: mapsutil.NewSyncLockMap[string, *atomic.Int32](),
34
errorCodes: mapsutil.NewSyncLockMap[string, *atomic.Int32](),
35
wafDetected: mapsutil.NewSyncLockMap[string, *atomic.Int32](),
36
wafDetector: waf.NewWafDetector(),
37
}
38
}
39
40
// TrackStatusCode tracks the status code of a request
41
func (t *Tracker) TrackStatusCode(statusCode string) {
42
t.incrementCounter(t.statusCodes, statusCode)
43
}
44
45
// TrackErrorKind tracks the error kind of a request
46
func (t *Tracker) TrackErrorKind(errKind string) {
47
t.incrementCounter(t.errorCodes, errKind)
48
}
49
50
// TrackWAFDetected tracks the waf detected of a request
51
//
52
// First it detects if a waf is running and if so, it increments
53
// the counter for the waf.
54
func (t *Tracker) TrackWAFDetected(httpResponse string) {
55
waf, ok := t.wafDetector.DetectWAF(httpResponse)
56
if !ok {
57
return
58
}
59
60
t.incrementCounter(t.wafDetected, waf)
61
}
62
63
func (t *Tracker) incrementCounter(m *mapsutil.SyncLockMap[string, *atomic.Int32], key string) {
64
if counter, ok := m.Get(key); ok {
65
counter.Add(1)
66
} else {
67
newCounter := new(atomic.Int32)
68
newCounter.Store(1)
69
_ = m.Set(key, newCounter)
70
}
71
}
72
73
type StatsOutput struct {
74
StatusCodeStats map[string]int `json:"status_code_stats"`
75
ErrorStats map[string]int `json:"error_stats"`
76
WAFStats map[string]int `json:"waf_stats"`
77
}
78
79
func (t *Tracker) GetStats() *StatsOutput {
80
stats := &StatsOutput{
81
StatusCodeStats: make(map[string]int),
82
ErrorStats: make(map[string]int),
83
WAFStats: make(map[string]int),
84
}
85
_ = t.errorCodes.Iterate(func(k string, v *atomic.Int32) error {
86
stats.ErrorStats[k] = int(v.Load())
87
return nil
88
})
89
_ = t.statusCodes.Iterate(func(k string, v *atomic.Int32) error {
90
stats.StatusCodeStats[k] = int(v.Load())
91
return nil
92
})
93
_ = t.wafDetected.Iterate(func(k string, v *atomic.Int32) error {
94
waf, ok := t.wafDetector.GetWAF(k)
95
if !ok {
96
return nil
97
}
98
stats.WAFStats[waf.Name] = int(v.Load())
99
return nil
100
})
101
return stats
102
}
103
104
// DisplayTopStats prints the most relevant statistics for CLI
105
func (t *Tracker) DisplayTopStats(noColor bool) {
106
stats := t.GetStats()
107
108
if len(stats.StatusCodeStats) > 0 {
109
fmt.Printf("\n%s\n", aurora.Bold(aurora.Blue("Top Status Codes:")))
110
topStatusCodes := getTopN(stats.StatusCodeStats, 6)
111
for _, item := range topStatusCodes {
112
if noColor {
113
fmt.Printf(" %s: %d\n", item.Key, item.Value)
114
} else {
115
color := getStatusCodeColor(item.Key)
116
fmt.Printf(" %s: %d\n", aurora.Colorize(item.Key, color), item.Value)
117
}
118
}
119
}
120
121
if len(stats.ErrorStats) > 0 {
122
fmt.Printf("\n%s\n", aurora.Bold(aurora.Red("Top Errors:")))
123
topErrors := getTopN(stats.ErrorStats, 5)
124
for _, item := range topErrors {
125
if noColor {
126
fmt.Printf(" %s: %d\n", item.Key, item.Value)
127
} else {
128
fmt.Printf(" %s: %d\n", aurora.Red(item.Key), item.Value)
129
}
130
}
131
}
132
133
if len(stats.WAFStats) > 0 {
134
fmt.Printf("\n%s\n", aurora.Bold(aurora.Yellow("WAF Detections:")))
135
for name, count := range stats.WAFStats {
136
if noColor {
137
fmt.Printf(" %s: %d\n", name, count)
138
} else {
139
fmt.Printf(" %s: %d\n", aurora.Yellow(name), count)
140
}
141
}
142
}
143
}
144
145
// Helper struct for sorting
146
type kv struct {
147
Key string
148
Value int
149
}
150
151
// getTopN returns top N items from a map, sorted by value
152
func getTopN(m map[string]int, n int) []kv {
153
var items []kv
154
for k, v := range m {
155
items = append(items, kv{k, v})
156
}
157
158
sort.Slice(items, func(i, j int) bool {
159
return items[i].Value > items[j].Value
160
})
161
162
if len(items) > n {
163
items = items[:n]
164
}
165
return items
166
}
167
168
// getStatusCodeColor returns appropriate color for status code
169
func getStatusCodeColor(statusCode string) aurora.Color {
170
code, _ := strconv.Atoi(statusCode)
171
switch {
172
case code >= 200 && code < 300:
173
return aurora.GreenFg
174
case code >= 300 && code < 400:
175
return aurora.BlueFg
176
case code >= 400 && code < 500:
177
return aurora.YellowFg
178
case code >= 500:
179
return aurora.RedFg
180
default:
181
return aurora.WhiteFg
182
}
183
}
184
185