mirror of https://github.com/daffainfo/nuclei.git
Merge branch 'vzamanillo-golang-cli'
commit
09da97ae93
|
@ -4,11 +4,33 @@ on:
|
|||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: golangci-lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v1
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
version: v1.29
|
||||
args: --timeout 5m
|
||||
|
||||
# Optional: working directory, useful for monorepos
|
||||
# working-directory: somedir
|
||||
|
||||
# Optional: golangci-lint command line arguments.
|
||||
# args: --issues-exit-code=0
|
||||
|
||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||
# only-new-issues: true
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
|
@ -20,8 +42,8 @@ jobs:
|
|||
|
||||
- name: Test
|
||||
run: go test .
|
||||
working-directory: v2/cmd/nuclei/
|
||||
working-directory: cmd/nuclei/
|
||||
|
||||
- name: Build
|
||||
run: go build .
|
||||
working-directory: v2/cmd/nuclei/
|
||||
working-directory: cmd/nuclei/
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
linters-settings:
|
||||
dupl:
|
||||
threshold: 100
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: false
|
||||
# funlen:
|
||||
# lines: 100
|
||||
# statements: 50
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- experimental
|
||||
- opinionated
|
||||
- performance
|
||||
- style
|
||||
disabled-checks:
|
||||
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
||||
- ifElseChain
|
||||
# gocyclo:
|
||||
# min-complexity: 15
|
||||
goimports:
|
||||
local-prefixes: github.com/golangci/golangci-lint
|
||||
golint:
|
||||
min-confidence: 0
|
||||
gomnd:
|
||||
settings:
|
||||
mnd:
|
||||
# don't include the "operation" and "assign"
|
||||
checks: argument,case,condition,return
|
||||
govet:
|
||||
check-shadowing: true
|
||||
settings:
|
||||
printf:
|
||||
funcs:
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
|
||||
# lll:
|
||||
# line-length: 140
|
||||
maligned:
|
||||
suggest-new: true
|
||||
misspell:
|
||||
locale: US
|
||||
nolintlint:
|
||||
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-explanation: false # don't require an explanation for nolint directives
|
||||
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
|
||||
|
||||
linters:
|
||||
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- exhaustive
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- gomnd
|
||||
- goprintffuncname
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- interfacer
|
||||
- maligned
|
||||
- misspell
|
||||
- nakedret
|
||||
- noctx
|
||||
- nolintlint
|
||||
- rowserrcheck
|
||||
- scopelint
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- varcheck
|
||||
- whitespace
|
||||
|
||||
# don't enable:
|
||||
# - depguard
|
||||
# - asciicheck
|
||||
# - funlen
|
||||
# - gochecknoglobals
|
||||
# - gocognit
|
||||
# - gocyclo
|
||||
# - godot
|
||||
# - godox
|
||||
# - goerr113
|
||||
# - gosec
|
||||
# - lll
|
||||
# - nestif
|
||||
# - prealloc
|
||||
# - testpackage
|
||||
# - wsl
|
||||
|
||||
# golangci.com configuration
|
||||
# https://github.com/golangci/golangci/wiki/Configuration
|
||||
service:
|
||||
golangci-lint-version: 1.29.x # use the fixed version to not introduce new linters unexpectedly
|
||||
prepare:
|
||||
- echo "here I can run custom commands, but no preparation needed for this repo"
|
|
@ -9,11 +9,11 @@ func main() {
|
|||
// Parse the command line flags and read config files
|
||||
options := runner.ParseOptions()
|
||||
|
||||
runner, err := runner.New(options)
|
||||
nucleiRunner, err := runner.New(options)
|
||||
if err != nil {
|
||||
gologger.Fatalf("Could not create runner: %s\n", err)
|
||||
}
|
||||
|
||||
runner.RunEnumeration()
|
||||
runner.Close()
|
||||
nucleiRunner.RunEnumeration()
|
||||
nucleiRunner.Close()
|
||||
}
|
|
@ -16,6 +16,7 @@ require (
|
|||
github.com/projectdiscovery/gologger v1.0.1
|
||||
github.com/projectdiscovery/retryabledns v1.0.4
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.1
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/vbauerster/mpb/v5 v5.2.4
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||
gopkg.in/yaml.v2 v2.3.0
|
|
@ -14,6 +14,7 @@ github.com/d5/tengo v1.24.8 h1:PRJ+NWt7ae/9sSbIfThOBTkPSvNV+dwYoBAvwfNgNJY=
|
|||
github.com/d5/tengo/v2 v2.6.0 h1:D0cJtpiBzaLJ/Smv6nnUc/LIfO46oKwDx85NZtIRNRI=
|
||||
github.com/d5/tengo/v2 v2.6.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
|
@ -32,6 +33,7 @@ github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/z
|
|||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
|
||||
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
|
@ -40,15 +42,20 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD
|
|||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/projectdiscovery/gologger v1.0.1 h1:FzoYQZnxz9DCvSi/eg5A6+ET4CQ0CDUs27l6Exr8zMQ=
|
||||
github.com/projectdiscovery/gologger v1.0.1/go.mod h1:Ok+axMqK53bWNwDSU1nTNwITLYMXMdZtRc8/y1c7sWE=
|
||||
github.com/projectdiscovery/nuclei v1.1.7 h1:5Z1fBHcjyAuuI89xcCzv8tYK7b6ucqLxs+mCC/nJjno=
|
||||
github.com/projectdiscovery/nuclei/v2 v2.1.0 h1:TUr9lwJ8lulBmEfz6AQHoKPiBQocl4PrPXSu3ekXsXY=
|
||||
github.com/projectdiscovery/nuclei/v2 v2.1.0/go.mod h1:iufrjj0m0PCZc8J8eVLZqZMyiCaMMv4R5d6QKBdqpZA=
|
||||
github.com/projectdiscovery/retryabledns v1.0.4 h1:0Va7qHlWQsIXjRLISTjzfN3tnJmHYDudY05Nu3IJd60=
|
||||
github.com/projectdiscovery/retryabledns v1.0.4/go.mod h1:/UzJn4I+cPdQl6pKiiQfvVAT636YZvJQYZhYhGB0dUQ=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.1 h1:V7wUvsZNq1Rcz7+IlcyoyQlNwshuwptuBVYWw9lx8RE=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.1/go.mod h1:SrN6iLZilNG1X4neq1D+SBxoqfAF4nyzvmevkTkWsek=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/vbauerster/mpb v1.1.3 h1:IRgic8VFaURXkW0VxDLkNOiNaAgtw0okB2YIaVvJDI4=
|
||||
github.com/vbauerster/mpb v3.4.0+incompatible h1:mfiiYw87ARaeRW6x5gWwYRUawxaW1tLAD8IceomUCNw=
|
|
@ -2,19 +2,24 @@ package progress
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/vbauerster/mpb/v5"
|
||||
"github.com/vbauerster/mpb/v5/decor"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/vbauerster/mpb/v5"
|
||||
"github.com/vbauerster/mpb/v5/decor"
|
||||
)
|
||||
|
||||
// global output refresh rate
|
||||
const RefreshHz = 8
|
||||
const (
|
||||
// global output refresh rate
|
||||
refreshHz = 8
|
||||
settleMilis = 250
|
||||
mili = 1000.
|
||||
)
|
||||
|
||||
// Encapsulates progress tracking.
|
||||
type IProgress interface {
|
||||
|
@ -45,12 +50,12 @@ type Progress struct {
|
|||
}
|
||||
|
||||
// Creates and returns a new progress tracking object.
|
||||
func NewProgress(noColor bool, active bool) IProgress {
|
||||
func NewProgress(noColor, active bool) IProgress {
|
||||
if !active {
|
||||
return &NoOpProgress{}
|
||||
}
|
||||
|
||||
refreshMillis := int64(1. / float64(RefreshHz) * 1000.)
|
||||
refreshMillis := int64(1. / float64(refreshHz) * mili)
|
||||
|
||||
renderChan := make(chan time.Time)
|
||||
p := &Progress{
|
||||
|
@ -70,6 +75,7 @@ func NewProgress(noColor bool, active bool) IProgress {
|
|||
stdRenderEvent: time.NewTicker(time.Millisecond * time.Duration(refreshMillis)),
|
||||
stdRenderWaitGroup: &sync.WaitGroup{},
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
|
@ -144,17 +150,19 @@ func (p *Progress) Wait() {
|
|||
func (p *Progress) renderStdData() {
|
||||
// trigger a render event
|
||||
p.renderChan <- time.Now()
|
||||
|
||||
gologger.Infof("Waiting for your terminal to settle..")
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
time.Sleep(time.Millisecond * settleMilis)
|
||||
|
||||
p.stdRenderWaitGroup.Add(1)
|
||||
|
||||
go func(waitGroup *sync.WaitGroup) {
|
||||
for {
|
||||
select {
|
||||
case <-p.stdStopRenderEvent:
|
||||
waitGroup.Done()
|
||||
return
|
||||
case _ = <-p.stdRenderEvent.C:
|
||||
case <-p.stdRenderEvent.C:
|
||||
p.stdCaptureMutex.Lock()
|
||||
{
|
||||
hasStdout := p.stdOut.Len() > 0
|
||||
|
@ -212,6 +220,7 @@ func pluralize(count int64, singular, plural string) string {
|
|||
if count > 1 {
|
||||
return plural
|
||||
}
|
||||
|
||||
return singular
|
||||
}
|
||||
|
|
@ -5,11 +5,17 @@ package progress
|
|||
*/
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
)
|
||||
|
||||
const (
|
||||
fourMegas = 4 * 1024
|
||||
two = 2
|
||||
)
|
||||
|
||||
type captureData struct {
|
||||
|
@ -20,7 +26,7 @@ type captureData struct {
|
|||
waitFinishRead *sync.WaitGroup
|
||||
}
|
||||
|
||||
func startCapture(writeMutex *sync.Mutex, stdout *strings.Builder, stderr *strings.Builder) *captureData {
|
||||
func startCapture(writeLocker sync.Locker, stdout, stderr *strings.Builder) *captureData {
|
||||
rStdout, wStdout, errStdout := os.Pipe()
|
||||
if errStdout != nil {
|
||||
panic(errStdout)
|
||||
|
@ -46,32 +52,39 @@ func startCapture(writeMutex *sync.Mutex, stdout *strings.Builder, stderr *strin
|
|||
|
||||
stdCopy := func(builder *strings.Builder, reader *os.File, waitGroup *sync.WaitGroup) {
|
||||
r := bufio.NewReader(reader)
|
||||
buf := make([]byte, 0, 4*1024)
|
||||
buf := make([]byte, 0, fourMegas)
|
||||
|
||||
for {
|
||||
n, err := r.Read(buf[:cap(buf)])
|
||||
buf = buf[:n]
|
||||
|
||||
if n == 0 {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
waitGroup.Done()
|
||||
break
|
||||
}
|
||||
|
||||
waitGroup.Done()
|
||||
gologger.Fatalf("stdcapture error: %s", err)
|
||||
}
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
waitGroup.Done()
|
||||
gologger.Fatalf("stdcapture error: %s", err)
|
||||
}
|
||||
writeMutex.Lock()
|
||||
|
||||
writeLocker.Lock()
|
||||
builder.Write(buf)
|
||||
writeMutex.Unlock()
|
||||
writeLocker.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
c.waitFinishRead.Add(2)
|
||||
c.waitFinishRead.Add(two)
|
||||
|
||||
go stdCopy(stdout, rStdout, c.waitFinishRead)
|
||||
go stdCopy(stderr, rStderr, c.waitFinishRead)
|
||||
|
|
@ -3,11 +3,11 @@ package runner
|
|||
import "github.com/projectdiscovery/gologger"
|
||||
|
||||
const banner = `
|
||||
__ _
|
||||
__ _
|
||||
____ __ _______/ /__ (_)
|
||||
/ __ \/ / / / ___/ / _ \/ /
|
||||
/ / / / /_/ / /__/ / __/ /
|
||||
/_/ /_/\__,_/\___/_/\___/_/ v2.1
|
||||
/ __ \/ / / / ___/ / _ \/ /
|
||||
/ / / / /_/ / /__/ / __/ /
|
||||
/_/ /_/\__,_/\___/_/\___/_/ v2.1
|
||||
`
|
||||
|
||||
// Version is the current version of nuclei
|
|
@ -29,8 +29,8 @@ type nucleiConfig struct {
|
|||
CurrentVersion string `json:"current-version,omitempty"`
|
||||
LastChecked time.Time `json:"last-checked,omitempty"`
|
||||
|
||||
// ignorePaths ignores all the paths listed unless specified manually
|
||||
ignorePaths []string `json:"ignore-paths,omitempty"`
|
||||
// IgnorePaths ignores all the paths listed unless specified manually
|
||||
IgnorePaths []string `json:"ignore-paths,omitempty"`
|
||||
}
|
||||
|
||||
// nucleiConfigFilename is the filename of nuclei configuration file.
|
||||
|
@ -47,21 +47,27 @@ func (r *Runner) readConfiguration() (*nucleiConfig, error) {
|
|||
|
||||
templatesConfigFile := path.Join(home, nucleiConfigFilename)
|
||||
file, err := os.Open(templatesConfigFile)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
config := &nucleiConfig{}
|
||||
if err = jsoniter.NewDecoder(file).Decode(config); err != nil {
|
||||
err = jsoniter.NewDecoder(file).Decode(config)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// readConfiguration reads the nuclei configuration file from disk.
|
||||
func (r *Runner) writeConfiguration(config *nucleiConfig) error {
|
||||
home, err := os.UserHomeDir()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -69,14 +75,18 @@ func (r *Runner) writeConfiguration(config *nucleiConfig) error {
|
|||
config.LastChecked = time.Now()
|
||||
templatesConfigFile := path.Join(home, nucleiConfigFilename)
|
||||
file, err := os.OpenFile(templatesConfigFile, os.O_WRONLY|os.O_CREATE, 0777)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
if err = jsoniter.NewEncoder(file).Encode(config); err != nil {
|
||||
err = jsoniter.NewEncoder(file).Encode(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -96,7 +106,8 @@ func (r *Runner) readNucleiIgnoreFile() {
|
|||
if text == "" {
|
||||
continue
|
||||
}
|
||||
r.templatesConfig.ignorePaths = append(r.templatesConfig.ignorePaths, text)
|
||||
|
||||
r.templatesConfig.IgnorePaths = append(r.templatesConfig.IgnorePaths, text)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,12 +116,14 @@ func (r *Runner) checkIfInNucleiIgnore(item string) bool {
|
|||
if r.templatesConfig == nil {
|
||||
return false
|
||||
}
|
||||
for _, paths := range r.templatesConfig.ignorePaths {
|
||||
|
||||
for _, paths := range r.templatesConfig.IgnorePaths {
|
||||
// If we have a path to ignore, check if it's in the item.
|
||||
if paths[len(paths)-1] == '/' {
|
||||
if strings.Contains(item, paths) {
|
||||
return true
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
// Check for file based extension in ignores
|
||||
|
@ -118,6 +131,7 @@ func (r *Runner) checkIfInNucleiIgnore(item string) bool {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -134,14 +148,18 @@ func (r *Runner) updateTemplates() error {
|
|||
}
|
||||
|
||||
templatesConfigFile := path.Join(home, nucleiConfigFilename)
|
||||
if _, err := os.Stat(templatesConfigFile); !os.IsNotExist(err) {
|
||||
config, err := r.readConfiguration()
|
||||
if err != nil {
|
||||
return err
|
||||
if _, statErr := os.Stat(templatesConfigFile); !os.IsNotExist(statErr) {
|
||||
config, readErr := r.readConfiguration()
|
||||
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
|
||||
r.templatesConfig = config
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if r.templatesConfig == nil || (r.options.TemplatesDirectory != "" && r.templatesConfig.TemplatesDirectory != r.options.TemplatesDirectory) {
|
||||
if !r.options.UpdateTemplates {
|
||||
gologger.Labelf("nuclei-templates are not installed, use update-templates flag.\n")
|
||||
|
@ -152,44 +170,54 @@ func (r *Runner) updateTemplates() error {
|
|||
if r.options.TemplatesDirectory != "" {
|
||||
home = r.options.TemplatesDirectory
|
||||
}
|
||||
|
||||
r.templatesConfig = &nucleiConfig{TemplatesDirectory: path.Join(home, "nuclei-templates")}
|
||||
|
||||
// Download the repository and also write the revision to a HEAD file.
|
||||
version, asset, err := r.getLatestReleaseFromGithub()
|
||||
if err != nil {
|
||||
return err
|
||||
version, asset, getErr := r.getLatestReleaseFromGithub()
|
||||
if getErr != nil {
|
||||
return getErr
|
||||
}
|
||||
|
||||
gologger.Verbosef("Downloading nuclei-templates (v%s) to %s\n", "update-templates", version.String(), r.templatesConfig.TemplatesDirectory)
|
||||
|
||||
if err = r.downloadReleaseAndUnzip(asset.GetZipballURL()); err != nil {
|
||||
err = r.downloadReleaseAndUnzip(ctx, asset.GetZipballURL())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.templatesConfig.CurrentVersion = version.String()
|
||||
if err = r.writeConfiguration(r.templatesConfig); err != nil {
|
||||
|
||||
err = r.writeConfiguration(r.templatesConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gologger.Infof("Successfully downloaded nuclei-templates (v%s). Enjoy!\n", version.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if last checked is more than 24 hours.
|
||||
// If not, return since we don't want to do anything now.
|
||||
if time.Now().Sub(r.templatesConfig.LastChecked) < 24*time.Hour && !r.options.UpdateTemplates {
|
||||
if time.Since(r.templatesConfig.LastChecked) < 24*time.Hour && !r.options.UpdateTemplates {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the configuration currently on disk.
|
||||
verText := r.templatesConfig.CurrentVersion
|
||||
indices := reVersion.FindStringIndex(verText)
|
||||
|
||||
if indices == nil {
|
||||
return fmt.Errorf("invalid release found with tag %s", err)
|
||||
}
|
||||
|
||||
if indices[0] > 0 {
|
||||
verText = verText[indices[0]:]
|
||||
}
|
||||
|
||||
oldVersion, err := semver.Make(verText)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -203,27 +231,35 @@ func (r *Runner) updateTemplates() error {
|
|||
gologger.Labelf("Latest version of nuclei-templates installed: v%s\n", oldVersion.String())
|
||||
return r.writeConfiguration(r.templatesConfig)
|
||||
}
|
||||
|
||||
if version.GT(oldVersion) {
|
||||
if !r.options.UpdateTemplates {
|
||||
gologger.Labelf("You're using outdated nuclei-templates. Latest v%s\n", version.String())
|
||||
return r.writeConfiguration(r.templatesConfig)
|
||||
}
|
||||
|
||||
if r.options.TemplatesDirectory != "" {
|
||||
home = r.options.TemplatesDirectory
|
||||
r.templatesConfig.TemplatesDirectory = path.Join(home, "nuclei-templates")
|
||||
}
|
||||
|
||||
r.templatesConfig.CurrentVersion = version.String()
|
||||
|
||||
gologger.Verbosef("Downloading nuclei-templates (v%s) to %s\n", "update-templates", version.String(), r.templatesConfig.TemplatesDirectory)
|
||||
|
||||
if err = r.downloadReleaseAndUnzip(asset.GetZipballURL()); err != nil {
|
||||
err = r.downloadReleaseAndUnzip(ctx, asset.GetZipballURL())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = r.writeConfiguration(r.templatesConfig); err != nil {
|
||||
|
||||
err = r.writeConfiguration(r.templatesConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gologger.Infof("Successfully updated nuclei-templates (v%s). Enjoy!\n", version.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -243,16 +279,21 @@ func (r *Runner) getLatestReleaseFromGithub() (semver.Version, *github.Repositor
|
|||
|
||||
// Find the most recent version based on semantic versioning.
|
||||
var latestRelease semver.Version
|
||||
|
||||
var latestPublish *github.RepositoryRelease
|
||||
|
||||
for _, release := range rels {
|
||||
verText := release.GetTagName()
|
||||
indices := reVersion.FindStringIndex(verText)
|
||||
|
||||
if indices == nil {
|
||||
return semver.Version{}, nil, fmt.Errorf("invalid release found with tag %s", err)
|
||||
}
|
||||
|
||||
if indices[0] > 0 {
|
||||
verText = verText[indices[0]:]
|
||||
}
|
||||
|
||||
ver, err := semver.Make(verText)
|
||||
if err != nil {
|
||||
return semver.Version{}, nil, err
|
||||
|
@ -263,75 +304,93 @@ func (r *Runner) getLatestReleaseFromGithub() (semver.Version, *github.Repositor
|
|||
latestPublish = release
|
||||
}
|
||||
}
|
||||
|
||||
if latestPublish == nil {
|
||||
return semver.Version{}, nil, errors.New("no version found for the templates")
|
||||
}
|
||||
|
||||
return latestRelease, latestPublish, nil
|
||||
}
|
||||
|
||||
// downloadReleaseAndUnzip downloads and unzips the release in a directory
|
||||
func (r *Runner) downloadReleaseAndUnzip(downloadURL string) error {
|
||||
req, err := http.NewRequest("GET", downloadURL, nil)
|
||||
func (r *Runner) downloadReleaseAndUnzip(ctx context.Context, downloadURL string) error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create HTTP request to %s: %s", downloadURL, err)
|
||||
return fmt.Errorf("failed to create HTTP request to %s: %s", downloadURL, err)
|
||||
}
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to download a release file from %s: %s", downloadURL, err)
|
||||
return fmt.Errorf("failed to download a release file from %s: %s", downloadURL, err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("Failed to download a release file from %s: Not successful status %d", downloadURL, res.StatusCode)
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("failed to download a release file from %s: Not successful status %d", downloadURL, res.StatusCode)
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create buffer for zip file: %s", err)
|
||||
return fmt.Errorf("failed to create buffer for zip file: %s", err)
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(buf)
|
||||
z, err := zip.NewReader(reader, reader.Size())
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to uncompress zip file: %s", err)
|
||||
return fmt.Errorf("failed to uncompress zip file: %s", err)
|
||||
}
|
||||
|
||||
// Create the template folder if it doesn't exists
|
||||
os.MkdirAll(r.templatesConfig.TemplatesDirectory, os.ModePerm)
|
||||
err = os.MkdirAll(r.templatesConfig.TemplatesDirectory, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create template base folder: %s", err)
|
||||
}
|
||||
|
||||
for _, file := range z.File {
|
||||
directory, name := filepath.Split(file.Name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
paths := strings.Split(directory, "/")
|
||||
finalPath := strings.Join(paths[1:], "/")
|
||||
|
||||
templateDirectory := path.Join(r.templatesConfig.TemplatesDirectory, finalPath)
|
||||
os.MkdirAll(templateDirectory, os.ModePerm)
|
||||
|
||||
f, err := os.OpenFile(path.Join(templateDirectory, name), os.O_CREATE|os.O_WRONLY, 0777)
|
||||
err = os.MkdirAll(templateDirectory, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not create uncompressed file: %s", err)
|
||||
return fmt.Errorf("failed to create template folder %s : %s", templateDirectory, err)
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(path.Join(templateDirectory, name), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("could not create uncompressed file: %s", err)
|
||||
}
|
||||
|
||||
reader, err := file.Open()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("Could not open archive to extract file: %s", err)
|
||||
return fmt.Errorf("could not open archive to extract file: %s", err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(f, reader)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("could not write template file: %s", err)
|
||||
}
|
||||
io.Copy(f, reader)
|
||||
f.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isRelative checks if a given path is a relative path
|
||||
func (r *Runner) isRelative(path string) bool {
|
||||
if strings.HasPrefix(path, "/") || strings.Contains(path, ":\\") {
|
||||
func (r *Runner) isRelative(thePath string) bool {
|
||||
if strings.HasPrefix(thePath, "/") || strings.Contains(thePath, ":\\") {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -344,18 +403,23 @@ func (r *Runner) resolvePath(templateName string) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
templatePath := path.Join(curDirectory, templateName)
|
||||
if _, err := os.Stat(templatePath); !os.IsNotExist(err) {
|
||||
gologger.Debugf("Found template in current directory: %s\n", templatePath)
|
||||
|
||||
return templatePath, nil
|
||||
}
|
||||
|
||||
if r.templatesConfig != nil {
|
||||
templatePath := path.Join(r.templatesConfig.TemplatesDirectory, templateName)
|
||||
if _, err := os.Stat(templatePath); !os.IsNotExist(err) {
|
||||
gologger.Debugf("Found template in nuclei-templates directory: %s\n", templatePath)
|
||||
|
||||
return templatePath, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no such path found: %s", templateName)
|
||||
}
|
||||
|
|
@ -11,10 +11,20 @@ import (
|
|||
// Options contains the configuration options for tuning
|
||||
// the template requesting process.
|
||||
type Options struct {
|
||||
Debug bool // Debug mode allows debugging request/responses for the engine
|
||||
Debug bool // Debug mode allows debugging request/responses for the engine
|
||||
Silent bool // Silent suppresses any extra text and only writes found URLs on screen.
|
||||
Version bool // Version specifies if we should just show version and exit
|
||||
Verbose bool // Verbose flag indicates whether to show verbose output or not
|
||||
NoColor bool // No-Color disables the colored output.
|
||||
UpdateTemplates bool // UpdateTemplates updates the templates installed at startup
|
||||
JSON bool // JSON writes json output to files
|
||||
JSONRequests bool // write requests/responses for matches in JSON output
|
||||
EnableProgressBar bool // Enable progrss bar
|
||||
|
||||
Stdin bool // Stdin specifies whether stdin input was given to the process
|
||||
Templates multiStringFlag // Signature specifies the template/templates to use
|
||||
ExcludedTemplates multiStringFlag // Signature specifies the template/templates to exclude
|
||||
Severity string // Filter templates based on their severity and only run the matching ones.
|
||||
Severity string // Filter templates based on their severity and only run the matching ones.
|
||||
Target string // Target is a single URL/Domain to scan usng a template
|
||||
Targets string // Targets specifies the targets to scan using templates.
|
||||
Threads int // Thread controls the number of concurrent requests to make.
|
||||
|
@ -23,18 +33,8 @@ type Options struct {
|
|||
Output string // Output is the file to write found subdomains to.
|
||||
ProxyURL string // ProxyURL is the URL for the proxy server
|
||||
ProxySocksURL string // ProxySocksURL is the URL for the proxy socks server
|
||||
Silent bool // Silent suppresses any extra text and only writes found URLs on screen.
|
||||
Version bool // Version specifies if we should just show version and exit
|
||||
Verbose bool // Verbose flag indicates whether to show verbose output or not
|
||||
NoColor bool // No-Color disables the colored output.
|
||||
CustomHeaders requests.CustomHeaders // Custom global headers
|
||||
UpdateTemplates bool // UpdateTemplates updates the templates installed at startup
|
||||
TemplatesDirectory string // TemplatesDirectory is the directory to use for storing templates
|
||||
JSON bool // JSON writes json output to files
|
||||
JSONRequests bool // write requests/responses for matches in JSON output
|
||||
EnableProgressBar bool // Enable progrss bar
|
||||
|
||||
Stdin bool // Stdin specifies whether stdin input was given to the process
|
||||
}
|
||||
|
||||
type multiStringFlag []string
|
||||
|
@ -97,6 +97,7 @@ func ParseOptions() *Options {
|
|||
if err != nil {
|
||||
gologger.Fatalf("Program exiting: %s\n", err)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
|
@ -105,8 +106,10 @@ func hasStdin() bool {
|
|||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeNamedPipe == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -51,6 +51,12 @@ type Runner struct {
|
|||
decolorizer *regexp.Regexp
|
||||
}
|
||||
|
||||
// WorkflowTemplates contains the initialized workflow templates per template group
|
||||
type WorkflowTemplates struct {
|
||||
Name string
|
||||
Templates []*workflows.Template
|
||||
}
|
||||
|
||||
// New creates a new client for running enumeration process.
|
||||
func New(options *Options) (*Runner, error) {
|
||||
runner := &Runner{
|
||||
|
@ -61,6 +67,7 @@ func New(options *Options) (*Runner, error) {
|
|||
if err := runner.updateTemplates(); err != nil {
|
||||
gologger.Warningf("Could not update templates: %s\n", err)
|
||||
}
|
||||
|
||||
if (len(options.Templates) == 0 || (options.Targets == "" && !options.Stdin && options.Target == "")) && options.UpdateTemplates {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
@ -72,24 +79,24 @@ func New(options *Options) (*Runner, error) {
|
|||
// output coloring
|
||||
useColor := !options.NoColor
|
||||
runner.colorizer = aurora.NewAurora(useColor)
|
||||
|
||||
if useColor {
|
||||
// compile a decolorization regex to cleanup file output messages
|
||||
compiled, err := regexp.Compile("\\x1B\\[[0-9;]*[a-zA-Z]")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runner.decolorizer = compiled
|
||||
runner.decolorizer = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`)
|
||||
}
|
||||
|
||||
// If we have stdin, write it to a new file
|
||||
if options.Stdin {
|
||||
tempInput, err := ioutil.TempFile("", "stdin-input-*")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(tempInput, os.Stdin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runner.tempFile = tempInput.Name()
|
||||
tempInput.Close()
|
||||
}
|
||||
|
@ -99,6 +106,7 @@ func New(options *Options) (*Runner, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Fprintf(tempInput, "%s\n", options.Target)
|
||||
runner.tempFile = tempInput.Name()
|
||||
tempInput.Close()
|
||||
|
@ -106,32 +114,38 @@ func New(options *Options) (*Runner, error) {
|
|||
|
||||
// Setup input, handle a list of hosts as argument
|
||||
var err error
|
||||
|
||||
var input *os.File
|
||||
|
||||
if options.Targets != "" {
|
||||
input, err = os.Open(options.Targets)
|
||||
} else if options.Stdin || options.Target != "" {
|
||||
input, err = os.Open(runner.tempFile)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
gologger.Fatalf("Could not open targets file '%s': %s\n", options.Targets, err)
|
||||
}
|
||||
|
||||
// Sanitize input and pre-compute total number of targets
|
||||
var usedInput = make(map[string]bool)
|
||||
|
||||
dupeCount := 0
|
||||
sb := strings.Builder{}
|
||||
scanner := bufio.NewScanner(input)
|
||||
runner.inputCount = 0
|
||||
|
||||
for scanner.Scan() {
|
||||
url := scanner.Text()
|
||||
// skip empty lines
|
||||
if len(url) == 0 {
|
||||
if url == "" {
|
||||
continue
|
||||
}
|
||||
// deduplication
|
||||
if _, ok := usedInput[url]; !ok {
|
||||
usedInput[url] = true
|
||||
runner.inputCount++
|
||||
|
||||
sb.WriteString(url)
|
||||
sb.WriteString("\n")
|
||||
} else {
|
||||
|
@ -139,7 +153,9 @@ func New(options *Options) (*Runner, error) {
|
|||
}
|
||||
}
|
||||
input.Close()
|
||||
|
||||
runner.input = sb.String()
|
||||
|
||||
if dupeCount > 0 {
|
||||
gologger.Labelf("Supplied input was automatically deduplicated (%d removed).", dupeCount)
|
||||
}
|
||||
|
@ -150,6 +166,7 @@ func New(options *Options) (*Runner, error) {
|
|||
if err != nil {
|
||||
gologger.Fatalf("Could not create output file '%s': %s\n", options.Output, err)
|
||||
}
|
||||
|
||||
runner.output = output
|
||||
}
|
||||
|
||||
|
@ -172,17 +189,21 @@ func isFilePath(path string) (bool, error) {
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return info.Mode().IsRegular(), nil
|
||||
}
|
||||
|
||||
func (r *Runner) resolvePathIfRelative(path string) (string, error) {
|
||||
if r.isRelative(path) {
|
||||
newPath, err := r.resolvePath(path)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return newPath, nil
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
|
@ -191,6 +212,7 @@ func isNewPath(path string, pathMap map[string]bool) bool {
|
|||
gologger.Warningf("Skipping already specified path '%s'", path)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -200,6 +222,7 @@ func hasMatchingSeverity(templateSeverity string, allowedSeverities []string) bo
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -210,6 +233,7 @@ func (r *Runner) logTemplateLoaded(id, name, author, severity string) {
|
|||
if severity != "" {
|
||||
message += " [" + r.colorizer.Yellow(severity).String() + "]"
|
||||
}
|
||||
|
||||
gologger.Infof("%s\n", message)
|
||||
}
|
||||
|
||||
|
@ -222,30 +246,30 @@ func (r *Runner) getParsedTemplatesFor(templatePaths []string, severities string
|
|||
filterBySeverity := len(severities) > 0
|
||||
|
||||
gologger.Infof("Loading templates...")
|
||||
|
||||
for _, match := range templatePaths {
|
||||
t, err := r.parse(match)
|
||||
switch t.(type) {
|
||||
switch tp := t.(type) {
|
||||
case *templates.Template:
|
||||
template := t.(*templates.Template)
|
||||
id := template.ID
|
||||
id := tp.ID
|
||||
|
||||
// only include if severity matches or no severity filtering
|
||||
sev := strings.ToLower(template.Info.Severity)
|
||||
sev := strings.ToLower(tp.Info.Severity)
|
||||
if !filterBySeverity || hasMatchingSeverity(sev, allSeverities) {
|
||||
parsedTemplates = append(parsedTemplates, template)
|
||||
r.logTemplateLoaded(template.ID, template.Info.Name, template.Info.Author, template.Info.Severity)
|
||||
parsedTemplates = append(parsedTemplates, tp)
|
||||
r.logTemplateLoaded(tp.ID, tp.Info.Name, tp.Info.Author, tp.Info.Severity)
|
||||
} else {
|
||||
gologger.Warningf("Excluding template %s due to severity filter (%s not in [%s])", id, sev, severities)
|
||||
}
|
||||
case *workflows.Workflow:
|
||||
workflow := t.(*workflows.Workflow)
|
||||
parsedTemplates = append(parsedTemplates, workflow)
|
||||
r.logTemplateLoaded(workflow.ID, workflow.Info.Name, workflow.Info.Author, workflow.Info.Severity)
|
||||
parsedTemplates = append(parsedTemplates, tp)
|
||||
r.logTemplateLoaded(tp.ID, tp.Info.Name, tp.Info.Author, tp.Info.Severity)
|
||||
workflowCount++
|
||||
default:
|
||||
gologger.Errorf("Could not parse file '%s': %s\n", match, err)
|
||||
}
|
||||
}
|
||||
|
||||
return parsedTemplates, workflowCount
|
||||
}
|
||||
|
||||
|
@ -258,6 +282,7 @@ func (r *Runner) getTemplatesFor(definitions []string) []string {
|
|||
// parses user input, handle file/directory cases and produce a list of unique templates
|
||||
for _, t := range definitions {
|
||||
var absPath string
|
||||
|
||||
var err error
|
||||
|
||||
if strings.Contains(t, "*") {
|
||||
|
@ -277,10 +302,12 @@ func (r *Runner) getTemplatesFor(definitions []string) []string {
|
|||
|
||||
// Template input includes a wildcard
|
||||
if strings.Contains(absPath, "*") {
|
||||
matches := []string{}
|
||||
var matches []string
|
||||
matches, err = filepath.Glob(absPath)
|
||||
|
||||
if err != nil {
|
||||
gologger.Labelf("Wildcard found, but unable to glob '%s': %s\n", absPath, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -295,6 +322,7 @@ func (r *Runner) getTemplatesFor(definitions []string) []string {
|
|||
for _, match := range matches {
|
||||
if !r.checkIfInNucleiIgnore(match) {
|
||||
processed[match] = true
|
||||
|
||||
allTemplates = append(allTemplates, match)
|
||||
}
|
||||
}
|
||||
|
@ -364,6 +392,7 @@ func (r *Runner) RunEnumeration() {
|
|||
excludedTemplates := r.getTemplatesFor(r.options.ExcludedTemplates)
|
||||
// defaults to all templates
|
||||
allTemplates := includedTemplates
|
||||
|
||||
if len(excludedTemplates) > 0 {
|
||||
excludedMap := make(map[string]struct{}, len(excludedTemplates))
|
||||
for _, excl := range excludedTemplates {
|
||||
|
@ -371,6 +400,7 @@ func (r *Runner) RunEnumeration() {
|
|||
}
|
||||
// rebuild list with only non-excluded templates
|
||||
allTemplates = []string{}
|
||||
|
||||
for _, incl := range includedTemplates {
|
||||
if _, found := excludedMap[incl]; !found {
|
||||
allTemplates = append(allTemplates, incl)
|
||||
|
@ -399,14 +429,13 @@ func (r *Runner) RunEnumeration() {
|
|||
var totalRequests int64 = 0
|
||||
|
||||
for _, t := range availableTemplates {
|
||||
switch t.(type) {
|
||||
switch av := t.(type) {
|
||||
case *templates.Template:
|
||||
template := t.(*templates.Template)
|
||||
totalRequests += (template.GetHTTPRequestCount() + template.GetDNSRequestCount()) * r.inputCount
|
||||
totalRequests += (av.GetHTTPRequestCount() + av.GetDNSRequestCount()) * r.inputCount
|
||||
case *workflows.Workflow:
|
||||
// workflows will dynamically adjust the totals while running, as
|
||||
// it can't be know in advance which requests will be called
|
||||
}
|
||||
} // nolint:wsl // comment
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -417,7 +446,7 @@ func (r *Runner) RunEnumeration() {
|
|||
if r.inputCount == 0 {
|
||||
gologger.Errorf("Could not find any valid input URLs.")
|
||||
} else if totalRequests > 0 || hasWorkflows {
|
||||
|
||||
ctx := context.Background()
|
||||
// tracks global progress and captures stdout/stderr until p.Wait finishes
|
||||
p := r.progress
|
||||
p.InitProgressbar(r.inputCount, templateCount, totalRequests)
|
||||
|
@ -426,14 +455,13 @@ func (r *Runner) RunEnumeration() {
|
|||
wgtemplates.Add(1)
|
||||
go func(template interface{}) {
|
||||
defer wgtemplates.Done()
|
||||
switch template.(type) {
|
||||
switch tt := template.(type) {
|
||||
case *templates.Template:
|
||||
t := template.(*templates.Template)
|
||||
for _, request := range t.RequestsDNS {
|
||||
results.Or(r.processTemplateWithList(p, t, request))
|
||||
for _, request := range tt.RequestsDNS {
|
||||
results.Or(r.processTemplateWithList(ctx, p, tt, request))
|
||||
}
|
||||
for _, request := range t.BulkRequestsHTTP {
|
||||
results.Or(r.processTemplateWithList(p, t, request))
|
||||
for _, request := range tt.BulkRequestsHTTP {
|
||||
results.Or(r.processTemplateWithList(ctx, p, tt, request))
|
||||
}
|
||||
case *workflows.Workflow:
|
||||
workflow := template.(*workflows.Workflow)
|
||||
|
@ -452,13 +480,13 @@ func (r *Runner) RunEnumeration() {
|
|||
r.output.Close()
|
||||
os.Remove(outputFile)
|
||||
}
|
||||
|
||||
gologger.Infof("No results found. Happy hacking!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// processTemplateWithList processes a template and runs the enumeration on all the targets
|
||||
func (r *Runner) processTemplateWithList(p progress.IProgress, template *templates.Template, request interface{}) bool {
|
||||
func (r *Runner) processTemplateWithList(ctx context.Context, p progress.IProgress, template *templates.Template, request interface{}) bool {
|
||||
var writer *bufio.Writer
|
||||
if r.output != nil {
|
||||
writer = bufio.NewWriter(r.output)
|
||||
|
@ -466,7 +494,9 @@ func (r *Runner) processTemplateWithList(p progress.IProgress, template *templat
|
|||
}
|
||||
|
||||
var httpExecuter *executer.HTTPExecuter
|
||||
|
||||
var dnsExecuter *executer.DNSExecuter
|
||||
|
||||
var err error
|
||||
|
||||
// Create an executer based on the request type.
|
||||
|
@ -487,7 +517,7 @@ func (r *Runner) processTemplateWithList(p progress.IProgress, template *templat
|
|||
httpExecuter, err = executer.NewHTTPExecuter(&executer.HTTPOptions{
|
||||
Debug: r.options.Debug,
|
||||
Template: template,
|
||||
BulkHttpRequest: value,
|
||||
BulkHTTPRequest: value,
|
||||
Writer: writer,
|
||||
Timeout: r.options.Timeout,
|
||||
Retries: r.options.Retries,
|
||||
|
@ -502,9 +532,11 @@ func (r *Runner) processTemplateWithList(p progress.IProgress, template *templat
|
|||
Decolorizer: r.decolorizer,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
p.Drop(request.(*requests.BulkHTTPRequest).GetRequestCount())
|
||||
gologger.Warningf("Could not create http client: %s\n", err)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -517,23 +549,28 @@ func (r *Runner) processTemplateWithList(p progress.IProgress, template *templat
|
|||
text := scanner.Text()
|
||||
|
||||
r.limiter <- struct{}{}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
go func(URL string) {
|
||||
defer wg.Done()
|
||||
|
||||
var result executer.Result
|
||||
|
||||
if httpExecuter != nil {
|
||||
result = httpExecuter.ExecuteHTTP(p, URL)
|
||||
result = httpExecuter.ExecuteHTTP(ctx, p, URL)
|
||||
globalresult.Or(result.GotResults)
|
||||
}
|
||||
|
||||
if dnsExecuter != nil {
|
||||
result = dnsExecuter.ExecuteDNS(p, URL)
|
||||
globalresult.Or(result.GotResults)
|
||||
}
|
||||
|
||||
if result.Error != nil {
|
||||
gologger.Warningf("Could not execute step: %s\n", result.Error)
|
||||
}
|
||||
|
||||
<-r.limiter
|
||||
}(text)
|
||||
}
|
||||
|
@ -546,38 +583,67 @@ func (r *Runner) processTemplateWithList(p progress.IProgress, template *templat
|
|||
|
||||
// ProcessWorkflowWithList coming from stdin or list of targets
|
||||
func (r *Runner) ProcessWorkflowWithList(p progress.IProgress, workflow *workflows.Workflow) {
|
||||
workflowTemplatesList, err := r.PreloadTemplates(p, workflow)
|
||||
if err != nil {
|
||||
gologger.Warningf("Could not preload templates for workflow %s: %s\n", workflow.ID, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logicBytes := []byte(workflow.Logic)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(r.input))
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
targetURL := scanner.Text()
|
||||
r.limiter <- struct{}{}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
go func(URL string) {
|
||||
go func(targetURL string) {
|
||||
defer wg.Done()
|
||||
|
||||
if err := r.ProcessWorkflow(p, workflow, text); err != nil {
|
||||
gologger.Warningf("Could not run workflow for %s: %s\n", text, err)
|
||||
script := tengo.NewScript(logicBytes)
|
||||
script.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
||||
|
||||
for _, workflowTemplate := range *workflowTemplatesList {
|
||||
err := script.Add(workflowTemplate.Name, &workflows.NucleiVar{Templates: workflowTemplate.Templates, URL: targetURL})
|
||||
if err != nil {
|
||||
gologger.Errorf("Could not initialize script for workflow '%s': %s\n", workflow.ID, err)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
_, err := script.RunContext(context.Background())
|
||||
if err != nil {
|
||||
gologger.Errorf("Could not execute workflow '%s': %s\n", workflow.ID, err)
|
||||
}
|
||||
|
||||
<-r.limiter
|
||||
}(text)
|
||||
}(targetURL)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// ProcessWorkflow towards an URL
|
||||
func (r *Runner) ProcessWorkflow(p progress.IProgress, workflow *workflows.Workflow, URL string) error {
|
||||
script := tengo.NewScript([]byte(workflow.Logic))
|
||||
script.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
|
||||
// PreloadTemplates preload the workflow templates once
|
||||
func (r *Runner) PreloadTemplates(p progress.IProgress, workflow *workflows.Workflow) (*[]WorkflowTemplates, error) {
|
||||
var jar *cookiejar.Jar
|
||||
|
||||
if workflow.CookieReuse {
|
||||
var err error
|
||||
jar, err = cookiejar.New(nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Single yaml provided
|
||||
var wflTemplatesList []WorkflowTemplates
|
||||
|
||||
for name, value := range workflow.Variables {
|
||||
var writer *bufio.Writer
|
||||
if r.output != nil {
|
||||
|
@ -592,19 +658,21 @@ func (r *Runner) ProcessWorkflow(p progress.IProgress, workflow *workflows.Workf
|
|||
if err != nil {
|
||||
newPath, err = r.resolvePathWithBaseFolder(filepath.Dir(workflow.GetPath()), value)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
value = newPath
|
||||
}
|
||||
|
||||
// Single yaml provided
|
||||
var templatesList []*workflows.Template
|
||||
var wtlst []*workflows.Template
|
||||
|
||||
if strings.HasSuffix(value, ".yaml") {
|
||||
t, err := templates.Parse(value)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template := &workflows.Template{Progress: p}
|
||||
if len(t.BulkRequestsHTTP) > 0 {
|
||||
template.HTTPOptions = &executer.HTTPOptions{
|
||||
|
@ -631,8 +699,9 @@ func (r *Runner) ProcessWorkflow(p progress.IProgress, workflow *workflows.Workf
|
|||
Decolorizer: r.decolorizer,
|
||||
}
|
||||
}
|
||||
|
||||
if template.DNSOptions != nil || template.HTTPOptions != nil {
|
||||
templatesList = append(templatesList, template)
|
||||
wtlst = append(wtlst, template)
|
||||
}
|
||||
} else {
|
||||
matches := []string{}
|
||||
|
@ -642,6 +711,7 @@ func (r *Runner) ProcessWorkflow(p progress.IProgress, workflow *workflows.Workf
|
|||
if !d.IsDir() && strings.HasSuffix(path, ".yaml") {
|
||||
matches = append(matches, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
ErrorCallback: func(path string, err error) godirwalk.ErrorAction {
|
||||
|
@ -649,18 +719,20 @@ func (r *Runner) ProcessWorkflow(p progress.IProgress, workflow *workflows.Workf
|
|||
},
|
||||
Unsorted: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 0 matches means no templates were found in directory
|
||||
if len(matches) == 0 {
|
||||
return errors.New("no match found in the directory")
|
||||
return nil, fmt.Errorf("no match found in the directory %s", value)
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
t, err := templates.Parse(match)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
template := &workflows.Template{Progress: p}
|
||||
if len(t.BulkRequestsHTTP) > 0 {
|
||||
|
@ -683,20 +755,15 @@ func (r *Runner) ProcessWorkflow(p progress.IProgress, workflow *workflows.Workf
|
|||
}
|
||||
}
|
||||
if template.DNSOptions != nil || template.HTTPOptions != nil {
|
||||
templatesList = append(templatesList, template)
|
||||
wtlst = append(wtlst, template)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
script.Add(name, &workflows.NucleiVar{Templates: templatesList, URL: URL})
|
||||
wflTemplatesList = append(wflTemplatesList, WorkflowTemplates{Name: name, Templates: wtlst})
|
||||
}
|
||||
|
||||
_, err := script.RunContext(context.Background())
|
||||
if err != nil {
|
||||
gologger.Errorf("Could not execute workflow '%s': %s\n", workflow.ID, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return &wflTemplatesList, nil
|
||||
}
|
||||
|
||||
func (r *Runner) parse(file string) (interface{}, error) {
|
||||
|
@ -715,8 +782,10 @@ func (r *Runner) parse(file string) (interface{}, error) {
|
|||
if errTemplate != nil {
|
||||
return nil, errTemplate
|
||||
}
|
||||
|
||||
if errWorkflow != nil {
|
||||
return nil, errWorkflow
|
||||
}
|
||||
return nil, errors.New("unknown error occured")
|
||||
|
||||
return nil, errors.New("unknown error occurred")
|
||||
}
|
|
@ -27,6 +27,7 @@ func (options *Options) validateOptions() error {
|
|||
if options.ProxyURL != "" && !isValidProxyURL(options.ProxyURL) {
|
||||
return errors.New("invalid http proxy format (It should be http://username:password@host:port)")
|
||||
}
|
||||
|
||||
if options.ProxySocksURL != "" && !isValidProxyURL(options.ProxySocksURL) {
|
||||
return errors.New("invalid socks proxy format (It should be socks5://username:password@host:port)")
|
||||
}
|
||||
|
@ -34,12 +35,10 @@ func (options *Options) validateOptions() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func isValidProxyURL(URL string) bool {
|
||||
if _, err := url.Parse(URL); err != nil {
|
||||
return false
|
||||
}
|
||||
func isValidProxyURL(proxyURL string) bool {
|
||||
_, err := url.Parse(proxyURL)
|
||||
|
||||
return true
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// configureOutput configures the output on the screen
|
||||
|
@ -48,9 +47,11 @@ func (options *Options) configureOutput() {
|
|||
if options.Verbose {
|
||||
gologger.MaxLevel = gologger.Verbose
|
||||
}
|
||||
|
||||
if options.NoColor {
|
||||
gologger.UseColors = false
|
||||
}
|
||||
|
||||
if options.Silent {
|
||||
gologger.MaxLevel = gologger.Silent
|
||||
}
|
|
@ -32,7 +32,7 @@ func (b *AtomBool) Set(value bool) {
|
|||
|
||||
func (b *AtomBool) Get() bool {
|
||||
b.RLock()
|
||||
defer b.RUnlock()
|
||||
defer b.RUnlock() //nolint
|
||||
|
||||
return b.flag
|
||||
}
|
|
@ -1,10 +1,6 @@
|
|||
package executer
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
)
|
||||
import "net/url"
|
||||
|
||||
// isURL tests a string to determine if it is a well-structured url or not.
|
||||
func isURL(toTest string) bool {
|
||||
|
@ -17,22 +13,18 @@ func isURL(toTest string) bool {
|
|||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// extractDomain extracts the domain name of a URL
|
||||
func extractDomain(URL string) string {
|
||||
u, err := url.Parse(URL)
|
||||
func extractDomain(theURL string) string {
|
||||
u, err := url.Parse(theURL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
hostname := u.Hostname()
|
||||
|
||||
return hostname
|
||||
}
|
||||
|
||||
// isDNS tests a string to determine if it is a well-structured dns or not
|
||||
// even if it's oneliner, we leave it wrapped in a function call for
|
||||
// future improvements
|
||||
func isDNS(toTest string) bool {
|
||||
return govalidator.IsDNSName(toTest)
|
||||
}
|
|
@ -20,19 +20,19 @@ import (
|
|||
// DNSExecuter is a client for performing a DNS request
|
||||
// for a template.
|
||||
type DNSExecuter struct {
|
||||
debug bool
|
||||
jsonOutput bool
|
||||
jsonRequest bool
|
||||
Results bool
|
||||
dnsClient *retryabledns.Client
|
||||
template *templates.Template
|
||||
dnsRequest *requests.DNSRequest
|
||||
writer *bufio.Writer
|
||||
outputMutex *sync.Mutex
|
||||
|
||||
coloredOutput bool
|
||||
colorizer aurora.Aurora
|
||||
decolorizer *regexp.Regexp
|
||||
debug bool
|
||||
jsonOutput bool
|
||||
jsonRequest bool
|
||||
Results bool
|
||||
dnsClient *retryabledns.Client
|
||||
template *templates.Template
|
||||
dnsRequest *requests.DNSRequest
|
||||
writer *bufio.Writer
|
||||
outputMutex *sync.Mutex
|
||||
|
||||
colorizer aurora.Aurora
|
||||
decolorizer *regexp.Regexp
|
||||
}
|
||||
|
||||
// DefaultResolvers contains the list of resolvers known to be trusted.
|
||||
|
@ -45,16 +45,16 @@ var DefaultResolvers = []string{
|
|||
|
||||
// DNSOptions contains configuration options for the DNS executer.
|
||||
type DNSOptions struct {
|
||||
Debug bool
|
||||
JSON bool
|
||||
JSONRequests bool
|
||||
Template *templates.Template
|
||||
DNSRequest *requests.DNSRequest
|
||||
Writer *bufio.Writer
|
||||
|
||||
ColoredOutput bool
|
||||
Colorizer aurora.Aurora
|
||||
Decolorizer *regexp.Regexp
|
||||
Debug bool
|
||||
JSON bool
|
||||
JSONRequests bool
|
||||
Template *templates.Template
|
||||
DNSRequest *requests.DNSRequest
|
||||
Writer *bufio.Writer
|
||||
|
||||
Colorizer aurora.Aurora
|
||||
Decolorizer *regexp.Regexp
|
||||
}
|
||||
|
||||
// NewDNSExecuter creates a new DNS executer from a template
|
||||
|
@ -75,29 +75,32 @@ func NewDNSExecuter(options *DNSOptions) *DNSExecuter {
|
|||
colorizer: options.Colorizer,
|
||||
decolorizer: options.Decolorizer,
|
||||
}
|
||||
|
||||
return executer
|
||||
}
|
||||
|
||||
// ExecuteDNS executes the DNS request on a URL
|
||||
func (e *DNSExecuter) ExecuteDNS(p progress.IProgress, URL string) (result Result) {
|
||||
func (e *DNSExecuter) ExecuteDNS(p progress.IProgress, reqURL string) (result Result) {
|
||||
// Parse the URL and return domain if URL.
|
||||
var domain string
|
||||
if isURL(URL) {
|
||||
domain = extractDomain(URL)
|
||||
if isURL(reqURL) {
|
||||
domain = extractDomain(reqURL)
|
||||
} else {
|
||||
domain = URL
|
||||
domain = reqURL
|
||||
}
|
||||
|
||||
// Compile each request for the template based on the URL
|
||||
compiledRequest, err := e.dnsRequest.MakeDNSRequest(domain)
|
||||
if err != nil {
|
||||
result.Error = errors.Wrap(err, "could not make dns request")
|
||||
|
||||
p.Drop(1)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if e.debug {
|
||||
gologger.Infof("Dumped DNS request for %s (%s)\n\n", URL, e.template.ID)
|
||||
gologger.Infof("Dumped DNS request for %s (%s)\n\n", reqURL, e.template.ID)
|
||||
fmt.Fprintf(os.Stderr, "%s\n", compiledRequest.String())
|
||||
}
|
||||
|
||||
|
@ -105,20 +108,23 @@ func (e *DNSExecuter) ExecuteDNS(p progress.IProgress, URL string) (result Resul
|
|||
resp, err := e.dnsClient.Do(compiledRequest)
|
||||
if err != nil {
|
||||
result.Error = errors.Wrap(err, "could not send dns request")
|
||||
|
||||
p.Drop(1)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
p.Update()
|
||||
|
||||
gologger.Verbosef("Sent DNS request to %s\n", "dns-request", URL)
|
||||
gologger.Verbosef("Sent DNS request to %s\n", "dns-request", reqURL)
|
||||
|
||||
if e.debug {
|
||||
gologger.Infof("Dumped DNS response for %s (%s)\n\n", URL, e.template.ID)
|
||||
gologger.Infof("Dumped DNS response for %s (%s)\n\n", reqURL, e.template.ID)
|
||||
fmt.Fprintf(os.Stderr, "%s\n", resp.String())
|
||||
}
|
||||
|
||||
matcherCondition := e.dnsRequest.GetMatchersCondition()
|
||||
|
||||
for _, matcher := range e.dnsRequest.Matchers {
|
||||
// Check if the matcher matched
|
||||
if !matcher.MatchDNS(resp) {
|
||||
|
@ -139,6 +145,7 @@ func (e *DNSExecuter) ExecuteDNS(p progress.IProgress, URL string) (result Resul
|
|||
// All matchers have successfully completed so now start with the
|
||||
// next task which is extraction of input from matchers.
|
||||
var extractorResults []string
|
||||
|
||||
for _, extractor := range e.dnsRequest.Extractors {
|
||||
for match := range extractor.ExtractDNS(resp) {
|
||||
if !extractor.Internal {
|
||||
|
@ -151,10 +158,11 @@ func (e *DNSExecuter) ExecuteDNS(p progress.IProgress, URL string) (result Resul
|
|||
// AND or if we have extractors for the mechanism too.
|
||||
if len(e.dnsRequest.Extractors) > 0 || matcherCondition == matchers.ANDCondition {
|
||||
e.writeOutputDNS(domain, compiledRequest, resp, nil, extractorResults)
|
||||
|
||||
result.GotResults = true
|
||||
}
|
||||
|
||||
return
|
||||
return result
|
||||
}
|
||||
|
||||
// Close closes the dns executer for a template.
|
|
@ -2,11 +2,12 @@ package executer
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/logrusorgru/aurora"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httputil"
|
||||
|
@ -17,6 +18,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/progress"
|
||||
|
@ -27,42 +30,47 @@ import (
|
|||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
const (
|
||||
two = 2
|
||||
ten = 10
|
||||
)
|
||||
|
||||
// HTTPExecuter is client for performing HTTP requests
|
||||
// for a template.
|
||||
type HTTPExecuter struct {
|
||||
coloredOutput bool
|
||||
debug bool
|
||||
Results bool
|
||||
jsonOutput bool
|
||||
jsonRequest bool
|
||||
httpClient *retryablehttp.Client
|
||||
template *templates.Template
|
||||
bulkHttpRequest *requests.BulkHTTPRequest
|
||||
bulkHTTPRequest *requests.BulkHTTPRequest
|
||||
writer *bufio.Writer
|
||||
outputMutex *sync.Mutex
|
||||
customHeaders requests.CustomHeaders
|
||||
CookieJar *cookiejar.Jar
|
||||
|
||||
coloredOutput bool
|
||||
colorizer aurora.Aurora
|
||||
decolorizer *regexp.Regexp
|
||||
colorizer aurora.Aurora
|
||||
decolorizer *regexp.Regexp
|
||||
}
|
||||
|
||||
// HTTPOptions contains configuration options for the HTTP executer.
|
||||
type HTTPOptions struct {
|
||||
Debug bool
|
||||
JSON bool
|
||||
JSONRequests bool
|
||||
CookieReuse bool
|
||||
ColoredOutput bool
|
||||
Template *templates.Template
|
||||
BulkHttpRequest *requests.BulkHTTPRequest
|
||||
BulkHTTPRequest *requests.BulkHTTPRequest
|
||||
Writer *bufio.Writer
|
||||
Timeout int
|
||||
Retries int
|
||||
ProxyURL string
|
||||
ProxySocksURL string
|
||||
Debug bool
|
||||
JSON bool
|
||||
JSONRequests bool
|
||||
CustomHeaders requests.CustomHeaders
|
||||
CookieReuse bool
|
||||
CookieJar *cookiejar.Jar
|
||||
ColoredOutput bool
|
||||
Colorizer aurora.Aurora
|
||||
Decolorizer *regexp.Regexp
|
||||
}
|
||||
|
@ -71,18 +79,22 @@ type HTTPOptions struct {
|
|||
// and a HTTP request query.
|
||||
func NewHTTPExecuter(options *HTTPOptions) (*HTTPExecuter, error) {
|
||||
var proxyURL *url.URL
|
||||
|
||||
var err error
|
||||
|
||||
if options.ProxyURL != "" {
|
||||
proxyURL, err = url.Parse(options.ProxyURL)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the HTTP Client
|
||||
client := makeHTTPClient(proxyURL, options)
|
||||
// nolint:bodyclose // false positive there is no body to close yet
|
||||
client.CheckRetry = retryablehttp.HostSprayRetryPolicy()
|
||||
|
||||
if options.CookieJar != nil {
|
||||
client.HTTPClient.Jar = options.CookieJar
|
||||
} else if options.CookieReuse {
|
||||
|
@ -99,7 +111,7 @@ func NewHTTPExecuter(options *HTTPOptions) (*HTTPExecuter, error) {
|
|||
jsonRequest: options.JSONRequests,
|
||||
httpClient: client,
|
||||
template: options.Template,
|
||||
bulkHttpRequest: options.BulkHttpRequest,
|
||||
bulkHTTPRequest: options.BulkHTTPRequest,
|
||||
outputMutex: &sync.Mutex{},
|
||||
writer: options.Writer,
|
||||
customHeaders: options.CustomHeaders,
|
||||
|
@ -113,45 +125,49 @@ func NewHTTPExecuter(options *HTTPOptions) (*HTTPExecuter, error) {
|
|||
}
|
||||
|
||||
// ExecuteHTTP executes the HTTP request on a URL
|
||||
func (e *HTTPExecuter) ExecuteHTTP(p progress.IProgress, URL string) (result Result) {
|
||||
func (e *HTTPExecuter) ExecuteHTTP(ctx context.Context, p progress.IProgress, reqURL string) (result Result) {
|
||||
result.Matches = make(map[string]interface{})
|
||||
result.Extractions = make(map[string]interface{})
|
||||
dynamicvalues := make(map[string]interface{})
|
||||
|
||||
// verify if the URL is already being processed
|
||||
if e.bulkHttpRequest.HasGenerator(URL) {
|
||||
if e.bulkHTTPRequest.HasGenerator(reqURL) {
|
||||
return
|
||||
}
|
||||
|
||||
remaining := e.bulkHttpRequest.GetRequestCount()
|
||||
remaining := e.bulkHTTPRequest.GetRequestCount()
|
||||
e.bulkHTTPRequest.CreateGenerator(reqURL)
|
||||
|
||||
e.bulkHttpRequest.CreateGenerator(URL)
|
||||
for e.bulkHttpRequest.Next(URL) && !result.Done {
|
||||
httpRequest, err := e.bulkHttpRequest.MakeHTTPRequest(URL, dynamicvalues, e.bulkHttpRequest.Current(URL))
|
||||
for e.bulkHTTPRequest.Next(reqURL) && !result.Done {
|
||||
httpRequest, err := e.bulkHTTPRequest.MakeHTTPRequest(ctx, reqURL, dynamicvalues, e.bulkHTTPRequest.Current(reqURL))
|
||||
if err != nil {
|
||||
result.Error = errors.Wrap(err, "could not build http request")
|
||||
|
||||
p.Drop(remaining)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = e.handleHTTP(p, URL, httpRequest, dynamicvalues, &result)
|
||||
err = e.handleHTTP(reqURL, httpRequest, dynamicvalues, &result)
|
||||
if err != nil {
|
||||
result.Error = errors.Wrap(err, "could not handle http request")
|
||||
|
||||
p.Drop(remaining)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
e.bulkHttpRequest.Increment(URL)
|
||||
e.bulkHTTPRequest.Increment(reqURL)
|
||||
p.Update()
|
||||
remaining--
|
||||
}
|
||||
|
||||
gologger.Verbosef("Sent HTTP request to %s\n", "http-request", URL)
|
||||
gologger.Verbosef("Sent HTTP request to %s\n", "http-request", reqURL)
|
||||
|
||||
return
|
||||
return result
|
||||
}
|
||||
|
||||
func (e *HTTPExecuter) handleHTTP(p progress.IProgress, URL string, request *requests.HttpRequest, dynamicvalues map[string]interface{}, result *Result) error {
|
||||
func (e *HTTPExecuter) handleHTTP(reqURL string, request *requests.HTTPRequest, dynamicvalues map[string]interface{}, result *Result) error {
|
||||
e.setCustomHeaders(request)
|
||||
req := request.Request
|
||||
|
||||
|
@ -160,32 +176,44 @@ func (e *HTTPExecuter) handleHTTP(p progress.IProgress, URL string, request *req
|
|||
if err != nil {
|
||||
return errors.Wrap(err, "could not make http request")
|
||||
}
|
||||
gologger.Infof("Dumped HTTP request for %s (%s)\n\n", URL, e.template.ID)
|
||||
|
||||
gologger.Infof("Dumped HTTP request for %s (%s)\n\n", reqURL, e.template.ID)
|
||||
fmt.Fprintf(os.Stderr, "%s", string(dumpedRequest))
|
||||
}
|
||||
|
||||
resp, err := e.httpClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "Could not do request")
|
||||
}
|
||||
|
||||
if e.debug {
|
||||
dumpedResponse, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not dump http response")
|
||||
dumpedResponse, dumpErr := httputil.DumpResponse(resp, true)
|
||||
if dumpErr != nil {
|
||||
return errors.Wrap(dumpErr, "could not dump http response")
|
||||
}
|
||||
gologger.Infof("Dumped HTTP response for %s (%s)\n\n", URL, e.template.ID)
|
||||
|
||||
gologger.Infof("Dumped HTTP response for %s (%s)\n\n", reqURL, e.template.ID)
|
||||
fmt.Fprintf(os.Stderr, "%s\n", string(dumpedResponse))
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
_, copyErr := io.Copy(ioutil.Discard, resp.Body)
|
||||
if copyErr != nil {
|
||||
resp.Body.Close()
|
||||
return copyErr
|
||||
}
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
return errors.Wrap(err, "could not read http body")
|
||||
}
|
||||
|
||||
resp.Body.Close()
|
||||
|
||||
// net/http doesn't automatically decompress the response body if an encoding has been specified by the user in the request
|
||||
|
@ -199,8 +227,9 @@ func (e *HTTPExecuter) handleHTTP(p progress.IProgress, URL string, request *req
|
|||
body := unsafeToString(data)
|
||||
|
||||
headers := headersToString(resp.Header)
|
||||
matcherCondition := e.bulkHttpRequest.GetMatchersCondition()
|
||||
for _, matcher := range e.bulkHttpRequest.Matchers {
|
||||
matcherCondition := e.bulkHTTPRequest.GetMatchersCondition()
|
||||
|
||||
for _, matcher := range e.bulkHTTPRequest.Matchers {
|
||||
// Check if the matcher matched
|
||||
if !matcher.Match(resp, body, headers) {
|
||||
// If the condition is AND we haven't matched, try next request.
|
||||
|
@ -223,12 +252,15 @@ func (e *HTTPExecuter) handleHTTP(p progress.IProgress, URL string, request *req
|
|||
// All matchers have successfully completed so now start with the
|
||||
// next task which is extraction of input from matchers.
|
||||
var extractorResults, outputExtractorResults []string
|
||||
for _, extractor := range e.bulkHttpRequest.Extractors {
|
||||
|
||||
for _, extractor := range e.bulkHTTPRequest.Extractors {
|
||||
for match := range extractor.Extract(resp, body, headers) {
|
||||
if _, ok := dynamicvalues[extractor.Name]; !ok {
|
||||
dynamicvalues[extractor.Name] = match
|
||||
}
|
||||
|
||||
extractorResults = append(extractorResults, match)
|
||||
|
||||
if !extractor.Internal {
|
||||
outputExtractorResults = append(outputExtractorResults, match)
|
||||
}
|
||||
|
@ -242,6 +274,7 @@ func (e *HTTPExecuter) handleHTTP(p progress.IProgress, URL string, request *req
|
|||
// AND or if we have extractors for the mechanism too.
|
||||
if len(outputExtractorResults) > 0 || matcherCondition == matchers.ANDCondition {
|
||||
e.writeOutputHTTP(request, resp, body, nil, outputExtractorResults)
|
||||
|
||||
result.GotResults = true
|
||||
}
|
||||
|
||||
|
@ -260,10 +293,14 @@ func makeHTTPClient(proxyURL *url.URL, options *HTTPOptions) *retryablehttp.Clie
|
|||
retryablehttpOptions := retryablehttp.DefaultOptionsSpraying
|
||||
retryablehttpOptions.RetryWaitMax = 10 * time.Second
|
||||
retryablehttpOptions.RetryMax = options.Retries
|
||||
followRedirects := options.BulkHttpRequest.Redirects
|
||||
maxRedirects := options.BulkHttpRequest.MaxRedirects
|
||||
followRedirects := options.BulkHTTPRequest.Redirects
|
||||
maxRedirects := options.BulkHTTPRequest.MaxRedirects
|
||||
|
||||
transport := &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
MaxIdleConnsPerHost: -1,
|
||||
TLSClientConfig: &tls.Config{
|
||||
Renegotiation: tls.RenegotiateOnceAsClient,
|
||||
|
@ -275,21 +312,29 @@ func makeHTTPClient(proxyURL *url.URL, options *HTTPOptions) *retryablehttp.Clie
|
|||
// Attempts to overwrite the dial function with the socks proxied version
|
||||
if options.ProxySocksURL != "" {
|
||||
var proxyAuth *proxy.Auth
|
||||
|
||||
socksURL, err := url.Parse(options.ProxySocksURL)
|
||||
|
||||
if err == nil {
|
||||
proxyAuth = &proxy.Auth{}
|
||||
proxyAuth.User = socksURL.User.Username()
|
||||
proxyAuth.Password, _ = socksURL.User.Password()
|
||||
}
|
||||
|
||||
dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", socksURL.Hostname(), socksURL.Port()), proxyAuth, proxy.Direct)
|
||||
dc := dialer.(interface {
|
||||
DialContext(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
transport.Dial = dialer.Dial
|
||||
transport.DialContext = dc.DialContext
|
||||
}
|
||||
}
|
||||
|
||||
if proxyURL != nil {
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
}
|
||||
|
||||
return retryablehttp.NewWithHTTPClient(&http.Client{
|
||||
Transport: transport,
|
||||
Timeout: time.Duration(options.Timeout) * time.Second,
|
||||
|
@ -304,25 +349,29 @@ func makeCheckRedirectFunc(followRedirects bool, maxRedirects int) checkRedirect
|
|||
if !followRedirects {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
if maxRedirects == 0 {
|
||||
if len(requests) > 10 {
|
||||
if len(requests) > ten {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(requests) > maxRedirects {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *HTTPExecuter) setCustomHeaders(r *requests.HttpRequest) {
|
||||
func (e *HTTPExecuter) setCustomHeaders(r *requests.HTTPRequest) {
|
||||
for _, customHeader := range e.customHeaders {
|
||||
// This should be pre-computed somewhere and done only once
|
||||
tokens := strings.Split(customHeader, ":")
|
||||
// if it's an invalid header skip it
|
||||
if len(tokens) < 2 {
|
||||
if len(tokens) < two {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -334,10 +383,10 @@ func (e *HTTPExecuter) setCustomHeaders(r *requests.HttpRequest) {
|
|||
}
|
||||
|
||||
type Result struct {
|
||||
GotResults bool
|
||||
Done bool
|
||||
Meta map[string]interface{}
|
||||
Matches map[string]interface{}
|
||||
Extractions map[string]interface{}
|
||||
GotResults bool
|
||||
Error error
|
||||
Done bool
|
||||
}
|
|
@ -34,11 +34,14 @@ func headersToString(headers http.Header) string {
|
|||
|
||||
for i, value := range values {
|
||||
builder.WriteString(value)
|
||||
|
||||
if i != len(values)-1 {
|
||||
builder.WriteRune(',')
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteRune('\n')
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
|
@ -1,16 +1,18 @@
|
|||
package executer
|
||||
|
||||
import (
|
||||
"github.com/miekg/dns"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/matchers"
|
||||
)
|
||||
|
||||
// writeOutputDNS writes dns output to streams
|
||||
func (e *DNSExecuter) writeOutputDNS(domain string, req *dns.Msg, resp *dns.Msg, matcher *matchers.Matcher, extractorResults []string) {
|
||||
// nolint:interfacer // dns.Msg is out of current scope
|
||||
func (e *DNSExecuter) writeOutputDNS(domain string, req, resp *dns.Msg, matcher *matchers.Matcher, extractorResults []string) {
|
||||
if e.jsonOutput {
|
||||
output := jsonOutput{
|
||||
Template: e.template.ID,
|
||||
|
@ -20,16 +22,20 @@ func (e *DNSExecuter) writeOutputDNS(domain string, req *dns.Msg, resp *dns.Msg,
|
|||
Author: e.template.Info.Author,
|
||||
Description: e.template.Info.Description,
|
||||
}
|
||||
|
||||
if matcher != nil && len(matcher.Name) > 0 {
|
||||
output.MatcherName = matcher.Name
|
||||
}
|
||||
|
||||
if len(extractorResults) > 0 {
|
||||
output.ExtractedResults = extractorResults
|
||||
}
|
||||
|
||||
if e.jsonRequest {
|
||||
output.Request = req.String()
|
||||
output.Response = resp.String()
|
||||
}
|
||||
|
||||
data, err := jsoniter.Marshal(output)
|
||||
if err != nil {
|
||||
gologger.Warningf("Could not marshal json output: %s\n", err)
|
||||
|
@ -39,10 +45,26 @@ func (e *DNSExecuter) writeOutputDNS(domain string, req *dns.Msg, resp *dns.Msg,
|
|||
|
||||
if e.writer != nil {
|
||||
e.outputMutex.Lock()
|
||||
e.writer.Write(data)
|
||||
e.writer.WriteRune('\n')
|
||||
_, err := e.writer.Write(data)
|
||||
|
||||
if err != nil {
|
||||
e.outputMutex.Unlock()
|
||||
gologger.Errorf("Could not write output data: %s\n", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_, err = e.writer.WriteRune('\n')
|
||||
|
||||
if err != nil {
|
||||
e.outputMutex.Unlock()
|
||||
gologger.Errorf("Could not write output data: %s\n", err)
|
||||
|
||||
return
|
||||
}
|
||||
e.outputMutex.Unlock()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -51,27 +73,32 @@ func (e *DNSExecuter) writeOutputDNS(domain string, req *dns.Msg, resp *dns.Msg,
|
|||
|
||||
builder.WriteRune('[')
|
||||
builder.WriteString(colorizer.BrightGreen(e.template.ID).String())
|
||||
|
||||
if matcher != nil && len(matcher.Name) > 0 {
|
||||
builder.WriteString(":")
|
||||
builder.WriteString(colorizer.BrightGreen(matcher.Name).Bold().String())
|
||||
}
|
||||
|
||||
builder.WriteString("] [")
|
||||
builder.WriteString(colorizer.BrightBlue("dns").String())
|
||||
builder.WriteString("] ")
|
||||
|
||||
builder.WriteString(domain)
|
||||
|
||||
// If any extractors, write the results
|
||||
if len(extractorResults) > 0 {
|
||||
builder.WriteString(" [")
|
||||
|
||||
for i, result := range extractorResults {
|
||||
builder.WriteString(colorizer.BrightCyan(result).String())
|
||||
|
||||
if i != len(extractorResults)-1 {
|
||||
builder.WriteRune(',')
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteString("]")
|
||||
}
|
||||
|
||||
builder.WriteRune('\n')
|
||||
|
||||
// Write output to screen as well as any output file
|
||||
|
@ -83,7 +110,15 @@ func (e *DNSExecuter) writeOutputDNS(domain string, req *dns.Msg, resp *dns.Msg,
|
|||
if e.coloredOutput {
|
||||
message = e.decolorizer.ReplaceAllString(message, "")
|
||||
}
|
||||
e.writer.WriteString(message)
|
||||
|
||||
_, err := e.writer.WriteString(message)
|
||||
|
||||
if err != nil {
|
||||
e.outputMutex.Unlock()
|
||||
gologger.Errorf("Could not write output data: %s\n", err)
|
||||
|
||||
return
|
||||
}
|
||||
e.outputMutex.Unlock()
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
// writeOutputHTTP writes http output to streams
|
||||
func (e *HTTPExecuter) writeOutputHTTP(req *requests.HttpRequest, resp *http.Response, body string, matcher *matchers.Matcher, extractorResults []string) {
|
||||
func (e *HTTPExecuter) writeOutputHTTP(req *requests.HTTPRequest, resp *http.Response, body string, matcher *matchers.Matcher, extractorResults []string) {
|
||||
URL := req.Request.URL.String()
|
||||
|
||||
if e.jsonOutput {
|
||||
|
@ -24,12 +24,15 @@ func (e *HTTPExecuter) writeOutputHTTP(req *requests.HttpRequest, resp *http.Res
|
|||
Author: e.template.Info.Author,
|
||||
Description: e.template.Info.Description,
|
||||
}
|
||||
|
||||
if matcher != nil && len(matcher.Name) > 0 {
|
||||
output.MatcherName = matcher.Name
|
||||
}
|
||||
|
||||
if len(extractorResults) > 0 {
|
||||
output.ExtractedResults = extractorResults
|
||||
}
|
||||
|
||||
if e.jsonRequest {
|
||||
dumpedRequest, err := httputil.DumpRequest(req.Request.Request, true)
|
||||
if err != nil {
|
||||
|
@ -37,15 +40,18 @@ func (e *HTTPExecuter) writeOutputHTTP(req *requests.HttpRequest, resp *http.Res
|
|||
} else {
|
||||
output.Request = string(dumpedRequest)
|
||||
}
|
||||
|
||||
dumpedResponse, err := httputil.DumpResponse(resp, false)
|
||||
|
||||
if err != nil {
|
||||
gologger.Warningf("could not dump response: %s\n", err)
|
||||
} else {
|
||||
output.Response = string(dumpedResponse) + body
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data, err := jsoniter.Marshal(output)
|
||||
|
||||
if err != nil {
|
||||
gologger.Warningf("Could not marshal json output: %s\n", err)
|
||||
}
|
||||
|
@ -54,10 +60,26 @@ func (e *HTTPExecuter) writeOutputHTTP(req *requests.HttpRequest, resp *http.Res
|
|||
|
||||
if e.writer != nil {
|
||||
e.outputMutex.Lock()
|
||||
e.writer.Write(data)
|
||||
e.writer.WriteRune('\n')
|
||||
_, err := e.writer.Write(data)
|
||||
|
||||
if err != nil {
|
||||
e.outputMutex.Unlock()
|
||||
gologger.Errorf("Could not write output data: %s\n", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_, err = e.writer.WriteRune('\n')
|
||||
|
||||
if err != nil {
|
||||
e.outputMutex.Unlock()
|
||||
gologger.Errorf("Could not write output data: %s\n", err)
|
||||
|
||||
return
|
||||
}
|
||||
e.outputMutex.Unlock()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -66,37 +88,45 @@ func (e *HTTPExecuter) writeOutputHTTP(req *requests.HttpRequest, resp *http.Res
|
|||
|
||||
builder.WriteRune('[')
|
||||
builder.WriteString(colorizer.BrightGreen(e.template.ID).String())
|
||||
|
||||
if matcher != nil && len(matcher.Name) > 0 {
|
||||
builder.WriteString(":")
|
||||
builder.WriteString(colorizer.BrightGreen(matcher.Name).Bold().String())
|
||||
}
|
||||
|
||||
builder.WriteString("] [")
|
||||
builder.WriteString(colorizer.BrightBlue("http").String())
|
||||
builder.WriteString("] ")
|
||||
|
||||
// Escape the URL by replacing all % with %%
|
||||
escapedURL := strings.Replace(URL, "%", "%%", -1)
|
||||
escapedURL := strings.ReplaceAll(URL, "%", "%%")
|
||||
builder.WriteString(escapedURL)
|
||||
|
||||
// If any extractors, write the results
|
||||
if len(extractorResults) > 0 {
|
||||
builder.WriteString(" [")
|
||||
|
||||
for i, result := range extractorResults {
|
||||
builder.WriteString(colorizer.BrightCyan(result).String())
|
||||
|
||||
if i != len(extractorResults)-1 {
|
||||
builder.WriteRune(',')
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteString("]")
|
||||
}
|
||||
|
||||
// write meta if any
|
||||
if len(req.Meta) > 0 {
|
||||
builder.WriteString(" [")
|
||||
|
||||
var metas []string
|
||||
|
||||
for name, value := range req.Meta {
|
||||
metas = append(metas, colorizer.BrightYellow(name).Bold().String()+"="+ colorizer.BrightYellow(value.(string)).String() )
|
||||
metas = append(metas, colorizer.BrightYellow(name).Bold().String()+"="+colorizer.BrightYellow(value.(string)).String())
|
||||
}
|
||||
|
||||
builder.WriteString(strings.Join(metas, ","))
|
||||
builder.WriteString("]")
|
||||
}
|
||||
|
@ -112,7 +142,15 @@ func (e *HTTPExecuter) writeOutputHTTP(req *requests.HttpRequest, resp *http.Res
|
|||
if e.coloredOutput {
|
||||
message = e.decolorizer.ReplaceAllString(message, "")
|
||||
}
|
||||
e.writer.WriteString(message)
|
||||
|
||||
_, err := e.writer.WriteString(message)
|
||||
|
||||
if err != nil {
|
||||
e.outputMutex.Unlock()
|
||||
gologger.Errorf("Could not write output data: %s\n", err)
|
||||
|
||||
return
|
||||
}
|
||||
e.outputMutex.Unlock()
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ func (e *Extractor) CompileExtractors() error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("could not compile regex: %s", regex)
|
||||
}
|
||||
|
||||
e.regexCompiled = append(e.regexCompiled, compiled)
|
||||
}
|
||||
|
|
@ -24,19 +24,22 @@ func (e *Extractor) Extract(resp *http.Response, body, headers string) map[strin
|
|||
case KValExtractor:
|
||||
if e.part == HeaderPart {
|
||||
return e.extractKVal(resp)
|
||||
} else {
|
||||
matches := e.extractKVal(resp)
|
||||
if len(matches) > 0 {
|
||||
return matches
|
||||
}
|
||||
return e.extractCookieKVal(resp, "set-cookie")
|
||||
}
|
||||
|
||||
matches := e.extractKVal(resp)
|
||||
|
||||
if len(matches) > 0 {
|
||||
return matches
|
||||
}
|
||||
|
||||
return e.extractCookieKVal(resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractDNS extracts response from dns message using a regex
|
||||
// nolint:interfacer // dns.Msg is out of current scope
|
||||
func (e *Extractor) ExtractDNS(msg *dns.Msg) map[string]struct{} {
|
||||
switch e.extractorType {
|
||||
case RegexExtractor:
|
||||
|
@ -50,29 +53,34 @@ func (e *Extractor) ExtractDNS(msg *dns.Msg) map[string]struct{} {
|
|||
// extractRegex extracts text from a corpus and returns it
|
||||
func (e *Extractor) extractRegex(corpus string) map[string]struct{} {
|
||||
results := make(map[string]struct{})
|
||||
|
||||
for _, regex := range e.regexCompiled {
|
||||
matches := regex.FindAllString(corpus, -1)
|
||||
for _, match := range matches {
|
||||
results[match] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// extractKVal extracts text from http response
|
||||
func (e *Extractor) extractKVal(r *http.Response) map[string]struct{} {
|
||||
results := make(map[string]struct{})
|
||||
|
||||
for _, k := range e.KVal {
|
||||
for _, v := range r.Header.Values(k) {
|
||||
results[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// extractCookieKVal extracts text from cookies
|
||||
func (e *Extractor) extractCookieKVal(r *http.Response, key string) map[string]struct{} {
|
||||
func (e *Extractor) extractCookieKVal(r *http.Response) map[string]struct{} {
|
||||
results := make(map[string]struct{})
|
||||
|
||||
for _, k := range e.KVal {
|
||||
for _, cookie := range r.Cookies() {
|
||||
if cookie.Name == k {
|
||||
|
@ -80,5 +88,6 @@ func (e *Extractor) extractCookieKVal(r *http.Response, key string) map[string]s
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
|
@ -8,8 +8,11 @@ func ClusterbombGenerator(payloads map[string][]string) (out chan map[string]int
|
|||
// generator
|
||||
go func() {
|
||||
defer close(out)
|
||||
|
||||
var order []string
|
||||
|
||||
var parts [][]string
|
||||
|
||||
for name, wordlist := range payloads {
|
||||
order = append(order, name)
|
||||
parts = append(parts, wordlist)
|
|
@ -17,95 +17,134 @@ import (
|
|||
// HelperFunctions contains the dsl functions
|
||||
func HelperFunctions() (functions map[string]govaluate.ExpressionFunction) {
|
||||
functions = make(map[string]govaluate.ExpressionFunction)
|
||||
|
||||
// strings
|
||||
functions["len"] = func(args ...interface{}) (interface{}, error) {
|
||||
length := len(args[0].(string))
|
||||
return (float64)(length), nil
|
||||
|
||||
return float64(length), nil
|
||||
}
|
||||
|
||||
functions["toupper"] = func(args ...interface{}) (interface{}, error) {
|
||||
return strings.ToUpper(args[0].(string)), nil
|
||||
}
|
||||
|
||||
functions["tolower"] = func(args ...interface{}) (interface{}, error) {
|
||||
return strings.ToLower(args[0].(string)), nil
|
||||
}
|
||||
|
||||
functions["replace"] = func(args ...interface{}) (interface{}, error) {
|
||||
return strings.Replace(args[0].(string), args[1].(string), args[2].(string), -1), nil
|
||||
return strings.ReplaceAll(args[0].(string), args[1].(string), args[2].(string)), nil
|
||||
}
|
||||
|
||||
functions["trim"] = func(args ...interface{}) (interface{}, error) {
|
||||
return strings.Trim(args[0].(string), args[2].(string)), nil
|
||||
}
|
||||
|
||||
functions["trimleft"] = func(args ...interface{}) (interface{}, error) {
|
||||
return strings.TrimLeft(args[0].(string), args[1].(string)), nil
|
||||
}
|
||||
|
||||
functions["trimright"] = func(args ...interface{}) (interface{}, error) {
|
||||
return strings.TrimRight(args[0].(string), args[1].(string)), nil
|
||||
}
|
||||
|
||||
functions["trimspace"] = func(args ...interface{}) (interface{}, error) {
|
||||
return strings.TrimSpace(args[0].(string)), nil
|
||||
}
|
||||
|
||||
functions["trimprefix"] = func(args ...interface{}) (interface{}, error) {
|
||||
return strings.TrimPrefix(args[0].(string), args[1].(string)), nil
|
||||
}
|
||||
|
||||
functions["trimsuffix"] = func(args ...interface{}) (interface{}, error) {
|
||||
return strings.TrimSuffix(args[0].(string), args[1].(string)), nil
|
||||
}
|
||||
|
||||
functions["reverse"] = func(args ...interface{}) (interface{}, error) {
|
||||
return reverseString(args[0].(string)), nil
|
||||
}
|
||||
|
||||
// encoding
|
||||
functions["base64"] = func(args ...interface{}) (interface{}, error) {
|
||||
sEnc := base64.StdEncoding.EncodeToString([]byte(args[0].(string)))
|
||||
|
||||
return sEnc, nil
|
||||
}
|
||||
|
||||
functions["base64_decode"] = func(args ...interface{}) (interface{}, error) {
|
||||
sEnc := base64.StdEncoding.EncodeToString([]byte(args[0].(string)))
|
||||
|
||||
return sEnc, nil
|
||||
}
|
||||
|
||||
functions["url_encode"] = func(args ...interface{}) (interface{}, error) {
|
||||
return url.PathEscape(args[0].(string)), nil
|
||||
}
|
||||
|
||||
functions["url_decode"] = func(args ...interface{}) (interface{}, error) {
|
||||
return url.PathUnescape(args[0].(string))
|
||||
}
|
||||
|
||||
functions["hex_encode"] = func(args ...interface{}) (interface{}, error) {
|
||||
return hex.EncodeToString([]byte(args[0].(string))), nil
|
||||
}
|
||||
|
||||
functions["hex_decode"] = func(args ...interface{}) (interface{}, error) {
|
||||
hx, _ := hex.DecodeString(args[0].(string))
|
||||
return string(hx), nil
|
||||
}
|
||||
|
||||
functions["html_escape"] = func(args ...interface{}) (interface{}, error) {
|
||||
return html.EscapeString(args[0].(string)), nil
|
||||
}
|
||||
|
||||
functions["html_unescape"] = func(args ...interface{}) (interface{}, error) {
|
||||
return html.UnescapeString(args[0].(string)), nil
|
||||
}
|
||||
|
||||
// hashing
|
||||
functions["md5"] = func(args ...interface{}) (interface{}, error) {
|
||||
hash := md5.Sum([]byte(args[0].(string)))
|
||||
|
||||
return hex.EncodeToString(hash[:]), nil
|
||||
}
|
||||
|
||||
functions["sha256"] = func(args ...interface{}) (interface{}, error) {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(args[0].(string)))
|
||||
_, err := h.Write([]byte(args[0].(string)))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
functions["sha1"] = func(args ...interface{}) (interface{}, error) {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(args[0].(string)))
|
||||
_, err := h.Write([]byte(args[0].(string)))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// search
|
||||
functions["contains"] = func(args ...interface{}) (interface{}, error) {
|
||||
return strings.Contains(args[0].(string), args[1].(string)), nil
|
||||
}
|
||||
|
||||
functions["regex"] = func(args ...interface{}) (interface{}, error) {
|
||||
compiled, err := regexp.Compile(args[0].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return compiled.MatchString(args[1].(string)), nil
|
||||
}
|
||||
|
||||
return
|
||||
return functions
|
||||
}
|
|
@ -14,7 +14,7 @@ func PitchforkGenerator(payloads map[string][]string) (out chan map[string]inter
|
|||
}
|
||||
|
||||
if len(wordlist) != size {
|
||||
//set size = 0 and exit the cycle
|
||||
// set size = 0 and exit the cycle
|
||||
size = 0
|
||||
break
|
||||
}
|
|
@ -7,26 +7,30 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
const two = 2
|
||||
|
||||
// LoadPayloads creating proper data structure
|
||||
func LoadPayloads(payloads map[string]interface{}) map[string][]string {
|
||||
loadedPayloads := make(map[string][]string)
|
||||
// load all wordlists
|
||||
for name, payload := range payloads {
|
||||
switch payload.(type) {
|
||||
switch pt := payload.(type) {
|
||||
case string:
|
||||
v := payload.(string)
|
||||
elements := strings.Split(v, "\n")
|
||||
if len(elements) >= 2 {
|
||||
elements := strings.Split(pt, "\n")
|
||||
if len(elements) >= two {
|
||||
loadedPayloads[name] = elements
|
||||
} else {
|
||||
loadedPayloads[name] = LoadFile(v)
|
||||
loadedPayloads[name] = LoadFile(pt)
|
||||
}
|
||||
case []interface{}, interface{}:
|
||||
vv := payload.([]interface{})
|
||||
|
||||
var v []string
|
||||
|
||||
for _, vvv := range vv {
|
||||
v = append(v, fmt.Sprintf("%v", vvv))
|
||||
}
|
||||
|
||||
loadedPayloads[name] = v
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +53,9 @@ func StreamFile(filepath string) (content chan string) {
|
|||
|
||||
go func() {
|
||||
defer close(content)
|
||||
|
||||
file, err := os.Open(filepath)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -72,9 +78,11 @@ func StreamFile(filepath string) (content chan string) {
|
|||
// MergeMaps into a new one
|
||||
func MergeMaps(m1, m2 map[string]interface{}) (m map[string]interface{}) {
|
||||
m = make(map[string]interface{})
|
||||
|
||||
for k, v := range m1 {
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
for k, v := range m2 {
|
||||
m[k] = v
|
||||
}
|
||||
|
@ -88,6 +96,7 @@ func MergeMapsWithStrings(m1, m2 map[string]string) (m map[string]string) {
|
|||
for k, v := range m1 {
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
for k, v := range m2 {
|
||||
m[k] = v
|
||||
}
|
||||
|
@ -100,6 +109,7 @@ func reverseString(s string) string {
|
|||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||
runes[i], runes[j] = runes[j], runes[i]
|
||||
}
|
||||
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
|
@ -109,6 +119,7 @@ func CopyMap(originalMap map[string]interface{}) map[string]interface{} {
|
|||
for key, value := range originalMap {
|
||||
newMap[key] = value
|
||||
}
|
||||
|
||||
return newMap
|
||||
}
|
||||
|
||||
|
@ -118,6 +129,7 @@ func CopyMapWithDefaultValue(originalMap map[string][]string, defaultValue inter
|
|||
for key := range originalMap {
|
||||
newMap[key] = defaultValue
|
||||
}
|
||||
|
||||
return newMap
|
||||
}
|
||||
|
||||
|
@ -143,5 +155,6 @@ func FileExists(filename string) bool {
|
|||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !info.IsDir()
|
||||
}
|
|
@ -57,5 +57,6 @@ func (m *Matcher) CompileMatchers() error {
|
|||
} else {
|
||||
m.part = BodyPart
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -46,6 +46,7 @@ func (m *Matcher) Match(resp *http.Response, body, headers string) bool {
|
|||
// Match complex query
|
||||
return m.isNegative(m.matchDSL(httpToMap(resp, body, headers)))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -68,6 +69,7 @@ func (m *Matcher) MatchDNS(msg *dns.Msg) bool {
|
|||
// Match complex query
|
||||
return m.matchDSL(dnsToMap(msg))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -84,6 +86,7 @@ func (m *Matcher) matchStatusCode(statusCode int) bool {
|
|||
// Return on the first match.
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -100,6 +103,7 @@ func (m *Matcher) matchSizeCode(length int) bool {
|
|||
// Return on the first match.
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -108,7 +112,7 @@ func (m *Matcher) matchWords(corpus string) bool {
|
|||
// Iterate over all the words accepted as valid
|
||||
for i, word := range m.Words {
|
||||
// Continue if the word doesn't match
|
||||
if strings.Index(corpus, word) == -1 {
|
||||
if !strings.Contains(corpus, word) {
|
||||
// If we are in an AND request and a match failed,
|
||||
// return false as the AND condition fails on any single mismatch.
|
||||
if m.condition == ANDCondition {
|
||||
|
@ -128,6 +132,7 @@ func (m *Matcher) matchWords(corpus string) bool {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -156,16 +161,15 @@ func (m *Matcher) matchRegex(corpus string) bool {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// matchWords matches a word check against an HTTP Response/Headers.
|
||||
func (m *Matcher) matchBinary(corpus string) bool {
|
||||
|
||||
// Iterate over all the words accepted as valid
|
||||
for i, binary := range m.Binary {
|
||||
// Continue if the word doesn't match
|
||||
|
||||
hexa, _ := hex.DecodeString(binary)
|
||||
if !strings.Contains(corpus, string(hexa)) {
|
||||
// If we are in an AND request and a match failed,
|
||||
|
@ -187,6 +191,7 @@ func (m *Matcher) matchBinary(corpus string) bool {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -198,6 +203,7 @@ func (m *Matcher) matchDSL(mp map[string]interface{}) bool {
|
|||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var bResult bool
|
||||
bResult, ok := result.(bool)
|
||||
|
||||
|
@ -222,5 +228,6 @@ func (m *Matcher) matchDSL(mp map[string]interface{}) bool {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -125,5 +125,6 @@ func (m *Matcher) isNegative(data bool) bool {
|
|||
if m.Negative {
|
||||
return !data
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
|
@ -14,13 +14,15 @@ func httpToMap(resp *http.Response, body, headers string) (m map[string]interfac
|
|||
|
||||
m["content_length"] = resp.ContentLength
|
||||
m["status_code"] = resp.StatusCode
|
||||
|
||||
for k, v := range resp.Header {
|
||||
k = strings.ToLower(strings.TrimSpace(strings.Replace(k, "-", "_", -1)))
|
||||
k = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(k, "-", "_")))
|
||||
m[k] = strings.Join(v, " ")
|
||||
}
|
||||
m["all_headers"] = headers
|
||||
|
||||
m["all_headers"] = headers
|
||||
m["body"] = body
|
||||
|
||||
if r, err := httputil.DumpResponse(resp, true); err == nil {
|
||||
m["raw"] = string(r)
|
||||
}
|
||||
|
@ -32,30 +34,35 @@ func dnsToMap(msg *dns.Msg) (m map[string]interface{}) {
|
|||
m = make(map[string]interface{})
|
||||
|
||||
m["rcode"] = msg.Rcode
|
||||
|
||||
var qs string
|
||||
|
||||
for _, question := range msg.Question {
|
||||
qs += fmt.Sprintln(question.String())
|
||||
}
|
||||
|
||||
m["question"] = qs
|
||||
|
||||
var exs string
|
||||
for _, extra := range msg.Extra {
|
||||
exs += fmt.Sprintln(extra.String())
|
||||
}
|
||||
|
||||
m["extra"] = exs
|
||||
|
||||
var ans string
|
||||
for _, answer := range msg.Answer {
|
||||
ans += fmt.Sprintln(answer.String())
|
||||
}
|
||||
|
||||
m["answer"] = ans
|
||||
|
||||
var nss string
|
||||
for _, ns := range msg.Ns {
|
||||
nss += fmt.Sprintln(ns.String())
|
||||
}
|
||||
m["ns"] = nss
|
||||
|
||||
m["ns"] = nss
|
||||
m["raw"] = msg.String()
|
||||
|
||||
return m
|
|
@ -2,6 +2,7 @@ package requests
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -16,9 +17,18 @@ import (
|
|||
retryablehttp "github.com/projectdiscovery/retryablehttp-go"
|
||||
)
|
||||
|
||||
const (
|
||||
two = 2
|
||||
three = 3
|
||||
)
|
||||
|
||||
// BulkHTTPRequest contains a request to be made from a template
|
||||
type BulkHTTPRequest struct {
|
||||
Name string `yaml:"Name,omitempty"`
|
||||
// CookieReuse is an optional setting that makes cookies shared within requests
|
||||
CookieReuse bool `yaml:"cookie-reuse,omitempty"`
|
||||
// Redirects specifies whether redirects should be followed.
|
||||
Redirects bool `yaml:"redirects,omitempty"`
|
||||
Name string `yaml:"Name,omitempty"`
|
||||
// AttackType is the attack type
|
||||
// Sniper, PitchFork and ClusterBomb. Default is Sniper
|
||||
AttackType string `yaml:"attack,omitempty"`
|
||||
|
@ -34,8 +44,6 @@ type BulkHTTPRequest struct {
|
|||
Headers map[string]string `yaml:"headers,omitempty"`
|
||||
// Body is an optional parameter which contains the request body for POST methods, etc
|
||||
Body string `yaml:"body,omitempty"`
|
||||
// CookieReuse is an optional setting that makes cookies shared within requests
|
||||
CookieReuse bool `yaml:"cookie-reuse,omitempty"`
|
||||
// Matchers contains the detection mechanism for the request to identify
|
||||
// whether the request was successful
|
||||
Matchers []*matchers.Matcher `yaml:"matchers,omitempty"`
|
||||
|
@ -47,8 +55,6 @@ type BulkHTTPRequest struct {
|
|||
// Extractors contains the extraction mechanism for the request to identify
|
||||
// and extract parts of the response.
|
||||
Extractors []*extractors.Extractor `yaml:"extractors,omitempty"`
|
||||
// Redirects specifies whether redirects should be followed.
|
||||
Redirects bool `yaml:"redirects,omitempty"`
|
||||
// MaxRedirects is the maximum number of redirects that should be followed.
|
||||
MaxRedirects int `yaml:"max-redirects,omitempty"`
|
||||
// Raw contains raw requests
|
||||
|
@ -81,11 +87,12 @@ func (r *BulkHTTPRequest) GetRequestCount() int64 {
|
|||
return int64(len(r.Raw) | len(r.Path))
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) MakeHTTPRequest(baseURL string, dynamicValues map[string]interface{}, data string) (*HttpRequest, error) {
|
||||
func (r *BulkHTTPRequest) MakeHTTPRequest(ctx context.Context, baseURL string, dynamicValues map[string]interface{}, data string) (*HTTPRequest, error) {
|
||||
parsed, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostname := parsed.Host
|
||||
|
||||
values := generators.MergeMaps(dynamicValues, map[string]interface{}{
|
||||
|
@ -95,19 +102,19 @@ func (r *BulkHTTPRequest) MakeHTTPRequest(baseURL string, dynamicValues map[stri
|
|||
|
||||
// if data contains \n it's a raw request
|
||||
if strings.Contains(data, "\n") {
|
||||
return r.makeHTTPRequestFromRaw(baseURL, data, values)
|
||||
return r.makeHTTPRequestFromRaw(ctx, baseURL, data, values)
|
||||
}
|
||||
|
||||
return r.makeHTTPRequestFromModel(baseURL, data, values)
|
||||
return r.makeHTTPRequestFromModel(ctx, data, values)
|
||||
}
|
||||
|
||||
// MakeHTTPRequestFromModel creates a *http.Request from a request template
|
||||
func (r *BulkHTTPRequest) makeHTTPRequestFromModel(baseURL string, data string, values map[string]interface{}) (*HttpRequest, error) {
|
||||
func (r *BulkHTTPRequest) makeHTTPRequestFromModel(ctx context.Context, data string, values map[string]interface{}) (*HTTPRequest, error) {
|
||||
replacer := newReplacer(values)
|
||||
URL := replacer.Replace(data)
|
||||
|
||||
// Build a request on the specified URL
|
||||
req, err := http.NewRequest(r.Method, URL, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, r.Method, URL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -117,40 +124,42 @@ func (r *BulkHTTPRequest) makeHTTPRequestFromModel(baseURL string, data string,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &HttpRequest{Request: request}, nil
|
||||
return &HTTPRequest{Request: request}, nil
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) InitGenerator() {
|
||||
r.gsfm = NewGeneratorFSM(r.attackType, r.Payloads, r.Path, r.Raw)
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) CreateGenerator(URL string) {
|
||||
r.gsfm.Add(URL)
|
||||
func (r *BulkHTTPRequest) CreateGenerator(reqURL string) {
|
||||
r.gsfm.Add(reqURL)
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) HasGenerator(URL string) bool {
|
||||
return r.gsfm.Has(URL)
|
||||
func (r *BulkHTTPRequest) HasGenerator(reqURL string) bool {
|
||||
return r.gsfm.Has(reqURL)
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) ReadOne(URL string) {
|
||||
r.gsfm.ReadOne(URL)
|
||||
func (r *BulkHTTPRequest) ReadOne(reqURL string) {
|
||||
r.gsfm.ReadOne(reqURL)
|
||||
}
|
||||
|
||||
// makeHTTPRequestFromRaw creates a *http.Request from a raw request
|
||||
func (r *BulkHTTPRequest) makeHTTPRequestFromRaw(baseURL string, data string, values map[string]interface{}) (*HttpRequest, error) {
|
||||
func (r *BulkHTTPRequest) makeHTTPRequestFromRaw(ctx context.Context, baseURL, data string, values map[string]interface{}) (*HTTPRequest, error) {
|
||||
// Add trailing line
|
||||
data += "\n"
|
||||
|
||||
if len(r.Payloads) > 0 {
|
||||
r.gsfm.InitOrSkip(baseURL)
|
||||
r.ReadOne(baseURL)
|
||||
return r.handleRawWithPaylods(data, baseURL, values, r.gsfm.Value(baseURL))
|
||||
|
||||
return r.handleRawWithPaylods(ctx, data, baseURL, values, r.gsfm.Value(baseURL))
|
||||
}
|
||||
|
||||
// otherwise continue with normal flow
|
||||
return r.handleRawWithPaylods(data, baseURL, values, nil)
|
||||
return r.handleRawWithPaylods(ctx, data, baseURL, values, nil)
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) handleRawWithPaylods(raw string, baseURL string, values, genValues map[string]interface{}) (*HttpRequest, error) {
|
||||
func (r *BulkHTTPRequest) handleRawWithPaylods(ctx context.Context, raw, baseURL string, values, genValues map[string]interface{}) (*HTTPRequest, error) {
|
||||
baseValues := generators.CopyMap(values)
|
||||
finValues := generators.MergeMaps(baseValues, genValues)
|
||||
|
||||
|
@ -166,13 +175,16 @@ func (r *BulkHTTPRequest) handleRawWithPaylods(raw string, baseURL string, value
|
|||
// check if the match contains a dynamic variable
|
||||
expr := generators.TrimDelimiters(match)
|
||||
compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expr, generators.HelperFunctions())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := compiled.Evaluate(finValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dynamicValues[expr] = result
|
||||
}
|
||||
|
||||
|
@ -185,7 +197,7 @@ func (r *BulkHTTPRequest) handleRawWithPaylods(raw string, baseURL string, value
|
|||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(compiledRequest.Method, compiledRequest.FullURL, strings.NewReader(compiledRequest.Data))
|
||||
req, err := http.NewRequestWithContext(ctx, compiledRequest.Method, compiledRequest.FullURL, strings.NewReader(compiledRequest.Data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -200,7 +212,7 @@ func (r *BulkHTTPRequest) handleRawWithPaylods(raw string, baseURL string, value
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &HttpRequest{Request: request, Meta: genValues}, nil
|
||||
return &HTTPRequest{Request: request, Meta: genValues}, nil
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) fillRequest(req *http.Request, values map[string]interface{}) (*retryablehttp.Request, error) {
|
||||
|
@ -231,6 +243,7 @@ func (r *BulkHTTPRequest) fillRequest(req *http.Request, values map[string]inter
|
|||
if _, ok := req.Header["Accept"]; !ok {
|
||||
req.Header.Set("Accept", "*/*")
|
||||
}
|
||||
|
||||
if _, ok := req.Header["Accept-Language"]; !ok {
|
||||
req.Header.Set("Accept-Language", "en")
|
||||
}
|
||||
|
@ -238,7 +251,7 @@ func (r *BulkHTTPRequest) fillRequest(req *http.Request, values map[string]inter
|
|||
return retryablehttp.FromRequest(req)
|
||||
}
|
||||
|
||||
type HttpRequest struct {
|
||||
type HTTPRequest struct {
|
||||
Request *retryablehttp.Request
|
||||
Meta map[string]interface{}
|
||||
}
|
||||
|
@ -266,7 +279,7 @@ type RawRequest struct {
|
|||
}
|
||||
|
||||
// parseRawRequest parses the raw request as supplied by the user
|
||||
func (r *BulkHTTPRequest) parseRawRequest(request string, baseURL string) (*RawRequest, error) {
|
||||
func (r *BulkHTTPRequest) parseRawRequest(request, baseURL string) (*RawRequest, error) {
|
||||
reader := bufio.NewReader(strings.NewReader(request))
|
||||
|
||||
rawRequest := RawRequest{
|
||||
|
@ -277,23 +290,25 @@ func (r *BulkHTTPRequest) parseRawRequest(request string, baseURL string) (*RawR
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read request: %s", err)
|
||||
}
|
||||
|
||||
parts := strings.Split(s, " ")
|
||||
if len(parts) < 3 {
|
||||
|
||||
if len(parts) < three {
|
||||
return nil, fmt.Errorf("malformed request supplied")
|
||||
}
|
||||
// Set the request Method
|
||||
rawRequest.Method = parts[0]
|
||||
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
line, readErr := reader.ReadString('\n')
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if err != nil || line == "" {
|
||||
if readErr != nil || line == "" {
|
||||
break
|
||||
}
|
||||
|
||||
p := strings.SplitN(line, ":", 2)
|
||||
if len(p) != 2 {
|
||||
p := strings.SplitN(line, ":", two)
|
||||
if len(p) != two {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -307,10 +322,11 @@ func (r *BulkHTTPRequest) parseRawRequest(request string, baseURL string) (*RawR
|
|||
// Handle case with the full http url in path. In that case,
|
||||
// ignore any host header that we encounter and use the path as request URL
|
||||
if strings.HasPrefix(parts[1], "http") {
|
||||
parsed, err := url.Parse(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse request URL: %s", err)
|
||||
parsed, parseErr := url.Parse(parts[1])
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("could not parse request URL: %s", parseErr)
|
||||
}
|
||||
|
||||
rawRequest.Path = parts[1]
|
||||
rawRequest.Headers["Host"] = parsed.Host
|
||||
} else {
|
||||
|
@ -325,21 +341,19 @@ func (r *BulkHTTPRequest) parseRawRequest(request string, baseURL string) (*RawR
|
|||
}
|
||||
|
||||
var hostURL string
|
||||
if len(rawRequest.Headers["Host"]) == 0 {
|
||||
if rawRequest.Headers["Host"] == "" {
|
||||
hostURL = parsedURL.Host
|
||||
} else {
|
||||
hostURL = rawRequest.Headers["Host"]
|
||||
}
|
||||
|
||||
if len(rawRequest.Path) == 0 {
|
||||
if rawRequest.Path == "" {
|
||||
rawRequest.Path = parsedURL.Path
|
||||
} else {
|
||||
} else if strings.HasPrefix(rawRequest.Path, "?") {
|
||||
// requests generated from http.ReadRequest have incorrect RequestURI, so they
|
||||
// cannot be used to perform another request directly, we need to generate a new one
|
||||
// with the new target url
|
||||
if strings.HasPrefix(rawRequest.Path, "?") {
|
||||
rawRequest.Path = fmt.Sprintf("%s%s", parsedURL.Path, rawRequest.Path)
|
||||
}
|
||||
rawRequest.Path = fmt.Sprintf("%s%s", parsedURL.Path, rawRequest.Path)
|
||||
}
|
||||
|
||||
rawRequest.FullURL = fmt.Sprintf("%s://%s%s", parsedURL.Scheme, hostURL, rawRequest.Path)
|
||||
|
@ -349,29 +363,31 @@ func (r *BulkHTTPRequest) parseRawRequest(request string, baseURL string) (*RawR
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read request body: %s", err)
|
||||
}
|
||||
|
||||
rawRequest.Data = string(b)
|
||||
|
||||
return &rawRequest, nil
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) Next(URL string) bool {
|
||||
return r.gsfm.Next(URL)
|
||||
func (r *BulkHTTPRequest) Next(reqURL string) bool {
|
||||
return r.gsfm.Next(reqURL)
|
||||
}
|
||||
func (r *BulkHTTPRequest) Position(URL string) int {
|
||||
return r.gsfm.Position(URL)
|
||||
func (r *BulkHTTPRequest) Position(reqURL string) int {
|
||||
return r.gsfm.Position(reqURL)
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) Reset(URL string) {
|
||||
r.gsfm.Reset(URL)
|
||||
func (r *BulkHTTPRequest) Reset(reqURL string) {
|
||||
r.gsfm.Reset(reqURL)
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) Current(URL string) string {
|
||||
return r.gsfm.Current(URL)
|
||||
func (r *BulkHTTPRequest) Current(reqURL string) string {
|
||||
return r.gsfm.Current(reqURL)
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) Total() int {
|
||||
return len(r.Path) + len(r.Raw)
|
||||
}
|
||||
|
||||
func (r *BulkHTTPRequest) Increment(URL string) {
|
||||
r.gsfm.Increment(URL)
|
||||
func (r *BulkHTTPRequest) Increment(reqURL string) {
|
||||
r.gsfm.Increment(reqURL)
|
||||
}
|
|
@ -92,6 +92,7 @@ func toQType(ttype string) (rtype uint16) {
|
|||
default:
|
||||
rtype = dns.TypeA
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -115,5 +116,6 @@ func toQClass(tclass string) (rclass uint16) {
|
|||
// Use INET by default.
|
||||
rclass = dns.ClassINET
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -10,16 +10,16 @@ import (
|
|||
type GeneratorState int
|
||||
|
||||
const (
|
||||
Init GeneratorState = iota
|
||||
Running
|
||||
Done
|
||||
fifteen = 15
|
||||
initial GeneratorState = iota
|
||||
running
|
||||
done
|
||||
)
|
||||
|
||||
type Generator struct {
|
||||
sync.RWMutex
|
||||
positionPath int
|
||||
positionRaw int
|
||||
currentPayloads map[string]interface{}
|
||||
gchan chan map[string]interface{}
|
||||
currentGeneratorValue map[string]interface{}
|
||||
state GeneratorState
|
||||
|
@ -49,14 +49,19 @@ func NewGeneratorFSM(typ generators.Type, payloads map[string]interface{}, paths
|
|||
}
|
||||
|
||||
generatorFunc := generators.SniperGenerator
|
||||
|
||||
switch typ {
|
||||
case generators.PitchFork:
|
||||
generatorFunc = generators.PitchforkGenerator
|
||||
case generators.ClusterBomb:
|
||||
generatorFunc = generators.ClusterbombGenerator
|
||||
case generators.Sniper:
|
||||
generatorFunc = generators.SniperGenerator
|
||||
}
|
||||
|
||||
gsfm.generator = generatorFunc
|
||||
}
|
||||
|
||||
gsfm.Generators = make(map[string]*Generator)
|
||||
|
||||
return &gsfm
|
||||
|
@ -67,7 +72,7 @@ func (gfsm *GeneratorFSM) Add(key string) {
|
|||
defer gfsm.Unlock()
|
||||
|
||||
if _, ok := gfsm.Generators[key]; !ok {
|
||||
gfsm.Generators[key] = &Generator{state: Init}
|
||||
gfsm.Generators[key] = &Generator{state: initial}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,6 +81,7 @@ func (gfsm *GeneratorFSM) Has(key string) bool {
|
|||
defer gfsm.RUnlock()
|
||||
|
||||
_, ok := gfsm.Generators[key]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
|
@ -90,31 +96,35 @@ func (gfsm *GeneratorFSM) ReadOne(key string) {
|
|||
gfsm.RLock()
|
||||
defer gfsm.RUnlock()
|
||||
g, ok := gfsm.Generators[key]
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for afterCh := time.After(15 * time.Second); ; {
|
||||
for afterCh := time.After(fifteen * time.Second); ; {
|
||||
select {
|
||||
// got a value
|
||||
case curGenValue, ok := <-g.gchan:
|
||||
if !ok {
|
||||
g.Lock()
|
||||
g.gchan = nil
|
||||
g.state = Done
|
||||
g.state = done
|
||||
g.currentGeneratorValue = nil
|
||||
g.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
g.currentGeneratorValue = curGenValue
|
||||
|
||||
return
|
||||
// timeout
|
||||
case <-afterCh:
|
||||
g.Lock()
|
||||
g.gchan = nil
|
||||
g.state = Done
|
||||
g.state = done
|
||||
g.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -132,9 +142,10 @@ func (gfsm *GeneratorFSM) InitOrSkip(key string) {
|
|||
if len(gfsm.payloads) > 0 {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
|
||||
if g.gchan == nil {
|
||||
g.gchan = gfsm.generator(gfsm.basePayloads)
|
||||
g.state = Running
|
||||
g.state = running
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,13 +175,14 @@ func (gfsm *GeneratorFSM) Next(key string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if gfsm.hasPayloads() && g.state == Done {
|
||||
if gfsm.hasPayloads() && g.state == done {
|
||||
return false
|
||||
}
|
||||
|
||||
if g.positionPath+g.positionRaw >= len(gfsm.Paths)+len(gfsm.Raws) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -189,6 +201,7 @@ func (gfsm *GeneratorFSM) Position(key string) int {
|
|||
func (gfsm *GeneratorFSM) Reset(key string) {
|
||||
gfsm.Lock()
|
||||
defer gfsm.Unlock()
|
||||
|
||||
if !gfsm.Has(key) {
|
||||
gfsm.Add(key)
|
||||
}
|
||||
|
@ -238,7 +251,7 @@ func (gfsm *GeneratorFSM) Increment(key string) {
|
|||
if len(gfsm.Raws) > 0 && g.positionRaw < len(gfsm.Raws) {
|
||||
// if we have payloads increment only when the generators are done
|
||||
if g.gchan == nil {
|
||||
g.state = Done
|
||||
g.state = done
|
||||
g.positionRaw++
|
||||
}
|
||||
}
|
|
@ -13,10 +13,7 @@ import (
|
|||
func newReplacer(values map[string]interface{}) *strings.Replacer {
|
||||
var replacerItems []string
|
||||
for k, v := range values {
|
||||
replacerItems = append(replacerItems, fmt.Sprintf("{{%s}}", k))
|
||||
replacerItems = append(replacerItems, fmt.Sprintf("%s", v))
|
||||
replacerItems = append(replacerItems, fmt.Sprintf("%s", k))
|
||||
replacerItems = append(replacerItems, fmt.Sprintf("%s", v))
|
||||
replacerItems = append(replacerItems, fmt.Sprintf("{{%s}}", k), fmt.Sprintf("%s", v), k, fmt.Sprintf("%s", v))
|
||||
}
|
||||
|
||||
return strings.NewReplacer(replacerItems...)
|
|
@ -1,7 +1,6 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -31,7 +30,7 @@ func Parse(file string) (*Template, error) {
|
|||
|
||||
// If no requests, and it is also not a workflow, return error.
|
||||
if len(template.BulkRequestsHTTP)+len(template.RequestsDNS) <= 0 {
|
||||
return nil, errors.New("No requests defined")
|
||||
return nil, fmt.Errorf("no requests defined for %s", template.ID)
|
||||
}
|
||||
|
||||
// Compile the matchers and the extractors for http requests
|
||||
|
@ -54,47 +53,51 @@ func Parse(file string) (*Template, error) {
|
|||
|
||||
// Validate the payloads if any
|
||||
for name, payload := range request.Payloads {
|
||||
switch payload.(type) {
|
||||
switch pt := payload.(type) {
|
||||
case string:
|
||||
v := payload.(string)
|
||||
// check if it's a multiline string list
|
||||
if len(strings.Split(v, "\n")) <= 1 {
|
||||
if len(strings.Split(pt, "\n")) <= 1 {
|
||||
// check if it's a worldlist file
|
||||
if !generators.FileExists(v) {
|
||||
if !generators.FileExists(pt) {
|
||||
// attempt to load the file by taking the full path, tokezining it and searching the template in such paths
|
||||
changed := false
|
||||
pathTokens := strings.Split(template.path, "/")
|
||||
|
||||
for i := range pathTokens {
|
||||
tpath := path.Join(strings.Join(pathTokens[:i], "/"), v)
|
||||
tpath := path.Join(strings.Join(pathTokens[:i], "/"), pt)
|
||||
if generators.FileExists(tpath) {
|
||||
request.Payloads[name] = tpath
|
||||
changed = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !changed {
|
||||
return nil, fmt.Errorf("The %s file for payload %s does not exist or does not contain enough elements", v, name)
|
||||
return nil, fmt.Errorf("the %s file for payload %s does not exist or does not contain enough elements", pt, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
case []string, []interface{}:
|
||||
if len(payload.([]interface{})) <= 0 {
|
||||
return nil, fmt.Errorf("The payload %s does not contain enough elements", name)
|
||||
if len(payload.([]interface{})) == 0 {
|
||||
return nil, fmt.Errorf("the payload %s does not contain enough elements", name)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("The payload %s has invalid type", name)
|
||||
return nil, fmt.Errorf("the payload %s has invalid type", name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, matcher := range request.Matchers {
|
||||
if err = matcher.CompileMatchers(); err != nil {
|
||||
return nil, err
|
||||
matchErr := matcher.CompileMatchers()
|
||||
if matchErr != nil {
|
||||
return nil, matchErr
|
||||
}
|
||||
}
|
||||
|
||||
for _, extractor := range request.Extractors {
|
||||
if err := extractor.CompileExtractors(); err != nil {
|
||||
return nil, err
|
||||
extractErr := extractor.CompileExtractors()
|
||||
if extractErr != nil {
|
||||
return nil, extractErr
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,13 +115,15 @@ func Parse(file string) (*Template, error) {
|
|||
}
|
||||
|
||||
for _, matcher := range request.Matchers {
|
||||
if err = matcher.CompileMatchers(); err != nil {
|
||||
err = matcher.CompileMatchers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, extractor := range request.Extractors {
|
||||
if err := extractor.CompileExtractors(); err != nil {
|
||||
err := extractor.CompileExtractors()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ func (t *Template) GetHTTPRequestCount() int64 {
|
|||
for _, request := range t.BulkRequestsHTTP {
|
||||
count += request.GetRequestCount()
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
|
@ -47,5 +48,6 @@ func (t *Template) GetDNSRequestCount() int64 {
|
|||
for _, request := range t.RequestsDNS {
|
||||
count += request.GetRequestCount()
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
|
@ -23,7 +23,7 @@ func Parse(file string) (*Workflow, error) {
|
|||
}
|
||||
|
||||
if workflow.Logic == "" {
|
||||
return nil, errors.New("No logic provided")
|
||||
return nil, errors.New("no logic provided")
|
||||
}
|
||||
|
||||
workflow.path = file
|
|
@ -1,6 +1,7 @@
|
|||
package workflows
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
tengo "github.com/d5/tengo/v2"
|
||||
|
@ -12,6 +13,8 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/generators"
|
||||
)
|
||||
|
||||
const two = 2
|
||||
|
||||
// NucleiVar within the scripting engine
|
||||
type NucleiVar struct {
|
||||
tengo.ObjectImpl
|
||||
|
@ -50,31 +53,43 @@ func (n *NucleiVar) Call(args ...tengo.Object) (ret tengo.Object, err error) {
|
|||
}
|
||||
|
||||
// if external variables are specified and matches the template ones, these gets overwritten
|
||||
if len(args) >= 2 {
|
||||
if len(args) >= two {
|
||||
externalVars = iterableToMap(args[1])
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
var gotResult atomicboolean.AtomBool
|
||||
|
||||
for _, template := range n.Templates {
|
||||
p := template.Progress
|
||||
|
||||
if template.HTTPOptions != nil {
|
||||
p.AddToTotal(template.HTTPOptions.Template.GetHTTPRequestCount())
|
||||
|
||||
for _, request := range template.HTTPOptions.Template.BulkRequestsHTTP {
|
||||
// apply externally supplied payloads if any
|
||||
request.Headers = generators.MergeMapsWithStrings(request.Headers, headers)
|
||||
// apply externally supplied payloads if any
|
||||
request.Payloads = generators.MergeMaps(request.Payloads, externalVars)
|
||||
template.HTTPOptions.BulkHttpRequest = request
|
||||
|
||||
template.HTTPOptions.BulkHTTPRequest = request
|
||||
|
||||
if template.HTTPOptions.Colorizer == nil {
|
||||
template.HTTPOptions.Colorizer = aurora.NewAurora(true)
|
||||
}
|
||||
|
||||
httpExecuter, err := executer.NewHTTPExecuter(template.HTTPOptions)
|
||||
|
||||
if err != nil {
|
||||
p.Drop(request.GetRequestCount())
|
||||
gologger.Warningf("Could not compile request for template '%s': %s\n", template.HTTPOptions.Template.ID, err)
|
||||
|
||||
continue
|
||||
}
|
||||
result := httpExecuter.ExecuteHTTP(p, n.URL)
|
||||
|
||||
result := httpExecuter.ExecuteHTTP(ctx, p, n.URL)
|
||||
|
||||
if result.Error != nil {
|
||||
gologger.Warningf("Could not send request for template '%s': %s\n", template.HTTPOptions.Template.ID, result.Error)
|
||||
continue
|
||||
|
@ -89,10 +104,12 @@ func (n *NucleiVar) Call(args ...tengo.Object) (ret tengo.Object, err error) {
|
|||
|
||||
if template.DNSOptions != nil {
|
||||
p.AddToTotal(template.DNSOptions.Template.GetDNSRequestCount())
|
||||
|
||||
for _, request := range template.DNSOptions.Template.RequestsDNS {
|
||||
template.DNSOptions.DNSRequest = request
|
||||
dnsExecuter := executer.NewDNSExecuter(template.DNSOptions)
|
||||
result := dnsExecuter.ExecuteDNS(p, n.URL)
|
||||
|
||||
if result.Error != nil {
|
||||
gologger.Warningf("Could not compile request for template '%s': %s\n", template.HTTPOptions.Template.ID, result.Error)
|
||||
continue
|
||||
|
@ -109,6 +126,7 @@ func (n *NucleiVar) Call(args ...tengo.Object) (ret tengo.Object, err error) {
|
|||
if gotResult.Get() {
|
||||
return tengo.TrueValue, nil
|
||||
}
|
||||
|
||||
return tengo.FalseValue, nil
|
||||
}
|
||||
|
||||
|
@ -151,32 +169,36 @@ func (n *NucleiVar) IndexGet(index tengo.Object) (res tengo.Object, err error) {
|
|||
return tengo.UndefinedValue, nil
|
||||
}
|
||||
|
||||
switch r.(type) {
|
||||
switch rt := r.(type) {
|
||||
case bool:
|
||||
if r.(bool) {
|
||||
if rt {
|
||||
res = tengo.TrueValue
|
||||
} else {
|
||||
res = tengo.FalseValue
|
||||
}
|
||||
case string:
|
||||
res = &tengo.String{Value: r.(string)}
|
||||
res = &tengo.String{Value: rt}
|
||||
case []string:
|
||||
rr, ok := r.([]string)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
var resA []tengo.Object
|
||||
|
||||
for _, rrr := range rr {
|
||||
resA = append(resA, &tengo.String{Value: rrr})
|
||||
}
|
||||
|
||||
res = &tengo.Array{Value: resA}
|
||||
}
|
||||
|
||||
return
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func iterableToMap(t tengo.Object) map[string]interface{} {
|
||||
m := make(map[string]interface{})
|
||||
|
||||
if t.CanIterate() {
|
||||
i := t.Iterate()
|
||||
for i.Next() {
|
||||
|
@ -184,6 +206,7 @@ func iterableToMap(t tengo.Object) map[string]interface{} {
|
|||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
value := tengo.ToInterface(i.Value())
|
||||
m[key] = value
|
||||
}
|
||||
|
@ -194,6 +217,7 @@ func iterableToMap(t tengo.Object) map[string]interface{} {
|
|||
|
||||
func iterableToMapString(t tengo.Object) map[string]string {
|
||||
m := make(map[string]string)
|
||||
|
||||
if t.CanIterate() {
|
||||
i := t.Iterate()
|
||||
for i.Next() {
|
||||
|
@ -201,6 +225,7 @@ func iterableToMapString(t tengo.Object) map[string]string {
|
|||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if value, ok := tengo.ToString(i.Value()); ok {
|
||||
m[key] = value
|
||||
}
|
Loading…
Reference in New Issue