Add nuclei template parsing support

This commit is contained in:
Sol Fisher Romanoff
2023-09-06 12:18:15 +03:00
parent 43b2a7e355
commit 39def8df6a
5 changed files with 227 additions and 0 deletions

215
cmd/nuclei.go Normal file
View File

@@ -0,0 +1,215 @@
package cmd
import (
"bufio"
"fmt"
"io"
"net/http"
"os"
"strings"
"sync"
"github.com/charmbracelet/log"
"gopkg.in/yaml.v3"
)
const (
nucleiURL = "https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/v9.6.2/"
nucleiFile = "templates-checksum.txt"
)
// only process attributes that can be used, with little to no metadata.
// there is no need to run any enum matching, because we trust nuclei-templates to have the proper
// value types, and we take the templates straight from their repository.
type Template struct {
ID string
Info struct {
Severity string
}
HTTP []struct {
Path []string
Raw []string
Attack string
Method string
Body string
Payloads map[string]interface{}
Headers map[string]string
RaceCount int
MaxRedirects int
PipelineCurrentConnections int
PipelineRequestsPerConnection int
Threads int
MaxSize int
Fuzzing []string
CookieReuse bool
ReadAll bool
Redirects bool
HostRedirects bool
Pipeline bool
Unsafe bool
Race bool
ReqCondition bool
StopAtFirstMatch bool
SkipVariablesCheck bool
IterateAll bool
DigestUsername string
DigestPassword string
DisablePathAutomerge bool
}
DNS []struct {
Type string
Retries int
Trace bool
TraceMaxRecursion int
Attack string
Payloads map[string]interface{}
Recursion bool
Resolvers []string
}
File []struct {
Extensions []string
DenyList []string
MaxSize string
Archive bool
MIMEType bool
NoRecursive bool
}
TCP []struct {
Host []string
Attack string
Payloads map[string]interface{}
Inputs []struct {
Data string
Type string
Read int
}
ReadSize int
ReadAll bool
}
Headless []struct {
Attack string
Payloads map[string]interface{}
Steps []struct {
Args map[string]string
Action string
}
UserAgent string
CustomUserAgent string
StopAtFirstMatch bool
Fuzzing []struct {
Type string
Part string
Mode string
Keys []string
KeysRegex []string
Values []string
Fuzz []string
}
CookieReuse bool
}
SSL []struct {
Address string
MinVersion string
MaxVersion string
CipherSuites []string
ScanMode string
}
Websocket []struct {
Address string
Inputs []struct {
Data string
}
Headers map[string]string
Attack string
Payloads map[string]interface{}
}
Whois []struct {
Query string
Server string
}
SelfContained bool
StopAtFirstMatch bool
Signature string
Variables map[string]string
Constants map[string]interface{}
}
func Nuclei(url string, threads int, logdir string) {
fmt.Println(separator.Render("⚛️ Starting " + statusstyle.Render("nuclei template scanning") + "..."))
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
f, err := os.OpenFile(logdir+"/"+sanitizedURL+".log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Errorf("Error creating log file: %s", err)
return
}
defer f.Close()
f.WriteString(fmt.Sprintf("\n\n--------------\nStarting nuclei template scanning...\n--------------\n"))
}
logger := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "nuclei ⚛️",
})
nucleilog := logger.With("url", url)
// We don't set timeout because it is specified by nuclei templates.
// This &http.Client is only used for fetching the templates themselves from GitHub.
client := &http.Client{}
resp, err := client.Get(nucleiURL + nucleiFile)
if err != nil {
log.Errorf("Error downloading nuclei template list: %v", err)
return
}
defer resp.Body.Close()
var templateFiles []string
scanner := bufio.NewScanner(resp.Body)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
templateFiles = append(templateFiles, scanner.Text())
}
var wg sync.WaitGroup
wg.Add(threads)
for thread := 0; thread < threads; thread++ {
go func(thread int) {
defer wg.Done()
for i, templateFile := range templateFiles {
if i%threads != thread {
continue
}
if !strings.Contains(templateFile, ".yaml:") {
continue
}
templateFile = strings.Split(templateFile, ":")[0]
resp, err := client.Get(nucleiURL + templateFile)
if err != nil {
nucleilog.Errorf("Error downloading nuclei template: %v", err)
continue
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
template := Template{}
err = yaml.Unmarshal(data, &template)
if err != nil {
nucleilog.Errorf("Error reading nuclei template: %v", err)
nucleilog.Errorf(string(data))
continue
}
if template.Info.Severity == "undefined" || template.Info.Severity == "info" || template.Info.Severity == "unknown" {
continue
}
log.Info(template.ID)
}
}(thread)
}
wg.Wait()
}

View File

@@ -20,6 +20,7 @@ type Settings struct {
Dorking bool
Git bool
Threads int
Nuclei bool
Timeout time.Duration
}
@@ -37,6 +38,7 @@ func parseURLs() Settings {
var noscan = pflag.Bool("noscan", false, "Do not perform base URL (robots.txt, etc) scanning")
var git = pflag.Bool("git", false, "Enable git repository scanning")
var threads = pflag.Int("threads", 10, "Number of threads to run scans on")
var nuclei = pflag.Bool("nuclei", false, "Scan for vulnerabilities using nuclei templates")
pflag.Parse()
if len(*url) > 0 {
@@ -52,6 +54,7 @@ func parseURLs() Settings {
LogDir: *logdir,
Threads: *threads,
Git: *git,
Nuclei: *nuclei,
}
} else if *file != "" {
if _, err := os.Stat(*file); err != nil {
@@ -84,6 +87,7 @@ func parseURLs() Settings {
LogDir: *logdir,
Threads: *threads,
Git: *git,
Nuclei: *nuclei,
}
}

1
go.mod
View File

@@ -9,6 +9,7 @@ require (
github.com/charmbracelet/log v0.2.4
github.com/rocketlaunchr/google-search v1.1.6
github.com/spf13/pflag v1.0.5
gopkg.in/yaml.v3 v3.0.1
)
require (

3
go.sum
View File

@@ -218,6 +218,9 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -108,6 +108,10 @@ func main() {
cmd.Git(url, settings.Timeout, settings.Threads, settings.LogDir)
}
if settings.Nuclei {
cmd.Nuclei(url, settings.Threads, settings.LogDir)
}
// TODO: WHOIS
fmt.Println()