Path: blob/dev/pkg/protocols/common/generators/generators.go
2838 views
// Inspired from https://github.com/ffuf/ffuf/blob/master/pkg/input/input.go12package generators34import (5"maps"67"github.com/pkg/errors"89"github.com/projectdiscovery/nuclei/v3/pkg/catalog"10"github.com/projectdiscovery/nuclei/v3/pkg/types"11)1213// PayloadGenerator is the generator struct for generating payloads14type PayloadGenerator struct {15Type AttackType16catalog catalog.Catalog17payloads map[string][]string18options *types.Options19}2021// New creates a new generator structure for payload generation22func New(payloads map[string]interface{}, attackType AttackType, templatePath string, catalog catalog.Catalog, customAttackType string, opts *types.Options) (*PayloadGenerator, error) {23if attackType.String() == "" {24attackType = BatteringRamAttack25}2627// Resolve payload paths if they are files.28payloadsFinal := make(map[string]interface{})29for payloadName, v := range payloads {30switch value := v.(type) {31case map[interface{}]interface{}:32values, err := parsePayloadsWithAggression(payloadName, value, opts.FuzzAggressionLevel)33if err != nil {34return nil, errors.Wrap(err, "could not parse payloads with aggression")35}36maps.Copy(payloadsFinal, values)37default:38payloadsFinal[payloadName] = v39}40}4142generator := &PayloadGenerator{catalog: catalog, options: opts}43if err := generator.validate(payloadsFinal, templatePath); err != nil {44return nil, err45}4647compiled, err := generator.loadPayloads(payloadsFinal, templatePath)48if err != nil {49return nil, err50}51generator.Type = attackType52generator.payloads = compiled5354if customAttackType != "" {55attackTypeNew, err := toAttackType(customAttackType)56if err != nil {57return nil, errors.Wrap(err, "could not parse custom attack-type")58}59generator.Type = attackTypeNew60}61// Validate the batteringram payload set62if attackType == BatteringRamAttack {63if len(payloads) != 1 {64return nil, errors.New("batteringram must have single payload set")65}66}67return generator, nil68}6970type aggressionLevelToPayloads struct {71Low []interface{}72Medium []interface{}73High []interface{}74}7576// parsePayloadsWithAggression parses the payloads with the aggression level77//78// Three aggression are supported -79// - low80// - medium81// - high82//83// low is the default level. If medium is specified, all templates from84// low and medium are executed. Similarly with high, including all templates85// from low, medium, high.86func parsePayloadsWithAggression(name string, v map[interface{}]interface{}, aggression string) (map[string]interface{}, error) {87payloadsLevels := &aggressionLevelToPayloads{}8889for k, v := range v {90if _, ok := v.([]interface{}); !ok {91return nil, errors.Errorf("only lists are supported for aggression levels payloads")92}93var ok bool94switch k {95case "low":96payloadsLevels.Low, ok = v.([]interface{})97case "medium":98payloadsLevels.Medium, ok = v.([]interface{})99case "high":100payloadsLevels.High, ok = v.([]interface{})101default:102return nil, errors.Errorf("invalid aggression level %s specified for %s", k, name)103}104if !ok {105return nil, errors.Errorf("invalid aggression level %s specified for %s", k, name)106}107}108109payloads := make(map[string]interface{})110switch aggression {111case "low":112payloads[name] = payloadsLevels.Low113case "medium":114payloads[name] = append(payloadsLevels.Low, payloadsLevels.Medium...)115case "high":116payloads[name] = append(payloadsLevels.Low, payloadsLevels.Medium...)117payloads[name] = append(payloads[name].([]interface{}), payloadsLevels.High...)118default:119return nil, errors.Errorf("invalid aggression level %s specified for %s", aggression, name)120}121return payloads, nil122}123124// Iterator is a single instance of an iterator for a generator structure125type Iterator struct {126Type AttackType127position int128msbIterator int129total int130payloads []*payloadIterator131}132133// NewIterator creates a new iterator for the payloads generator134func (g *PayloadGenerator) NewIterator() *Iterator {135var payloads []*payloadIterator136137for name, values := range g.payloads {138payloads = append(payloads, &payloadIterator{name: name, values: values})139}140iterator := &Iterator{141Type: g.Type,142payloads: payloads,143}144iterator.total = iterator.Total()145return iterator146}147148// Reset resets the iterator back to its initial value149func (i *Iterator) Reset() {150i.position = 0151i.msbIterator = 0152153for _, payload := range i.payloads {154payload.resetPosition()155}156}157158// Remaining returns the amount of requests left for the generator.159func (i *Iterator) Remaining() int {160return i.total - i.position161}162163// Total returns the amount of input combinations available164func (i *Iterator) Total() int {165count := 0166switch i.Type {167case BatteringRamAttack:168for _, p := range i.payloads {169count += len(p.values)170}171case PitchForkAttack:172count = len(i.payloads[0].values)173for _, p := range i.payloads {174if count > len(p.values) {175count = len(p.values)176}177}178case ClusterBombAttack:179count = 1180for _, p := range i.payloads {181count *= len(p.values)182}183}184return count185}186187// Value returns the next value for an iterator188func (i *Iterator) Value() (map[string]interface{}, bool) {189switch i.Type {190case BatteringRamAttack:191return i.batteringRamValue()192case PitchForkAttack:193return i.pitchforkValue()194case ClusterBombAttack:195return i.clusterbombValue()196default:197return i.batteringRamValue()198}199}200201// batteringRamValue returns a list of all payloads for the iterator202func (i *Iterator) batteringRamValue() (map[string]interface{}, bool) {203values := make(map[string]interface{}, 1)204205currentIndex := i.msbIterator206payload := i.payloads[currentIndex]207if !payload.next() {208i.msbIterator++209if i.msbIterator == len(i.payloads) {210return nil, false211}212return i.batteringRamValue()213}214values[payload.name] = payload.value()215payload.incrementPosition()216i.position++217return values, true218}219220// pitchforkValue returns a map of keyword:value pairs in same index221func (i *Iterator) pitchforkValue() (map[string]interface{}, bool) {222values := make(map[string]interface{}, len(i.payloads))223224for _, p := range i.payloads {225if !p.next() {226return nil, false227}228values[p.name] = p.value()229p.incrementPosition()230}231i.position++232return values, true233}234235// clusterbombValue returns a combination of all input pairs in key:value format.236func (i *Iterator) clusterbombValue() (map[string]interface{}, bool) {237if i.position >= i.total {238return nil, false239}240values := make(map[string]interface{}, len(i.payloads))241242// Should we signal the next InputProvider in the slice to increment243signalNext := false244first := true245for index, p := range i.payloads {246if signalNext {247p.incrementPosition()248signalNext = false249}250if !p.next() {251// No more inputs in this input provider252if index == i.msbIterator {253// Reset all previous wordlists and increment the msb counter254i.msbIterator++255i.clusterbombIteratorReset()256// Start again257return i.clusterbombValue()258}259p.resetPosition()260signalNext = true261}262values[p.name] = p.value()263if first {264p.incrementPosition()265first = false266}267}268i.position++269return values, true270}271272func (i *Iterator) clusterbombIteratorReset() {273for index, p := range i.payloads {274if index < i.msbIterator {275p.resetPosition()276}277if index == i.msbIterator {278p.incrementPosition()279}280}281}282283// payloadIterator is a single instance of an iterator for a single payload list.284type payloadIterator struct {285index int286name string287values []string288}289290// next returns true if there are more values in payload iterator291func (i *payloadIterator) next() bool {292return i.index < len(i.values)293}294295// resetPosition resets the position of the payload iterator296func (i *payloadIterator) resetPosition() {297i.index = 0298}299300// incrementPosition increments the position of the payload iterator301func (i *payloadIterator) incrementPosition() {302i.index++303}304305// value returns the value of the payload at an index306func (i *payloadIterator) value() string {307return i.values[i.index]308}309310311