Path: blob/dev/pkg/catalog/config/nucleiconfig.go
2070 views
package config12import (3"bytes"4"crypto/md5"5"fmt"6"os"7"path/filepath"8"slices"9"strings"10"sync"1112"github.com/projectdiscovery/gologger"13"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"14"github.com/projectdiscovery/utils/env"15"github.com/projectdiscovery/utils/errkit"16fileutil "github.com/projectdiscovery/utils/file"17folderutil "github.com/projectdiscovery/utils/folder"18)1920// DefaultConfig is the default nuclei configuration21// all config values and default are centralized here22var DefaultConfig *Config2324type Config struct {25TemplatesDirectory string `json:"nuclei-templates-directory,omitempty"`2627// customtemplates exists in templates directory with the name of custom-templates provider28// below custom paths are absolute paths to respective custom-templates directories29CustomS3TemplatesDirectory string `json:"custom-s3-templates-directory"`30CustomGitHubTemplatesDirectory string `json:"custom-github-templates-directory"`31CustomGitLabTemplatesDirectory string `json:"custom-gitlab-templates-directory"`32CustomAzureTemplatesDirectory string `json:"custom-azure-templates-directory"`3334TemplateVersion string `json:"nuclei-templates-version,omitempty"`35NucleiIgnoreHash string `json:"nuclei-ignore-hash,omitempty"`36LogAllEvents bool `json:"-"` // when enabled logs all events (more than verbose)37HideTemplateSigWarning bool `json:"-"` // when enabled disables template signature warning3839// LatestXXX are not meant to be used directly and is used as40// local cache of nuclei version check endpoint41// these fields are only update during nuclei version check42// TODO: move these fields to a separate unexported struct as they are not meant to be used directly43LatestNucleiVersion string `json:"nuclei-latest-version"`44LatestNucleiTemplatesVersion string `json:"nuclei-templates-latest-version"`45LatestNucleiIgnoreHash string `json:"nuclei-latest-ignore-hash,omitempty"`46Logger *gologger.Logger `json:"-"` // logger4748// internal / unexported fields49disableUpdates bool `json:"-"` // disable updates both version check and template updates50homeDir string `json:"-"` // User Home Directory51configDir string `json:"-"` // Nuclei Global Config Directory52debugArgs []string `json:"-"` // debug args5354m sync.Mutex55}5657// IsCustomTemplate determines whether a given template is custom-built or part of the official Nuclei templates.58// It checks if the template's path matches any of the predefined custom template directories59// (such as S3, GitHub, GitLab, and Azure directories). If the template resides in any of these directories,60// it is considered custom. Additionally, if the template's path does not start with the main Nuclei TemplatesDirectory,61// it is also considered custom. This function assumes that template paths are either absolute62// or relative to the same base as the paths configured in DefaultConfig.63func (c *Config) IsCustomTemplate(templatePath string) bool {64customDirs := []string{65c.CustomS3TemplatesDirectory,66c.CustomGitHubTemplatesDirectory,67c.CustomGitLabTemplatesDirectory,68c.CustomAzureTemplatesDirectory,69}7071for _, dir := range customDirs {72if strings.HasPrefix(templatePath, dir) {73return true74}75}76return !strings.HasPrefix(templatePath, c.TemplatesDirectory)77}7879// WriteVersionCheckData writes version check data to config file80func (c *Config) WriteVersionCheckData(ignorehash, nucleiVersion, templatesVersion string) error {81updated := false82if ignorehash != "" && c.LatestNucleiIgnoreHash != ignorehash {83c.LatestNucleiIgnoreHash = ignorehash84updated = true85}86if nucleiVersion != "" && c.LatestNucleiVersion != nucleiVersion {87c.LatestNucleiVersion = nucleiVersion88updated = true89}90if templatesVersion != "" && c.LatestNucleiTemplatesVersion != templatesVersion {91c.LatestNucleiTemplatesVersion = templatesVersion92updated = true93}94// write config to disk if any of the fields are updated95if updated {96return c.WriteTemplatesConfig()97}98return nil99}100101// GetTemplateDir returns the nuclei templates directory absolute path102func (c *Config) GetTemplateDir() string {103val, _ := filepath.Abs(c.TemplatesDirectory)104return val105}106107// DisableUpdateCheck disables update check and template updates108func (c *Config) DisableUpdateCheck() {109c.m.Lock()110defer c.m.Unlock()111c.disableUpdates = true112}113114// CanCheckForUpdates returns true if update check is enabled115func (c *Config) CanCheckForUpdates() bool {116c.m.Lock()117defer c.m.Unlock()118return !c.disableUpdates119}120121// NeedsTemplateUpdate returns true if template installation/update is required122func (c *Config) NeedsTemplateUpdate() bool {123c.m.Lock()124defer c.m.Unlock()125return !c.disableUpdates && (c.TemplateVersion == "" || IsOutdatedVersion(c.TemplateVersion, c.LatestNucleiTemplatesVersion) || !fileutil.FolderExists(c.TemplatesDirectory))126}127128// NeedsIgnoreFileUpdate returns true if Ignore file hash is different (aka ignore file is outdated)129func (c *Config) NeedsIgnoreFileUpdate() bool {130c.m.Lock()131defer c.m.Unlock()132return c.NucleiIgnoreHash == "" || c.NucleiIgnoreHash != c.LatestNucleiIgnoreHash133}134135// UpdateNucleiIgnoreHash updates the nuclei ignore hash in config136func (c *Config) UpdateNucleiIgnoreHash() error {137// calculate hash of ignore file and update config138ignoreFilePath := c.GetIgnoreFilePath()139if fileutil.FileExists(ignoreFilePath) {140bin, err := os.ReadFile(ignoreFilePath)141if err != nil {142return errkit.Newf("could not read nuclei ignore file: %v", err)143}144c.NucleiIgnoreHash = fmt.Sprintf("%x", md5.Sum(bin))145// write config to disk146return c.WriteTemplatesConfig()147}148return errkit.New("ignore file not found: could not update nuclei ignore hash")149}150151// GetConfigDir returns the nuclei configuration directory152func (c *Config) GetConfigDir() string {153return c.configDir154}155156// GetKeysDir returns the nuclei signer keys directory157func (c *Config) GetKeysDir() string {158return filepath.Join(c.configDir, "keys")159}160161// GetAllCustomTemplateDirs returns all custom template directories162func (c *Config) GetAllCustomTemplateDirs() []string {163return []string{c.CustomS3TemplatesDirectory, c.CustomGitHubTemplatesDirectory, c.CustomGitLabTemplatesDirectory, c.CustomAzureTemplatesDirectory}164}165166// GetReportingConfigFilePath returns the nuclei reporting config file path167func (c *Config) GetReportingConfigFilePath() string {168return filepath.Join(c.configDir, ReportingConfigFilename)169}170171// GetIgnoreFilePath returns the nuclei ignore file path172func (c *Config) GetIgnoreFilePath() string {173return filepath.Join(c.configDir, NucleiIgnoreFileName)174}175176func (c *Config) GetTemplateIndexFilePath() string {177return filepath.Join(c.TemplatesDirectory, NucleiTemplatesIndexFileName)178}179180// GetChecksumFilePath returns checksum file path of nuclei templates181func (c *Config) GetChecksumFilePath() string {182return filepath.Join(c.TemplatesDirectory, NucleiTemplatesCheckSumFileName)183}184185// GetFlagsConfigFilePath returns the nuclei cli config file path186func (c *Config) GetFlagsConfigFilePath() string {187return filepath.Join(c.configDir, CLIConfigFileName)188}189190// GetNewAdditions returns new template additions in current template release191// if .new-additions file is not present empty slice is returned192func (c *Config) GetNewAdditions() []string {193arr := []string{}194newAdditionsPath := filepath.Join(c.TemplatesDirectory, NewTemplateAdditionsFileName)195if !fileutil.FileExists(newAdditionsPath) {196return arr197}198bin, err := os.ReadFile(newAdditionsPath)199if err != nil {200return arr201}202for _, v := range strings.Fields(string(bin)) {203if IsTemplate(v) {204arr = append(arr, v)205}206}207return arr208}209210// GetCacheDir returns the nuclei cache directory211// with new version of nuclei cache directory is changed212// instead of saving resume files in nuclei config directory213// they are saved in nuclei cache directory214func (c *Config) GetCacheDir() string {215return folderutil.AppCacheDirOrDefault(".nuclei-cache", BinaryName)216}217218// SetConfigDir sets the nuclei configuration directory219// and appropriate changes are made to the config220func (c *Config) SetConfigDir(dir string) {221c.configDir = dir222if err := c.createConfigDirIfNotExists(); err != nil {223c.Logger.Fatal().Msgf("Could not create nuclei config directory at %s: %s", c.configDir, err)224}225226// if folder already exists read config or create new227if err := c.ReadTemplatesConfig(); err != nil {228// create new config229applyDefaultConfig()230if err2 := c.WriteTemplatesConfig(); err2 != nil {231c.Logger.Fatal().Msgf("Could not create nuclei config file at %s: %s", c.getTemplatesConfigFilePath(), err2)232}233}234235// while other config files are optional, ignore file is mandatory236// since it is used to ignore templates with weak matchers237c.copyIgnoreFile()238}239240// SetTemplatesDir sets the new nuclei templates directory241func (c *Config) SetTemplatesDir(dirPath string) {242if dirPath != "" && !filepath.IsAbs(dirPath) {243cwd, _ := os.Getwd()244dirPath = filepath.Join(cwd, dirPath)245}246c.TemplatesDirectory = dirPath247// Update the custom templates directory248c.CustomGitHubTemplatesDirectory = filepath.Join(dirPath, CustomGitHubTemplatesDirName)249c.CustomS3TemplatesDirectory = filepath.Join(dirPath, CustomS3TemplatesDirName)250c.CustomGitLabTemplatesDirectory = filepath.Join(dirPath, CustomGitLabTemplatesDirName)251c.CustomAzureTemplatesDirectory = filepath.Join(dirPath, CustomAzureTemplatesDirName)252}253254// SetTemplatesVersion sets the new nuclei templates version255func (c *Config) SetTemplatesVersion(version string) error {256c.TemplateVersion = version257// write config to disk258if err := c.WriteTemplatesConfig(); err != nil {259return errkit.Newf("could not write nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)260}261return nil262}263264// ReadTemplatesConfig reads the nuclei templates config file265func (c *Config) ReadTemplatesConfig() error {266if !fileutil.FileExists(c.getTemplatesConfigFilePath()) {267return errkit.Newf("nuclei config file at %s does not exist", c.getTemplatesConfigFilePath())268}269var cfg *Config270bin, err := os.ReadFile(c.getTemplatesConfigFilePath())271if err != nil {272return errkit.Newf("could not read nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)273}274if err := json.Unmarshal(bin, &cfg); err != nil {275return errkit.Newf("could not unmarshal nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)276}277// apply config278c.TemplatesDirectory = cfg.TemplatesDirectory279c.TemplateVersion = cfg.TemplateVersion280c.NucleiIgnoreHash = cfg.NucleiIgnoreHash281c.LatestNucleiIgnoreHash = cfg.LatestNucleiIgnoreHash282c.LatestNucleiTemplatesVersion = cfg.LatestNucleiTemplatesVersion283return nil284}285286// WriteTemplatesConfig writes the nuclei templates config file287func (c *Config) WriteTemplatesConfig() error {288// check if config folder exists if not create one289if err := c.createConfigDirIfNotExists(); err != nil {290return err291}292bin, err := json.Marshal(c)293if err != nil {294return errkit.Newf("failed to marshal nuclei config: %v", err)295}296if err = os.WriteFile(c.getTemplatesConfigFilePath(), bin, 0600); err != nil {297return errkit.Newf("failed to write nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)298}299return nil300}301302// WriteTemplatesIndex writes the nuclei templates index file303func (c *Config) WriteTemplatesIndex(index map[string]string) error {304indexFile := c.GetTemplateIndexFilePath()305var buff bytes.Buffer306for k, v := range index {307_, _ = buff.WriteString(k + "," + v + "\n")308}309return os.WriteFile(indexFile, buff.Bytes(), 0600)310}311312// getTemplatesConfigFilePath returns configDir/.templates-config.json file path313func (c *Config) getTemplatesConfigFilePath() string {314return filepath.Join(c.configDir, TemplateConfigFileName)315}316317// createConfigDirIfNotExists creates the nuclei config directory if not exists318func (c *Config) createConfigDirIfNotExists() error {319if !fileutil.FolderExists(c.configDir) {320if err := fileutil.CreateFolder(c.configDir); err != nil {321return errkit.Newf("could not create nuclei config directory at %s: %v", c.configDir, err)322}323}324return nil325}326327// copyIgnoreFile copies the nuclei ignore file default config directory328// to the current config directory329func (c *Config) copyIgnoreFile() {330if err := c.createConfigDirIfNotExists(); err != nil {331c.Logger.Error().Msgf("Could not create nuclei config directory at %s: %s", c.configDir, err)332return333}334ignoreFilePath := c.GetIgnoreFilePath()335if !fileutil.FileExists(ignoreFilePath) {336// copy ignore file from default config directory337if err := fileutil.CopyFile(filepath.Join(folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName), NucleiIgnoreFileName), ignoreFilePath); err != nil {338c.Logger.Error().Msgf("Could not copy nuclei ignore file at %s: %s", ignoreFilePath, err)339}340}341}342343// IsDebugArgEnabled checks if debug arg is enabled344// this could be a feature specific to debugging like PPROF or printing stats345// of max host error etc346func (c *Config) IsDebugArgEnabled(arg string) bool {347return slices.Contains(c.debugArgs, arg)348}349350// parseDebugArgs from string351func (c *Config) parseDebugArgs(data string) {352// use space as seperator instead of commas353tmp := strings.Fields(data)354for _, v := range tmp {355key := v356value := ""357// if it is key value pair then split it358if strings.Contains(v, "=") {359parts := strings.SplitN(v, "=", 2)360if len(parts) != 2 {361continue362}363key, value = strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])364}365if value == "false" || value == "0" {366// if false or disabled then skip367continue368}369switch key {370case DebugArgHostErrorStats:371c.debugArgs = append(c.debugArgs, DebugArgHostErrorStats)372case DebugExportURLPattern:373c.debugArgs = append(c.debugArgs, DebugExportURLPattern)374}375}376}377378func init() {379ConfigDir := folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName)380381if cfgDir := os.Getenv(NucleiConfigDirEnv); cfgDir != "" {382ConfigDir = cfgDir383}384385// create config directory if not exists386if !fileutil.FolderExists(ConfigDir) {387if err := fileutil.CreateFolder(ConfigDir); err != nil {388gologger.Error().Msgf("failed to create config directory at %v got: %s", ConfigDir, err)389}390}391DefaultConfig = &Config{392homeDir: folderutil.HomeDirOrDefault(""),393configDir: ConfigDir,394Logger: gologger.DefaultLogger,395}396397// when enabled will log events in more verbosity than -v or -debug398// ex: N templates are excluded399// with this switch enabled nuclei will print details of above N templates400if value := env.GetEnvOrDefault("NUCLEI_LOG_ALL", false); value {401DefaultConfig.LogAllEvents = true402}403if value := env.GetEnvOrDefault("HIDE_TEMPLATE_SIG_WARNING", false); value {404DefaultConfig.HideTemplateSigWarning = true405}406407// try to read config from file408if err := DefaultConfig.ReadTemplatesConfig(); err != nil {409gologger.Verbose().Msgf("config file not found, creating new config file at %s", DefaultConfig.getTemplatesConfigFilePath())410applyDefaultConfig()411// write config to file412if err := DefaultConfig.WriteTemplatesConfig(); err != nil {413gologger.Error().Msgf("failed to write config file at %s got: %s", DefaultConfig.getTemplatesConfigFilePath(), err)414}415}416417// Loads/updates paths of custom templates418// Note: custom templates paths should not be updated in config file419// and even if it is changed we don't follow it since it is not expected behavior420// If custom templates are in default locations only then they are loaded while running nuclei421DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory)422DefaultConfig.parseDebugArgs(env.GetEnvOrDefault("NUCLEI_ARGS", ""))423}424425// Add Default Config adds default when .templates-config.json file is not present426func applyDefaultConfig() {427DefaultConfig.TemplatesDirectory = filepath.Join(DefaultConfig.homeDir, NucleiTemplatesDirName)428// updates all necessary paths429DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory)430}431432433