Merge branch 'vzamanillo-golang-cli'

dev
Ice3man543 2020-08-27 18:20:02 +05:30
commit 09da97ae93
50 changed files with 919 additions and 348 deletions

View File

@ -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/

116
.golangci.yml Normal file
View File

@ -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"

View File

@ -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()
}

View File

@ -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

View File

@ -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=

View File

@ -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
}

View File

@ -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)

View File

@ -3,11 +3,11 @@ package runner
import "github.com/projectdiscovery/gologger"
const banner = `
__ _
__ _
____ __ _______/ /__ (_)
/ __ \/ / / / ___/ / _ \/ /
/ / / / /_/ / /__/ / __/ /
/_/ /_/\__,_/\___/_/\___/_/ v2.1
/ __ \/ / / / ___/ / _ \/ /
/ / / / /_/ / /__/ / __/ /
/_/ /_/\__,_/\___/_/\___/_/ v2.1
`
// Version is the current version of nuclei

View File

@ -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)
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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.

View File

@ -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
}

View File

@ -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()
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -57,5 +57,6 @@ func (m *Matcher) CompileMatchers() error {
} else {
m.part = BodyPart
}
return nil
}

View File

@ -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
}

View File

@ -125,5 +125,6 @@ func (m *Matcher) isNegative(data bool) bool {
if m.Negative {
return !data
}
return data
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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++
}
}

View File

@ -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...)

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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

View 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
}