github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/common/generators/generators.go (about)

     1  // Inspired from https://github.com/ffuf/ffuf/blob/master/pkg/input/input.go
     2  
     3  package generators
     4  
     5  import (
     6  	"github.com/pkg/errors"
     7  
     8  	"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
     9  	"github.com/projectdiscovery/nuclei/v2/pkg/catalog/config"
    10  )
    11  
    12  // PayloadGenerator is the generator struct for generating payloads
    13  type PayloadGenerator struct {
    14  	Type     AttackType
    15  	catalog  catalog.Catalog
    16  	payloads map[string][]string
    17  }
    18  
    19  // New creates a new generator structure for payload generation
    20  func New(payloads map[string]interface{}, attackType AttackType, templatePath string, allowLocalFileAccess bool, catalog catalog.Catalog, customAttackType string) (*PayloadGenerator, error) {
    21  	if attackType.String() == "" {
    22  		attackType = BatteringRamAttack
    23  	}
    24  
    25  	// Resolve payload paths if they are files.
    26  	payloadsFinal := make(map[string]interface{})
    27  	for name, payload := range payloads {
    28  		payloadsFinal[name] = payload
    29  	}
    30  	for name, payload := range payloads {
    31  		payloadStr, ok := payload.(string)
    32  		if ok {
    33  			final, resolveErr := catalog.ResolvePath(payloadStr, templatePath)
    34  			if resolveErr != nil {
    35  				return nil, errors.Wrap(resolveErr, "could not read payload file")
    36  			}
    37  			payloadsFinal[name] = final
    38  		}
    39  	}
    40  
    41  	generator := &PayloadGenerator{catalog: catalog}
    42  	if err := generator.validate(payloadsFinal, templatePath); err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	compiled, err := generator.loadPayloads(payloadsFinal, templatePath, config.DefaultConfig.TemplatesDirectory, allowLocalFileAccess)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	generator.Type = attackType
    51  	generator.payloads = compiled
    52  
    53  	if customAttackType != "" {
    54  		attackTypeNew, err := toAttackType(customAttackType)
    55  		if err != nil {
    56  			return nil, errors.Wrap(err, "could not parse custom attack-type")
    57  		}
    58  		generator.Type = attackTypeNew
    59  	}
    60  	// Validate the batteringram payload set
    61  	if attackType == BatteringRamAttack {
    62  		if len(payloads) != 1 {
    63  			return nil, errors.New("batteringram must have single payload set")
    64  		}
    65  	}
    66  	return generator, nil
    67  }
    68  
    69  // Iterator is a single instance of an iterator for a generator structure
    70  type Iterator struct {
    71  	Type        AttackType
    72  	position    int
    73  	msbIterator int
    74  	total       int
    75  	payloads    []*payloadIterator
    76  }
    77  
    78  // NewIterator creates a new iterator for the payloads generator
    79  func (g *PayloadGenerator) NewIterator() *Iterator {
    80  	var payloads []*payloadIterator
    81  
    82  	for name, values := range g.payloads {
    83  		payloads = append(payloads, &payloadIterator{name: name, values: values})
    84  	}
    85  	iterator := &Iterator{
    86  		Type:     g.Type,
    87  		payloads: payloads,
    88  	}
    89  	iterator.total = iterator.Total()
    90  	return iterator
    91  }
    92  
    93  // Reset resets the iterator back to its initial value
    94  func (i *Iterator) Reset() {
    95  	i.position = 0
    96  	i.msbIterator = 0
    97  
    98  	for _, payload := range i.payloads {
    99  		payload.resetPosition()
   100  	}
   101  }
   102  
   103  // Remaining returns the amount of requests left for the generator.
   104  func (i *Iterator) Remaining() int {
   105  	return i.total - i.position
   106  }
   107  
   108  // Total returns the amount of input combinations available
   109  func (i *Iterator) Total() int {
   110  	count := 0
   111  	switch i.Type {
   112  	case BatteringRamAttack:
   113  		for _, p := range i.payloads {
   114  			count += len(p.values)
   115  		}
   116  	case PitchForkAttack:
   117  		count = len(i.payloads[0].values)
   118  		for _, p := range i.payloads {
   119  			if count > len(p.values) {
   120  				count = len(p.values)
   121  			}
   122  		}
   123  	case ClusterBombAttack:
   124  		count = 1
   125  		for _, p := range i.payloads {
   126  			count *= len(p.values)
   127  		}
   128  	}
   129  	return count
   130  }
   131  
   132  // Value returns the next value for an iterator
   133  func (i *Iterator) Value() (map[string]interface{}, bool) {
   134  	switch i.Type {
   135  	case BatteringRamAttack:
   136  		return i.batteringRamValue()
   137  	case PitchForkAttack:
   138  		return i.pitchforkValue()
   139  	case ClusterBombAttack:
   140  		return i.clusterbombValue()
   141  	default:
   142  		return i.batteringRamValue()
   143  	}
   144  }
   145  
   146  // batteringRamValue returns a list of all payloads for the iterator
   147  func (i *Iterator) batteringRamValue() (map[string]interface{}, bool) {
   148  	values := make(map[string]interface{}, 1)
   149  
   150  	currentIndex := i.msbIterator
   151  	payload := i.payloads[currentIndex]
   152  	if !payload.next() {
   153  		i.msbIterator++
   154  		if i.msbIterator == len(i.payloads) {
   155  			return nil, false
   156  		}
   157  		return i.batteringRamValue()
   158  	}
   159  	values[payload.name] = payload.value()
   160  	payload.incrementPosition()
   161  	i.position++
   162  	return values, true
   163  }
   164  
   165  // pitchforkValue returns a map of keyword:value pairs in same index
   166  func (i *Iterator) pitchforkValue() (map[string]interface{}, bool) {
   167  	values := make(map[string]interface{}, len(i.payloads))
   168  
   169  	for _, p := range i.payloads {
   170  		if !p.next() {
   171  			return nil, false
   172  		}
   173  		values[p.name] = p.value()
   174  		p.incrementPosition()
   175  	}
   176  	i.position++
   177  	return values, true
   178  }
   179  
   180  // clusterbombValue returns a combination of all input pairs in key:value format.
   181  func (i *Iterator) clusterbombValue() (map[string]interface{}, bool) {
   182  	if i.position >= i.total {
   183  		return nil, false
   184  	}
   185  	values := make(map[string]interface{}, len(i.payloads))
   186  
   187  	// Should we signal the next InputProvider in the slice to increment
   188  	signalNext := false
   189  	first := true
   190  	for index, p := range i.payloads {
   191  		if signalNext {
   192  			p.incrementPosition()
   193  			signalNext = false
   194  		}
   195  		if !p.next() {
   196  			// No more inputs in this input provider
   197  			if index == i.msbIterator {
   198  				// Reset all previous wordlists and increment the msb counter
   199  				i.msbIterator++
   200  				i.clusterbombIteratorReset()
   201  				// Start again
   202  				return i.clusterbombValue()
   203  			}
   204  			p.resetPosition()
   205  			signalNext = true
   206  		}
   207  		values[p.name] = p.value()
   208  		if first {
   209  			p.incrementPosition()
   210  			first = false
   211  		}
   212  	}
   213  	i.position++
   214  	return values, true
   215  }
   216  
   217  func (i *Iterator) clusterbombIteratorReset() {
   218  	for index, p := range i.payloads {
   219  		if index < i.msbIterator {
   220  			p.resetPosition()
   221  		}
   222  		if index == i.msbIterator {
   223  			p.incrementPosition()
   224  		}
   225  	}
   226  }
   227  
   228  // payloadIterator is a single instance of an iterator for a single payload list.
   229  type payloadIterator struct {
   230  	index  int
   231  	name   string
   232  	values []string
   233  }
   234  
   235  // next returns true if there are more values in payload iterator
   236  func (i *payloadIterator) next() bool {
   237  	return i.index < len(i.values)
   238  }
   239  
   240  // resetPosition resets the position of the payload iterator
   241  func (i *payloadIterator) resetPosition() {
   242  	i.index = 0
   243  }
   244  
   245  // incrementPosition increments the position of the payload iterator
   246  func (i *payloadIterator) incrementPosition() {
   247  	i.index++
   248  }
   249  
   250  // value returns the value of the payload at an index
   251  func (i *payloadIterator) value() string {
   252  	return i.values[i.index]
   253  }