Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/catalog/config/nucleiconfig.go
2070 views
1
package config
2
3
import (
4
"bytes"
5
"crypto/md5"
6
"fmt"
7
"os"
8
"path/filepath"
9
"slices"
10
"strings"
11
"sync"
12
13
"github.com/projectdiscovery/gologger"
14
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
15
"github.com/projectdiscovery/utils/env"
16
"github.com/projectdiscovery/utils/errkit"
17
fileutil "github.com/projectdiscovery/utils/file"
18
folderutil "github.com/projectdiscovery/utils/folder"
19
)
20
21
// DefaultConfig is the default nuclei configuration
22
// all config values and default are centralized here
23
var DefaultConfig *Config
24
25
type Config struct {
26
TemplatesDirectory string `json:"nuclei-templates-directory,omitempty"`
27
28
// customtemplates exists in templates directory with the name of custom-templates provider
29
// below custom paths are absolute paths to respective custom-templates directories
30
CustomS3TemplatesDirectory string `json:"custom-s3-templates-directory"`
31
CustomGitHubTemplatesDirectory string `json:"custom-github-templates-directory"`
32
CustomGitLabTemplatesDirectory string `json:"custom-gitlab-templates-directory"`
33
CustomAzureTemplatesDirectory string `json:"custom-azure-templates-directory"`
34
35
TemplateVersion string `json:"nuclei-templates-version,omitempty"`
36
NucleiIgnoreHash string `json:"nuclei-ignore-hash,omitempty"`
37
LogAllEvents bool `json:"-"` // when enabled logs all events (more than verbose)
38
HideTemplateSigWarning bool `json:"-"` // when enabled disables template signature warning
39
40
// LatestXXX are not meant to be used directly and is used as
41
// local cache of nuclei version check endpoint
42
// these fields are only update during nuclei version check
43
// TODO: move these fields to a separate unexported struct as they are not meant to be used directly
44
LatestNucleiVersion string `json:"nuclei-latest-version"`
45
LatestNucleiTemplatesVersion string `json:"nuclei-templates-latest-version"`
46
LatestNucleiIgnoreHash string `json:"nuclei-latest-ignore-hash,omitempty"`
47
Logger *gologger.Logger `json:"-"` // logger
48
49
// internal / unexported fields
50
disableUpdates bool `json:"-"` // disable updates both version check and template updates
51
homeDir string `json:"-"` // User Home Directory
52
configDir string `json:"-"` // Nuclei Global Config Directory
53
debugArgs []string `json:"-"` // debug args
54
55
m sync.Mutex
56
}
57
58
// IsCustomTemplate determines whether a given template is custom-built or part of the official Nuclei templates.
59
// It checks if the template's path matches any of the predefined custom template directories
60
// (such as S3, GitHub, GitLab, and Azure directories). If the template resides in any of these directories,
61
// it is considered custom. Additionally, if the template's path does not start with the main Nuclei TemplatesDirectory,
62
// it is also considered custom. This function assumes that template paths are either absolute
63
// or relative to the same base as the paths configured in DefaultConfig.
64
func (c *Config) IsCustomTemplate(templatePath string) bool {
65
customDirs := []string{
66
c.CustomS3TemplatesDirectory,
67
c.CustomGitHubTemplatesDirectory,
68
c.CustomGitLabTemplatesDirectory,
69
c.CustomAzureTemplatesDirectory,
70
}
71
72
for _, dir := range customDirs {
73
if strings.HasPrefix(templatePath, dir) {
74
return true
75
}
76
}
77
return !strings.HasPrefix(templatePath, c.TemplatesDirectory)
78
}
79
80
// WriteVersionCheckData writes version check data to config file
81
func (c *Config) WriteVersionCheckData(ignorehash, nucleiVersion, templatesVersion string) error {
82
updated := false
83
if ignorehash != "" && c.LatestNucleiIgnoreHash != ignorehash {
84
c.LatestNucleiIgnoreHash = ignorehash
85
updated = true
86
}
87
if nucleiVersion != "" && c.LatestNucleiVersion != nucleiVersion {
88
c.LatestNucleiVersion = nucleiVersion
89
updated = true
90
}
91
if templatesVersion != "" && c.LatestNucleiTemplatesVersion != templatesVersion {
92
c.LatestNucleiTemplatesVersion = templatesVersion
93
updated = true
94
}
95
// write config to disk if any of the fields are updated
96
if updated {
97
return c.WriteTemplatesConfig()
98
}
99
return nil
100
}
101
102
// GetTemplateDir returns the nuclei templates directory absolute path
103
func (c *Config) GetTemplateDir() string {
104
val, _ := filepath.Abs(c.TemplatesDirectory)
105
return val
106
}
107
108
// DisableUpdateCheck disables update check and template updates
109
func (c *Config) DisableUpdateCheck() {
110
c.m.Lock()
111
defer c.m.Unlock()
112
c.disableUpdates = true
113
}
114
115
// CanCheckForUpdates returns true if update check is enabled
116
func (c *Config) CanCheckForUpdates() bool {
117
c.m.Lock()
118
defer c.m.Unlock()
119
return !c.disableUpdates
120
}
121
122
// NeedsTemplateUpdate returns true if template installation/update is required
123
func (c *Config) NeedsTemplateUpdate() bool {
124
c.m.Lock()
125
defer c.m.Unlock()
126
return !c.disableUpdates && (c.TemplateVersion == "" || IsOutdatedVersion(c.TemplateVersion, c.LatestNucleiTemplatesVersion) || !fileutil.FolderExists(c.TemplatesDirectory))
127
}
128
129
// NeedsIgnoreFileUpdate returns true if Ignore file hash is different (aka ignore file is outdated)
130
func (c *Config) NeedsIgnoreFileUpdate() bool {
131
c.m.Lock()
132
defer c.m.Unlock()
133
return c.NucleiIgnoreHash == "" || c.NucleiIgnoreHash != c.LatestNucleiIgnoreHash
134
}
135
136
// UpdateNucleiIgnoreHash updates the nuclei ignore hash in config
137
func (c *Config) UpdateNucleiIgnoreHash() error {
138
// calculate hash of ignore file and update config
139
ignoreFilePath := c.GetIgnoreFilePath()
140
if fileutil.FileExists(ignoreFilePath) {
141
bin, err := os.ReadFile(ignoreFilePath)
142
if err != nil {
143
return errkit.Newf("could not read nuclei ignore file: %v", err)
144
}
145
c.NucleiIgnoreHash = fmt.Sprintf("%x", md5.Sum(bin))
146
// write config to disk
147
return c.WriteTemplatesConfig()
148
}
149
return errkit.New("ignore file not found: could not update nuclei ignore hash")
150
}
151
152
// GetConfigDir returns the nuclei configuration directory
153
func (c *Config) GetConfigDir() string {
154
return c.configDir
155
}
156
157
// GetKeysDir returns the nuclei signer keys directory
158
func (c *Config) GetKeysDir() string {
159
return filepath.Join(c.configDir, "keys")
160
}
161
162
// GetAllCustomTemplateDirs returns all custom template directories
163
func (c *Config) GetAllCustomTemplateDirs() []string {
164
return []string{c.CustomS3TemplatesDirectory, c.CustomGitHubTemplatesDirectory, c.CustomGitLabTemplatesDirectory, c.CustomAzureTemplatesDirectory}
165
}
166
167
// GetReportingConfigFilePath returns the nuclei reporting config file path
168
func (c *Config) GetReportingConfigFilePath() string {
169
return filepath.Join(c.configDir, ReportingConfigFilename)
170
}
171
172
// GetIgnoreFilePath returns the nuclei ignore file path
173
func (c *Config) GetIgnoreFilePath() string {
174
return filepath.Join(c.configDir, NucleiIgnoreFileName)
175
}
176
177
func (c *Config) GetTemplateIndexFilePath() string {
178
return filepath.Join(c.TemplatesDirectory, NucleiTemplatesIndexFileName)
179
}
180
181
// GetChecksumFilePath returns checksum file path of nuclei templates
182
func (c *Config) GetChecksumFilePath() string {
183
return filepath.Join(c.TemplatesDirectory, NucleiTemplatesCheckSumFileName)
184
}
185
186
// GetFlagsConfigFilePath returns the nuclei cli config file path
187
func (c *Config) GetFlagsConfigFilePath() string {
188
return filepath.Join(c.configDir, CLIConfigFileName)
189
}
190
191
// GetNewAdditions returns new template additions in current template release
192
// if .new-additions file is not present empty slice is returned
193
func (c *Config) GetNewAdditions() []string {
194
arr := []string{}
195
newAdditionsPath := filepath.Join(c.TemplatesDirectory, NewTemplateAdditionsFileName)
196
if !fileutil.FileExists(newAdditionsPath) {
197
return arr
198
}
199
bin, err := os.ReadFile(newAdditionsPath)
200
if err != nil {
201
return arr
202
}
203
for _, v := range strings.Fields(string(bin)) {
204
if IsTemplate(v) {
205
arr = append(arr, v)
206
}
207
}
208
return arr
209
}
210
211
// GetCacheDir returns the nuclei cache directory
212
// with new version of nuclei cache directory is changed
213
// instead of saving resume files in nuclei config directory
214
// they are saved in nuclei cache directory
215
func (c *Config) GetCacheDir() string {
216
return folderutil.AppCacheDirOrDefault(".nuclei-cache", BinaryName)
217
}
218
219
// SetConfigDir sets the nuclei configuration directory
220
// and appropriate changes are made to the config
221
func (c *Config) SetConfigDir(dir string) {
222
c.configDir = dir
223
if err := c.createConfigDirIfNotExists(); err != nil {
224
c.Logger.Fatal().Msgf("Could not create nuclei config directory at %s: %s", c.configDir, err)
225
}
226
227
// if folder already exists read config or create new
228
if err := c.ReadTemplatesConfig(); err != nil {
229
// create new config
230
applyDefaultConfig()
231
if err2 := c.WriteTemplatesConfig(); err2 != nil {
232
c.Logger.Fatal().Msgf("Could not create nuclei config file at %s: %s", c.getTemplatesConfigFilePath(), err2)
233
}
234
}
235
236
// while other config files are optional, ignore file is mandatory
237
// since it is used to ignore templates with weak matchers
238
c.copyIgnoreFile()
239
}
240
241
// SetTemplatesDir sets the new nuclei templates directory
242
func (c *Config) SetTemplatesDir(dirPath string) {
243
if dirPath != "" && !filepath.IsAbs(dirPath) {
244
cwd, _ := os.Getwd()
245
dirPath = filepath.Join(cwd, dirPath)
246
}
247
c.TemplatesDirectory = dirPath
248
// Update the custom templates directory
249
c.CustomGitHubTemplatesDirectory = filepath.Join(dirPath, CustomGitHubTemplatesDirName)
250
c.CustomS3TemplatesDirectory = filepath.Join(dirPath, CustomS3TemplatesDirName)
251
c.CustomGitLabTemplatesDirectory = filepath.Join(dirPath, CustomGitLabTemplatesDirName)
252
c.CustomAzureTemplatesDirectory = filepath.Join(dirPath, CustomAzureTemplatesDirName)
253
}
254
255
// SetTemplatesVersion sets the new nuclei templates version
256
func (c *Config) SetTemplatesVersion(version string) error {
257
c.TemplateVersion = version
258
// write config to disk
259
if err := c.WriteTemplatesConfig(); err != nil {
260
return errkit.Newf("could not write nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)
261
}
262
return nil
263
}
264
265
// ReadTemplatesConfig reads the nuclei templates config file
266
func (c *Config) ReadTemplatesConfig() error {
267
if !fileutil.FileExists(c.getTemplatesConfigFilePath()) {
268
return errkit.Newf("nuclei config file at %s does not exist", c.getTemplatesConfigFilePath())
269
}
270
var cfg *Config
271
bin, err := os.ReadFile(c.getTemplatesConfigFilePath())
272
if err != nil {
273
return errkit.Newf("could not read nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)
274
}
275
if err := json.Unmarshal(bin, &cfg); err != nil {
276
return errkit.Newf("could not unmarshal nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)
277
}
278
// apply config
279
c.TemplatesDirectory = cfg.TemplatesDirectory
280
c.TemplateVersion = cfg.TemplateVersion
281
c.NucleiIgnoreHash = cfg.NucleiIgnoreHash
282
c.LatestNucleiIgnoreHash = cfg.LatestNucleiIgnoreHash
283
c.LatestNucleiTemplatesVersion = cfg.LatestNucleiTemplatesVersion
284
return nil
285
}
286
287
// WriteTemplatesConfig writes the nuclei templates config file
288
func (c *Config) WriteTemplatesConfig() error {
289
// check if config folder exists if not create one
290
if err := c.createConfigDirIfNotExists(); err != nil {
291
return err
292
}
293
bin, err := json.Marshal(c)
294
if err != nil {
295
return errkit.Newf("failed to marshal nuclei config: %v", err)
296
}
297
if err = os.WriteFile(c.getTemplatesConfigFilePath(), bin, 0600); err != nil {
298
return errkit.Newf("failed to write nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)
299
}
300
return nil
301
}
302
303
// WriteTemplatesIndex writes the nuclei templates index file
304
func (c *Config) WriteTemplatesIndex(index map[string]string) error {
305
indexFile := c.GetTemplateIndexFilePath()
306
var buff bytes.Buffer
307
for k, v := range index {
308
_, _ = buff.WriteString(k + "," + v + "\n")
309
}
310
return os.WriteFile(indexFile, buff.Bytes(), 0600)
311
}
312
313
// getTemplatesConfigFilePath returns configDir/.templates-config.json file path
314
func (c *Config) getTemplatesConfigFilePath() string {
315
return filepath.Join(c.configDir, TemplateConfigFileName)
316
}
317
318
// createConfigDirIfNotExists creates the nuclei config directory if not exists
319
func (c *Config) createConfigDirIfNotExists() error {
320
if !fileutil.FolderExists(c.configDir) {
321
if err := fileutil.CreateFolder(c.configDir); err != nil {
322
return errkit.Newf("could not create nuclei config directory at %s: %v", c.configDir, err)
323
}
324
}
325
return nil
326
}
327
328
// copyIgnoreFile copies the nuclei ignore file default config directory
329
// to the current config directory
330
func (c *Config) copyIgnoreFile() {
331
if err := c.createConfigDirIfNotExists(); err != nil {
332
c.Logger.Error().Msgf("Could not create nuclei config directory at %s: %s", c.configDir, err)
333
return
334
}
335
ignoreFilePath := c.GetIgnoreFilePath()
336
if !fileutil.FileExists(ignoreFilePath) {
337
// copy ignore file from default config directory
338
if err := fileutil.CopyFile(filepath.Join(folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName), NucleiIgnoreFileName), ignoreFilePath); err != nil {
339
c.Logger.Error().Msgf("Could not copy nuclei ignore file at %s: %s", ignoreFilePath, err)
340
}
341
}
342
}
343
344
// IsDebugArgEnabled checks if debug arg is enabled
345
// this could be a feature specific to debugging like PPROF or printing stats
346
// of max host error etc
347
func (c *Config) IsDebugArgEnabled(arg string) bool {
348
return slices.Contains(c.debugArgs, arg)
349
}
350
351
// parseDebugArgs from string
352
func (c *Config) parseDebugArgs(data string) {
353
// use space as seperator instead of commas
354
tmp := strings.Fields(data)
355
for _, v := range tmp {
356
key := v
357
value := ""
358
// if it is key value pair then split it
359
if strings.Contains(v, "=") {
360
parts := strings.SplitN(v, "=", 2)
361
if len(parts) != 2 {
362
continue
363
}
364
key, value = strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
365
}
366
if value == "false" || value == "0" {
367
// if false or disabled then skip
368
continue
369
}
370
switch key {
371
case DebugArgHostErrorStats:
372
c.debugArgs = append(c.debugArgs, DebugArgHostErrorStats)
373
case DebugExportURLPattern:
374
c.debugArgs = append(c.debugArgs, DebugExportURLPattern)
375
}
376
}
377
}
378
379
func init() {
380
ConfigDir := folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName)
381
382
if cfgDir := os.Getenv(NucleiConfigDirEnv); cfgDir != "" {
383
ConfigDir = cfgDir
384
}
385
386
// create config directory if not exists
387
if !fileutil.FolderExists(ConfigDir) {
388
if err := fileutil.CreateFolder(ConfigDir); err != nil {
389
gologger.Error().Msgf("failed to create config directory at %v got: %s", ConfigDir, err)
390
}
391
}
392
DefaultConfig = &Config{
393
homeDir: folderutil.HomeDirOrDefault(""),
394
configDir: ConfigDir,
395
Logger: gologger.DefaultLogger,
396
}
397
398
// when enabled will log events in more verbosity than -v or -debug
399
// ex: N templates are excluded
400
// with this switch enabled nuclei will print details of above N templates
401
if value := env.GetEnvOrDefault("NUCLEI_LOG_ALL", false); value {
402
DefaultConfig.LogAllEvents = true
403
}
404
if value := env.GetEnvOrDefault("HIDE_TEMPLATE_SIG_WARNING", false); value {
405
DefaultConfig.HideTemplateSigWarning = true
406
}
407
408
// try to read config from file
409
if err := DefaultConfig.ReadTemplatesConfig(); err != nil {
410
gologger.Verbose().Msgf("config file not found, creating new config file at %s", DefaultConfig.getTemplatesConfigFilePath())
411
applyDefaultConfig()
412
// write config to file
413
if err := DefaultConfig.WriteTemplatesConfig(); err != nil {
414
gologger.Error().Msgf("failed to write config file at %s got: %s", DefaultConfig.getTemplatesConfigFilePath(), err)
415
}
416
}
417
418
// Loads/updates paths of custom templates
419
// Note: custom templates paths should not be updated in config file
420
// and even if it is changed we don't follow it since it is not expected behavior
421
// If custom templates are in default locations only then they are loaded while running nuclei
422
DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory)
423
DefaultConfig.parseDebugArgs(env.GetEnvOrDefault("NUCLEI_ARGS", ""))
424
}
425
426
// Add Default Config adds default when .templates-config.json file is not present
427
func applyDefaultConfig() {
428
DefaultConfig.TemplatesDirectory = filepath.Join(DefaultConfig.homeDir, NucleiTemplatesDirName)
429
// updates all necessary paths
430
DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory)
431
}
432
433