nuclei/pkg/protocols/common/generators/generators.go

245 lines
6.0 KiB
Go

// Inspired from https://github.com/ffuf/ffuf/blob/master/pkg/input/input.go
package generators
import (
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
// PayloadGenerator is the generator struct for generating payloads
type PayloadGenerator struct {
Type AttackType
catalog catalog.Catalog
payloads map[string][]string
options *types.Options
}
// New creates a new generator structure for payload generation
func New(payloads map[string]interface{}, attackType AttackType, templatePath string, catalog catalog.Catalog, customAttackType string, opts *types.Options) (*PayloadGenerator, error) {
if attackType.String() == "" {
attackType = BatteringRamAttack
}
// Resolve payload paths if they are files.
payloadsFinal := make(map[string]interface{})
for name, payload := range payloads {
payloadsFinal[name] = payload
}
generator := &PayloadGenerator{catalog: catalog, options: opts}
if err := generator.validate(payloadsFinal, templatePath); err != nil {
return nil, err
}
compiled, err := generator.loadPayloads(payloadsFinal, templatePath)
if err != nil {
return nil, err
}
generator.Type = attackType
generator.payloads = compiled
if customAttackType != "" {
attackTypeNew, err := toAttackType(customAttackType)
if err != nil {
return nil, errors.Wrap(err, "could not parse custom attack-type")
}
generator.Type = attackTypeNew
}
// Validate the batteringram payload set
if attackType == BatteringRamAttack {
if len(payloads) != 1 {
return nil, errors.New("batteringram must have single payload set")
}
}
return generator, nil
}
// Iterator is a single instance of an iterator for a generator structure
type Iterator struct {
Type AttackType
position int
msbIterator int
total int
payloads []*payloadIterator
}
// NewIterator creates a new iterator for the payloads generator
func (g *PayloadGenerator) NewIterator() *Iterator {
var payloads []*payloadIterator
for name, values := range g.payloads {
payloads = append(payloads, &payloadIterator{name: name, values: values})
}
iterator := &Iterator{
Type: g.Type,
payloads: payloads,
}
iterator.total = iterator.Total()
return iterator
}
// Reset resets the iterator back to its initial value
func (i *Iterator) Reset() {
i.position = 0
i.msbIterator = 0
for _, payload := range i.payloads {
payload.resetPosition()
}
}
// Remaining returns the amount of requests left for the generator.
func (i *Iterator) Remaining() int {
return i.total - i.position
}
// Total returns the amount of input combinations available
func (i *Iterator) Total() int {
count := 0
switch i.Type {
case BatteringRamAttack:
for _, p := range i.payloads {
count += len(p.values)
}
case PitchForkAttack:
count = len(i.payloads[0].values)
for _, p := range i.payloads {
if count > len(p.values) {
count = len(p.values)
}
}
case ClusterBombAttack:
count = 1
for _, p := range i.payloads {
count *= len(p.values)
}
}
return count
}
// Value returns the next value for an iterator
func (i *Iterator) Value() (map[string]interface{}, bool) {
switch i.Type {
case BatteringRamAttack:
return i.batteringRamValue()
case PitchForkAttack:
return i.pitchforkValue()
case ClusterBombAttack:
return i.clusterbombValue()
default:
return i.batteringRamValue()
}
}
// batteringRamValue returns a list of all payloads for the iterator
func (i *Iterator) batteringRamValue() (map[string]interface{}, bool) {
values := make(map[string]interface{}, 1)
currentIndex := i.msbIterator
payload := i.payloads[currentIndex]
if !payload.next() {
i.msbIterator++
if i.msbIterator == len(i.payloads) {
return nil, false
}
return i.batteringRamValue()
}
values[payload.name] = payload.value()
payload.incrementPosition()
i.position++
return values, true
}
// pitchforkValue returns a map of keyword:value pairs in same index
func (i *Iterator) pitchforkValue() (map[string]interface{}, bool) {
values := make(map[string]interface{}, len(i.payloads))
for _, p := range i.payloads {
if !p.next() {
return nil, false
}
values[p.name] = p.value()
p.incrementPosition()
}
i.position++
return values, true
}
// clusterbombValue returns a combination of all input pairs in key:value format.
func (i *Iterator) clusterbombValue() (map[string]interface{}, bool) {
if i.position >= i.total {
return nil, false
}
values := make(map[string]interface{}, len(i.payloads))
// Should we signal the next InputProvider in the slice to increment
signalNext := false
first := true
for index, p := range i.payloads {
if signalNext {
p.incrementPosition()
signalNext = false
}
if !p.next() {
// No more inputs in this input provider
if index == i.msbIterator {
// Reset all previous wordlists and increment the msb counter
i.msbIterator++
i.clusterbombIteratorReset()
// Start again
return i.clusterbombValue()
}
p.resetPosition()
signalNext = true
}
values[p.name] = p.value()
if first {
p.incrementPosition()
first = false
}
}
i.position++
return values, true
}
func (i *Iterator) clusterbombIteratorReset() {
for index, p := range i.payloads {
if index < i.msbIterator {
p.resetPosition()
}
if index == i.msbIterator {
p.incrementPosition()
}
}
}
// payloadIterator is a single instance of an iterator for a single payload list.
type payloadIterator struct {
index int
name string
values []string
}
// next returns true if there are more values in payload iterator
func (i *payloadIterator) next() bool {
return i.index < len(i.values)
}
// resetPosition resets the position of the payload iterator
func (i *payloadIterator) resetPosition() {
i.index = 0
}
// incrementPosition increments the position of the payload iterator
func (i *payloadIterator) incrementPosition() {
i.index++
}
// value returns the value of the payload at an index
func (i *payloadIterator) value() string {
return i.values[i.index]
}