Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/lib/sdk.go
2061 views
1
package nuclei
2
3
import (
4
"bufio"
5
"bytes"
6
"context"
7
"io"
8
"sync"
9
10
"github.com/projectdiscovery/gologger"
11
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
12
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
13
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
14
"github.com/projectdiscovery/nuclei/v3/pkg/core"
15
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
16
providerTypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
17
"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow"
18
"github.com/projectdiscovery/nuclei/v3/pkg/output"
19
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
20
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
21
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
22
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
23
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
24
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
25
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
26
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
27
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
28
"github.com/projectdiscovery/nuclei/v3/pkg/types"
29
"github.com/projectdiscovery/ratelimit"
30
"github.com/projectdiscovery/retryablehttp-go"
31
"github.com/projectdiscovery/utils/errkit"
32
"github.com/rs/xid"
33
)
34
35
// NucleiSDKOptions contains options for nuclei SDK
36
type NucleiSDKOptions func(e *NucleiEngine) error
37
38
var (
39
// ErrNotImplemented is returned when a feature is not implemented
40
ErrNotImplemented = errkit.New("Not implemented")
41
// ErrNoTemplatesAvailable is returned when no templates are available to execute
42
ErrNoTemplatesAvailable = errkit.New("No templates available")
43
// ErrNoTargetsAvailable is returned when no targets are available to scan
44
ErrNoTargetsAvailable = errkit.New("No targets available")
45
// ErrOptionsNotSupported is returned when an option is not supported in thread safe mode
46
ErrOptionsNotSupported = errkit.New("Option not supported in thread safe mode")
47
)
48
49
type engineMode uint
50
51
const (
52
singleInstance engineMode = iota
53
threadSafe
54
)
55
56
// NucleiEngine is the Engine/Client for nuclei which
57
// runs scans using templates and returns results
58
type NucleiEngine struct {
59
// user options
60
resultCallbacks []func(event *output.ResultEvent)
61
onFailureCallback func(event *output.InternalEvent)
62
disableTemplatesAutoUpgrade bool
63
enableStats bool
64
onUpdateAvailableCallback func(newVersion string)
65
66
// ready-status fields
67
templatesLoaded bool
68
69
// unexported core fields
70
ctx context.Context
71
interactshClient *interactsh.Client
72
catalog catalog.Catalog
73
rateLimiter *ratelimit.Limiter
74
store *loader.Store
75
httpxClient providerTypes.InputLivenessProbe
76
inputProvider provider.InputProvider
77
engine *core.Engine
78
mode engineMode
79
browserInstance *engine.Browser
80
httpClient *retryablehttp.Client
81
parser *templates.Parser
82
authprovider authprovider.AuthProvider
83
84
// unexported meta options
85
opts *types.Options
86
interactshOpts *interactsh.Options
87
hostErrCache *hosterrorscache.Cache
88
customWriter output.Writer
89
customProgress progress.Progress
90
rc reporting.Client
91
executerOpts *protocols.ExecutorOptions
92
93
// Logger instance for the engine
94
Logger *gologger.Logger
95
}
96
97
// LoadAllTemplates loads all nuclei template based on given options
98
func (e *NucleiEngine) LoadAllTemplates() error {
99
workflowLoader, err := workflow.NewLoader(e.executerOpts)
100
if err != nil {
101
return errkit.Wrapf(err, "Could not create workflow loader: %s", err)
102
}
103
e.executerOpts.WorkflowLoader = workflowLoader
104
105
e.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts))
106
if err != nil {
107
return errkit.Wrapf(err, "Could not create loader client: %s", err)
108
}
109
e.store.Load()
110
e.templatesLoaded = true
111
return nil
112
}
113
114
// GetTemplates returns all nuclei templates that are loaded
115
func (e *NucleiEngine) GetTemplates() []*templates.Template {
116
if !e.templatesLoaded {
117
_ = e.LoadAllTemplates()
118
}
119
return e.store.Templates()
120
}
121
122
// GetWorkflows returns all nuclei workflows that are loaded
123
func (e *NucleiEngine) GetWorkflows() []*templates.Template {
124
if !e.templatesLoaded {
125
_ = e.LoadAllTemplates()
126
}
127
return e.store.Workflows()
128
}
129
130
// LoadTargets(urls/domains/ips only) adds targets to the nuclei engine
131
func (e *NucleiEngine) LoadTargets(targets []string, probeNonHttp bool) {
132
for _, target := range targets {
133
if probeNonHttp {
134
_ = e.inputProvider.SetWithProbe(e.opts.ExecutionId, target, e.httpxClient)
135
} else {
136
e.inputProvider.Set(e.opts.ExecutionId, target)
137
}
138
}
139
}
140
141
// LoadTargetsFromReader adds targets(urls/domains/ips only) from reader to the nuclei engine
142
func (e *NucleiEngine) LoadTargetsFromReader(reader io.Reader, probeNonHttp bool) {
143
buff := bufio.NewScanner(reader)
144
for buff.Scan() {
145
if probeNonHttp {
146
_ = e.inputProvider.SetWithProbe(e.opts.ExecutionId, buff.Text(), e.httpxClient)
147
} else {
148
e.inputProvider.Set(e.opts.ExecutionId, buff.Text())
149
}
150
}
151
}
152
153
// LoadTargetsWithHttpData loads targets that contain http data from file it currently supports
154
// multiple formats like burp xml,openapi,swagger,proxify json
155
// Note: this is mutually exclusive with LoadTargets and LoadTargetsFromReader
156
func (e *NucleiEngine) LoadTargetsWithHttpData(filePath string, filemode string) error {
157
e.opts.TargetsFilePath = filePath
158
e.opts.InputFileMode = filemode
159
httpProvider, err := provider.NewInputProvider(provider.InputOptions{Options: e.opts})
160
if err != nil {
161
e.opts.TargetsFilePath = ""
162
e.opts.InputFileMode = ""
163
return err
164
}
165
e.inputProvider = httpProvider
166
return nil
167
}
168
169
// GetExecuterOptions returns the nuclei executor options
170
func (e *NucleiEngine) GetExecuterOptions() *protocols.ExecutorOptions {
171
return e.executerOpts
172
}
173
174
// ParseTemplate parses a template from given data
175
// template verification status can be accessed from template.Verified
176
func (e *NucleiEngine) ParseTemplate(data []byte) (*templates.Template, error) {
177
return templates.ParseTemplateFromReader(bytes.NewReader(data), nil, e.executerOpts)
178
}
179
180
// SignTemplate signs the tempalate using given signer
181
func (e *NucleiEngine) SignTemplate(tmplSigner *signer.TemplateSigner, data []byte) ([]byte, error) {
182
tmpl, err := e.ParseTemplate(data)
183
if err != nil {
184
return data, err
185
}
186
if tmpl.Verified {
187
// already signed
188
return data, nil
189
}
190
if len(tmpl.Workflows) > 0 {
191
return data, templates.ErrNotATemplate
192
}
193
signatureData, err := tmplSigner.Sign(data, tmpl)
194
if err != nil {
195
return data, err
196
}
197
_, content := signer.ExtractSignatureAndContent(data)
198
buff := bytes.NewBuffer(content)
199
buff.WriteString("\n" + signatureData)
200
return buff.Bytes(), err
201
}
202
203
func (e *NucleiEngine) closeInternal() {
204
if e.interactshClient != nil {
205
e.interactshClient.Close()
206
}
207
if e.rc != nil {
208
e.rc.Close()
209
}
210
if e.customWriter != nil {
211
e.customWriter.Close()
212
}
213
if e.customProgress != nil {
214
e.customProgress.Stop()
215
}
216
if e.hostErrCache != nil {
217
e.hostErrCache.Close()
218
}
219
if e.executerOpts.RateLimiter != nil {
220
e.executerOpts.RateLimiter.Stop()
221
}
222
if e.rateLimiter != nil {
223
e.rateLimiter.Stop()
224
}
225
if e.inputProvider != nil {
226
e.inputProvider.Close()
227
}
228
if e.browserInstance != nil {
229
e.browserInstance.Close()
230
}
231
if e.httpxClient != nil {
232
_ = e.httpxClient.Close()
233
}
234
}
235
236
// Close all resources used by nuclei engine
237
func (e *NucleiEngine) Close() {
238
e.closeInternal()
239
protocolinit.Close(e.opts.ExecutionId)
240
}
241
242
// ExecuteCallbackWithCtx executes templates on targets and calls callback on each result(only if results are found)
243
// enable matcher-status option if you expect this callback to be called for all results regardless if it matched or not
244
func (e *NucleiEngine) ExecuteCallbackWithCtx(ctx context.Context, callback ...func(event *output.ResultEvent)) error {
245
if !e.templatesLoaded {
246
_ = e.LoadAllTemplates()
247
}
248
if len(e.store.Templates()) == 0 && len(e.store.Workflows()) == 0 {
249
return ErrNoTemplatesAvailable
250
}
251
if e.inputProvider.Count() == 0 {
252
return ErrNoTargetsAvailable
253
}
254
255
filtered := []func(event *output.ResultEvent){}
256
for _, cb := range callback {
257
if cb != nil {
258
filtered = append(filtered, cb)
259
}
260
}
261
e.resultCallbacks = append(e.resultCallbacks, filtered...)
262
263
templatesAndWorkflows := append(e.store.Templates(), e.store.Workflows()...)
264
if len(templatesAndWorkflows) == 0 {
265
return ErrNoTemplatesAvailable
266
}
267
268
var wg sync.WaitGroup
269
wg.Add(1)
270
go func() {
271
defer wg.Done()
272
_ = e.engine.ExecuteScanWithOpts(ctx, templatesAndWorkflows, e.inputProvider, false)
273
}()
274
275
// wait for context to be cancelled
276
select {
277
case <-ctx.Done():
278
<-wait(&wg) // wait for scan to finish
279
return ctx.Err()
280
case <-wait(&wg):
281
// scan finished
282
}
283
return nil
284
}
285
286
// ExecuteWithCallback is same as ExecuteCallbackWithCtx but with default context
287
// Note this is deprecated and will be removed in future major release
288
func (e *NucleiEngine) ExecuteWithCallback(callback ...func(event *output.ResultEvent)) error {
289
ctx := context.Background()
290
if e.ctx != nil {
291
ctx = e.ctx
292
}
293
return e.ExecuteCallbackWithCtx(ctx, callback...)
294
}
295
296
// Options return nuclei Type Options
297
func (e *NucleiEngine) Options() *types.Options {
298
return e.opts
299
}
300
301
// Engine returns core Executer of nuclei
302
func (e *NucleiEngine) Engine() *core.Engine {
303
return e.engine
304
}
305
306
// Store returns store of nuclei
307
func (e *NucleiEngine) Store() *loader.Store {
308
return e.store
309
}
310
311
// NewNucleiEngineCtx creates a new nuclei engine instance with given context
312
func NewNucleiEngineCtx(ctx context.Context, options ...NucleiSDKOptions) (*NucleiEngine, error) {
313
// default options
314
defaultOptions := types.DefaultOptions()
315
defaultOptions.ExecutionId = xid.New().String()
316
e := &NucleiEngine{
317
opts: defaultOptions,
318
mode: singleInstance,
319
ctx: ctx,
320
}
321
for _, option := range options {
322
if err := option(e); err != nil {
323
return nil, err
324
}
325
}
326
if err := e.init(ctx); err != nil {
327
return nil, err
328
}
329
return e, nil
330
}
331
332
// Deprecated: use NewNucleiEngineCtx instead
333
func NewNucleiEngine(options ...NucleiSDKOptions) (*NucleiEngine, error) {
334
return NewNucleiEngineCtx(context.Background(), options...)
335
}
336
337
// GetParser returns the template parser with cache
338
func (e *NucleiEngine) GetParser() *templates.Parser {
339
return e.parser
340
}
341
342
// wait for a waitgroup to finish
343
func wait(wg *sync.WaitGroup) <-chan struct{} {
344
ch := make(chan struct{})
345
go func() {
346
defer close(ch)
347
wg.Wait()
348
}()
349
return ch
350
}
351
352