nuclei/v2/internal/runner/options.go

225 lines
7.0 KiB
Go
Raw Normal View History

2020-04-04 03:45:39 +05:30
package runner
import (
"bufio"
"io"
"log"
2020-04-04 03:45:39 +05:30
"os"
2021-08-27 17:06:06 +03:00
"path/filepath"
"strings"
2020-04-04 03:45:39 +05:30
"github.com/pkg/errors"
"github.com/go-playground/validator/v10"
2021-10-07 12:36:27 +02:00
"github.com/projectdiscovery/fileutil"
2020-04-04 03:45:39 +05:30
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/formatter"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
2020-04-04 03:45:39 +05:30
)
// ParseOptions parses the command line flags provided by a user
func ParseOptions(options *types.Options) {
2020-04-04 03:45:39 +05:30
// Check if stdin pipe was given
options.Stdin = hasStdin()
// Read the inputs and configure the logging
configureOutput(options)
2020-04-04 03:45:39 +05:30
// Show the user the banner
showBanner()
if !filepath.IsAbs(options.TemplatesDirectory) {
cwd, _ := os.Getwd()
options.TemplatesDirectory = filepath.Join(cwd, options.TemplatesDirectory)
}
2020-04-04 03:45:39 +05:30
if options.Version {
gologger.Info().Msgf("Current Version: %s\n", config.Version)
2020-04-04 03:45:39 +05:30
os.Exit(0)
}
if options.TemplatesVersion {
2021-07-05 17:29:45 +05:30
configuration, err := config.ReadConfiguration()
if err != nil {
gologger.Fatal().Msgf("Could not read template configuration: %s\n", err)
}
gologger.Info().Msgf("Current nuclei-templates version: %s (%s)\n", configuration.TemplateVersion, configuration.TemplatesDirectory)
os.Exit(0)
}
2020-04-04 03:45:39 +05:30
// Validate the options passed by the user and if any
// invalid options have been used, exit.
if err := validateOptions(options); err != nil {
gologger.Fatal().Msgf("Program exiting: %s\n", err)
2020-04-04 03:45:39 +05:30
}
// Load the resolvers if user asked for them
loadResolvers(options)
2021-10-07 12:36:27 +02:00
// removes all cli variables containing payloads and add them to the internal struct
for key, value := range options.Vars.AsMap() {
if fileutil.FileExists(value.(string)) {
2021-10-07 12:40:18 +02:00
_ = options.Vars.Del(key)
2021-10-07 12:36:27 +02:00
options.AddVarPayload(key, value)
}
}
err := protocolinit.Init(options)
if err != nil {
gologger.Fatal().Msgf("Could not initialize protocols: %s\n", err)
}
2020-04-04 03:45:39 +05:30
}
// hasStdin returns true if we have stdin input
2020-04-04 03:45:39 +05:30
func hasStdin() bool {
fi, err := os.Stdin.Stat()
2020-04-04 03:45:39 +05:30
if err != nil {
return false
}
if fi.Mode()&os.ModeNamedPipe == 0 {
return false
}
return true
2020-04-04 03:45:39 +05:30
}
2020-08-29 15:26:11 +02:00
// validateOptions validates the configuration options passed
func validateOptions(options *types.Options) error {
validate := validator.New()
if err := validate.Struct(options); err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
return err
}
errs := []string{}
for _, err := range err.(validator.ValidationErrors) {
errs = append(errs, err.Namespace()+": "+err.Tag())
}
return errors.Wrap(errors.New(strings.Join(errs, ", ")), "validation failed for these fields")
}
2020-08-29 15:26:11 +02:00
if options.Verbose && options.Silent {
return errors.New("both verbose and silent mode specified")
}
// loading the proxy server list from file or cli and test the connectivity
if err := loadProxyServers(options); err != nil {
2020-08-29 16:25:30 +02:00
return err
}
2021-08-27 17:06:06 +03:00
if options.Validate {
options.Headless = true // required for correct validation of headless templates
2021-08-27 17:06:06 +03:00
validateTemplatePaths(options.TemplatesDirectory, options.Templates, options.Workflows)
}
// Verify if any of the client certificate options were set since it requires all three to work properly
if len(options.ClientCertFile) > 0 || len(options.ClientKeyFile) > 0 || len(options.ClientCAFile) > 0 {
if len(options.ClientCertFile) == 0 || len(options.ClientKeyFile) == 0 || len(options.ClientCAFile) == 0 {
return errors.New("if a client certification option is provided, then all three must be provided")
}
validateCertificatePaths([]string{options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile})
}
// expand include/exclude templates id filenames
if includeIds, err := processIdsFiltering(options.IncludeIds); err != nil {
return err
} else {
options.IncludeIds = includeIds
}
if excludeIds, err := processIdsFiltering(options.ExcludeIds); err != nil {
return err
} else {
options.ExcludeIds = excludeIds
}
2020-08-29 16:25:30 +02:00
return nil
}
func processIdsFiltering(ids []string) ([]string, error) {
var finalIds []string
for _, id := range ids {
if fileutil.FileExists(id) {
fileIds, err := utils.LoadFile(id)
if err != nil {
return nil, err
}
finalIds = append(finalIds, fileIds...)
} else {
finalIds = append(finalIds, id)
}
}
return finalIds, nil
}
// configureOutput configures the output logging levels to be displayed on the screen
func configureOutput(options *types.Options) {
2020-08-29 15:26:11 +02:00
// If the user desires verbose output, show verbose output
2021-12-01 10:35:18 -06:00
if options.Verbose || options.Validate {
gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
}
if options.Debug || options.DebugRequests || options.DebugResponse {
gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug)
2020-08-29 15:26:11 +02:00
}
if options.NoColor {
gologger.DefaultLogger.SetFormatter(formatter.NewCLI(true))
2020-08-29 15:26:11 +02:00
}
if options.Silent {
gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
2020-08-29 15:26:11 +02:00
}
// disable standard logger (ref: https://github.com/golang/go/issues/19895)
log.SetFlags(0)
log.SetOutput(io.Discard)
2020-08-29 15:26:11 +02:00
}
// loadResolvers loads resolvers from both user provided flag and file
func loadResolvers(options *types.Options) {
if options.ResolversFile == "" {
return
}
file, err := os.Open(options.ResolversFile)
if err != nil {
gologger.Fatal().Msgf("Could not open resolvers file: %s\n", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
part := scanner.Text()
if part == "" {
continue
}
if strings.Contains(part, ":") {
options.InternalResolversList = append(options.InternalResolversList, part)
} else {
options.InternalResolversList = append(options.InternalResolversList, part+":53")
}
}
}
2021-08-27 17:06:06 +03:00
func validateTemplatePaths(templatesDirectory string, templatePaths, workflowPaths []string) {
allGivenTemplatePaths := append(templatePaths, workflowPaths...)
for _, templatePath := range allGivenTemplatePaths {
if templatesDirectory != templatePath && filepath.IsAbs(templatePath) {
fileInfo, err := os.Stat(templatePath)
if err == nil && fileInfo.IsDir() {
relativizedPath, err2 := filepath.Rel(templatesDirectory, templatePath)
if err2 != nil || (len(relativizedPath) >= 2 && relativizedPath[:2] == "..") {
gologger.Warning().Msgf("The given path (%s) is outside the default template directory path (%s)! "+
"Referenced sub-templates with relative paths in workflows will be resolved against the default template directory.", templatePath, templatesDirectory)
break
}
}
}
}
}
func validateCertificatePaths(certificatePaths []string) {
for _, certificatePath := range certificatePaths {
if _, err := os.Stat(certificatePath); os.IsNotExist(err) {
// The provided path to the PEM certificate does not exist for the client authentication. As this is
// required for successful authentication, log and return an error
gologger.Fatal().Msgf("The given path (%s) to the certificate does not exist!", certificatePath)
break
}
}
}