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