mirror of
https://github.com/ffuf/ffuf
synced 2024-12-01 15:49:18 +00:00
Added support for using ranges in size, word count or status code matching/filtering (#47)
* allow ranges on response size matching/filtering * allow ranges on word count matching/filtering * allow ranges on http status matching/filtering * documentation update about using ranges in size, word count and status code filtering/matching * moved valuerange code to ffuf main package
This commit is contained in:
parent
cb37501616
commit
08c4cb4f6f
9 changed files with 110 additions and 48 deletions
|
@ -122,13 +122,13 @@ To define the test case for ffuf, use the keyword `FUZZ` anywhere in the URL (`-
|
|||
-e string
|
||||
Comma separated list of extensions to apply. Each extension provided will extend the wordlist entry once.
|
||||
-fc string
|
||||
Filter HTTP status codes from response
|
||||
Filter HTTP status codes from response. Comma separated list of codes and ranges
|
||||
-fr string
|
||||
Filter regexp
|
||||
-fs string
|
||||
Filter HTTP response size
|
||||
Filter HTTP response size. Comma separated list of sizes and ranges
|
||||
-fw string
|
||||
Filter by amount of words in response
|
||||
Filter by amount of words in response. Comma separated list of word counts and ranges
|
||||
-input-cmd string
|
||||
Command producing the input. --input-num is required when using this input method. Overrides -w.
|
||||
-input-num int
|
||||
|
@ -185,6 +185,7 @@ The only dependency of ffuf is Go 1.11. No dependencies outside of Go standard l
|
|||
- Changed
|
||||
- New CLI flag: -i, dummy flag that does nothing. for compatibility with copy as curl.
|
||||
- New CLI flag: -b/--cookie, cookie data for compatibility with copy as curl.
|
||||
- Filtering and matching by status code, response size or word count now allow using ranges in addition to single values
|
||||
|
||||
- v0.10
|
||||
- New
|
||||
|
|
6
main.go
6
main.go
|
@ -59,10 +59,10 @@ func main() {
|
|||
flag.StringVar(&conf.Wordlist, "w", "", "Wordlist file path or - to read from standard input")
|
||||
flag.BoolVar(&conf.TLSVerify, "k", false, "TLS identity verification")
|
||||
flag.StringVar(&opts.delay, "p", "", "Seconds of `delay` between requests, or a range of random delay. For example \"0.1\" or \"0.1-2.0\"")
|
||||
flag.StringVar(&opts.filterStatus, "fc", "", "Filter HTTP status codes from response")
|
||||
flag.StringVar(&opts.filterSize, "fs", "", "Filter HTTP response size")
|
||||
flag.StringVar(&opts.filterStatus, "fc", "", "Filter HTTP status codes from response. Comma separated list of codes and ranges")
|
||||
flag.StringVar(&opts.filterSize, "fs", "", "Filter HTTP response size. Comma separated list of sizes and ranges")
|
||||
flag.StringVar(&opts.filterRegexp, "fr", "", "Filter regexp")
|
||||
flag.StringVar(&opts.filterWords, "fw", "", "Filter by amount of words in response")
|
||||
flag.StringVar(&opts.filterWords, "fw", "", "Filter by amount of words in response. Comma separated list of word counts and ranges")
|
||||
flag.StringVar(&conf.Data, "d", "", "POST data")
|
||||
flag.StringVar(&conf.Data, "data", "", "POST data (alias of -d)")
|
||||
flag.StringVar(&conf.Data, "data-ascii", "", "POST data (alias of -d)")
|
||||
|
|
38
pkg/ffuf/valuerange.go
Normal file
38
pkg/ffuf/valuerange.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package ffuf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ValueRange struct {
|
||||
Min, Max int64
|
||||
}
|
||||
|
||||
func ValueRangeFromString(instr string) (ValueRange, error) {
|
||||
// is the value a range
|
||||
minmax := regexp.MustCompile("^(\\d+)\\-(\\d+)$").FindAllStringSubmatch(instr, -1)
|
||||
if minmax != nil {
|
||||
// yes
|
||||
minval, err := strconv.ParseInt(minmax[0][1], 10, 0)
|
||||
if err != nil {
|
||||
return ValueRange{}, fmt.Errorf("Invalid value: %s", minmax[0][1])
|
||||
}
|
||||
maxval, err := strconv.ParseInt(minmax[0][2], 10, 0)
|
||||
if err != nil {
|
||||
return ValueRange{}, fmt.Errorf("Invalid value: %s", minmax[0][2])
|
||||
}
|
||||
if minval >= maxval {
|
||||
return ValueRange{}, fmt.Errorf("Minimum has to be smaller than maximum")
|
||||
}
|
||||
return ValueRange{minval, maxval}, nil
|
||||
} else {
|
||||
// no, a single value or something else
|
||||
intval, err := strconv.ParseInt(instr, 10, 0)
|
||||
if err != nil {
|
||||
return ValueRange{}, fmt.Errorf("Invalid value: %s", instr)
|
||||
}
|
||||
return ValueRange{intval, intval}, nil
|
||||
}
|
||||
}
|
|
@ -9,24 +9,25 @@ import (
|
|||
)
|
||||
|
||||
type SizeFilter struct {
|
||||
Value []int64
|
||||
Value []ffuf.ValueRange
|
||||
}
|
||||
|
||||
func NewSizeFilter(value string) (ffuf.FilterProvider, error) {
|
||||
var intvals []int64
|
||||
var intranges []ffuf.ValueRange
|
||||
for _, sv := range strings.Split(value, ",") {
|
||||
intval, err := strconv.ParseInt(sv, 10, 0)
|
||||
vr, err := ffuf.ValueRangeFromString(sv)
|
||||
if err != nil {
|
||||
return &SizeFilter{}, fmt.Errorf("Size filter or matcher (-fs / -ms): invalid value: %s", value)
|
||||
return &SizeFilter{}, fmt.Errorf("Size filter or matcher (-fs / -ms): invalid value: %s", sv)
|
||||
}
|
||||
intvals = append(intvals, intval)
|
||||
|
||||
intranges = append(intranges, vr)
|
||||
}
|
||||
return &SizeFilter{Value: intvals}, nil
|
||||
return &SizeFilter{Value: intranges}, nil
|
||||
}
|
||||
|
||||
func (f *SizeFilter) Filter(response *ffuf.Response) (bool, error) {
|
||||
for _, iv := range f.Value {
|
||||
if iv == response.ContentLength {
|
||||
if iv.Min <= response.ContentLength && response.ContentLength <= iv.Max {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +37,11 @@ func (f *SizeFilter) Filter(response *ffuf.Response) (bool, error) {
|
|||
func (f *SizeFilter) Repr() string {
|
||||
var strval []string
|
||||
for _, iv := range f.Value {
|
||||
strval = append(strval, strconv.Itoa(int(iv)))
|
||||
if iv.Min == iv.Max {
|
||||
strval = append(strval, strconv.Itoa(int(iv.Min)))
|
||||
} else {
|
||||
strval = append(strval, strconv.Itoa(int(iv.Min))+"-"+strconv.Itoa(int(iv.Max)))
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Response size: %s", strings.Join(strval, ","))
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import (
|
|||
)
|
||||
|
||||
func TestNewSizeFilter(t *testing.T) {
|
||||
f, _ := NewSizeFilter("1,2,3,444")
|
||||
f, _ := NewSizeFilter("1,2,3,444,5-90")
|
||||
sizeRepr := f.Repr()
|
||||
if strings.Index(sizeRepr, "1,2,3,444") == -1 {
|
||||
t.Errorf("Size filter was expected to have 4 values")
|
||||
if strings.Index(sizeRepr, "1,2,3,444,5-90") == -1 {
|
||||
t.Errorf("Size filter was expected to have 5 values")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ func TestNewSizeFilterError(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFiltering(t *testing.T) {
|
||||
f, _ := NewSizeFilter("1,2,3,444")
|
||||
f, _ := NewSizeFilter("1,2,3,5-90,444")
|
||||
for i, test := range []struct {
|
||||
input int64
|
||||
output bool
|
||||
|
@ -32,6 +32,10 @@ func TestFiltering(t *testing.T) {
|
|||
{2, true},
|
||||
{3, true},
|
||||
{4, false},
|
||||
{5, true},
|
||||
{70, true},
|
||||
{90, true},
|
||||
{91, false},
|
||||
{444, true},
|
||||
} {
|
||||
resp := ffuf.Response{ContentLength: test.input}
|
||||
|
|
|
@ -8,33 +8,35 @@ import (
|
|||
"github.com/ffuf/ffuf/pkg/ffuf"
|
||||
)
|
||||
|
||||
const AllStatuses = 0
|
||||
|
||||
type StatusFilter struct {
|
||||
Value []int64
|
||||
Value []ffuf.ValueRange
|
||||
}
|
||||
|
||||
func NewStatusFilter(value string) (ffuf.FilterProvider, error) {
|
||||
var intvals []int64
|
||||
var intranges []ffuf.ValueRange
|
||||
for _, sv := range strings.Split(value, ",") {
|
||||
if sv == "all" {
|
||||
intvals = append(intvals, 0)
|
||||
intranges = append(intranges, ffuf.ValueRange{AllStatuses, AllStatuses})
|
||||
} else {
|
||||
intval, err := strconv.ParseInt(sv, 10, 0)
|
||||
vr, err := ffuf.ValueRangeFromString(sv)
|
||||
if err != nil {
|
||||
return &StatusFilter{}, fmt.Errorf("Status filter or matcher (-fc / -mc): invalid value %s", value)
|
||||
return &StatusFilter{}, fmt.Errorf("Status filter or matcher (-fc / -mc): invalid value %s", sv)
|
||||
}
|
||||
intvals = append(intvals, intval)
|
||||
intranges = append(intranges, vr)
|
||||
}
|
||||
}
|
||||
return &StatusFilter{Value: intvals}, nil
|
||||
return &StatusFilter{Value: intranges}, nil
|
||||
}
|
||||
|
||||
func (f *StatusFilter) Filter(response *ffuf.Response) (bool, error) {
|
||||
for _, iv := range f.Value {
|
||||
if iv == 0 {
|
||||
if iv.Min == AllStatuses && iv.Max == AllStatuses {
|
||||
// Handle the "all" case
|
||||
return true, nil
|
||||
}
|
||||
if iv == response.StatusCode {
|
||||
if iv.Min <= response.StatusCode && response.StatusCode <= iv.Max {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
@ -44,10 +46,12 @@ func (f *StatusFilter) Filter(response *ffuf.Response) (bool, error) {
|
|||
func (f *StatusFilter) Repr() string {
|
||||
var strval []string
|
||||
for _, iv := range f.Value {
|
||||
if iv == 0 {
|
||||
if iv.Min == AllStatuses && iv.Max == AllStatuses {
|
||||
strval = append(strval, "all")
|
||||
} else if iv.Min == iv.Max {
|
||||
strval = append(strval, strconv.Itoa(int(iv.Min)))
|
||||
} else {
|
||||
strval = append(strval, strconv.Itoa(int(iv)))
|
||||
strval = append(strval, strconv.Itoa(int(iv.Min))+"-"+strconv.Itoa(int(iv.Max)))
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Response status: %s", strings.Join(strval, ","))
|
||||
|
|
|
@ -8,10 +8,10 @@ import (
|
|||
)
|
||||
|
||||
func TestNewStatusFilter(t *testing.T) {
|
||||
f, _ := NewStatusFilter("200,301,500")
|
||||
f, _ := NewStatusFilter("200,301,400-410,500")
|
||||
statusRepr := f.Repr()
|
||||
if strings.Index(statusRepr, "200,301,500") == -1 {
|
||||
t.Errorf("Status filter was expected to have 3 values")
|
||||
if strings.Index(statusRepr, "200,301,400-410,500") == -1 {
|
||||
t.Errorf("Status filter was expected to have 4 values")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ func TestNewStatusFilterError(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStatusFiltering(t *testing.T) {
|
||||
f, _ := NewStatusFilter("200,301,500")
|
||||
f, _ := NewStatusFilter("200,301,400-498,500")
|
||||
for i, test := range []struct {
|
||||
input int64
|
||||
output bool
|
||||
|
@ -32,9 +32,12 @@ func TestStatusFiltering(t *testing.T) {
|
|||
{301, true},
|
||||
{500, true},
|
||||
{4, false},
|
||||
{444, false},
|
||||
{399, false},
|
||||
{400, true},
|
||||
{444, true},
|
||||
{498, true},
|
||||
{499, false},
|
||||
{302, false},
|
||||
{401, false},
|
||||
} {
|
||||
resp := ffuf.Response{StatusCode: test.input}
|
||||
filterReturn, _ := f.Filter(&resp)
|
||||
|
|
|
@ -9,25 +9,25 @@ import (
|
|||
)
|
||||
|
||||
type WordFilter struct {
|
||||
Value []int64
|
||||
Value []ffuf.ValueRange
|
||||
}
|
||||
|
||||
func NewWordFilter(value string) (ffuf.FilterProvider, error) {
|
||||
var intvals []int64
|
||||
var intranges []ffuf.ValueRange
|
||||
for _, sv := range strings.Split(value, ",") {
|
||||
intval, err := strconv.ParseInt(sv, 10, 0)
|
||||
vr, err := ffuf.ValueRangeFromString(sv)
|
||||
if err != nil {
|
||||
return &WordFilter{}, fmt.Errorf("Word filter or matcher (-fw / -mw): invalid value: %s", value)
|
||||
return &WordFilter{}, fmt.Errorf("Word filter or matcher (-fw / -mw): invalid value: %s", sv)
|
||||
}
|
||||
intvals = append(intvals, intval)
|
||||
intranges = append(intranges, vr)
|
||||
}
|
||||
return &WordFilter{Value: intvals}, nil
|
||||
return &WordFilter{Value: intranges}, nil
|
||||
}
|
||||
|
||||
func (f *WordFilter) Filter(response *ffuf.Response) (bool, error) {
|
||||
wordsSize := len(strings.Split(string(response.Data), " "))
|
||||
for _, iv := range f.Value {
|
||||
if iv == int64(wordsSize) {
|
||||
if iv.Min <= int64(wordsSize) && int64(wordsSize) <= iv.Max {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,11 @@ func (f *WordFilter) Filter(response *ffuf.Response) (bool, error) {
|
|||
func (f *WordFilter) Repr() string {
|
||||
var strval []string
|
||||
for _, iv := range f.Value {
|
||||
strval = append(strval, strconv.Itoa(int(iv)))
|
||||
if iv.Min == iv.Max {
|
||||
strval = append(strval, strconv.Itoa(int(iv.Min)))
|
||||
} else {
|
||||
strval = append(strval, strconv.Itoa(int(iv.Min))+"-"+strconv.Itoa(int(iv.Max)))
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Response words: %s", strings.Join(strval, ","))
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import (
|
|||
)
|
||||
|
||||
func TestNewWordFilter(t *testing.T) {
|
||||
f, _ := NewWordFilter("200,301,500")
|
||||
f, _ := NewWordFilter("200,301,400-410,500")
|
||||
wordsRepr := f.Repr()
|
||||
if strings.Index(wordsRepr, "200,301,500") == -1 {
|
||||
t.Errorf("Word filter was expected to have 3 values")
|
||||
if strings.Index(wordsRepr, "200,301,400-410,500") == -1 {
|
||||
t.Errorf("Word filter was expected to have 4 values")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ func TestNewWordFilterError(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWordFiltering(t *testing.T) {
|
||||
f, _ := NewWordFilter("200,301,500")
|
||||
f, _ := NewWordFilter("200,301,402-450,500")
|
||||
for i, test := range []struct {
|
||||
input int64
|
||||
output bool
|
||||
|
@ -32,9 +32,12 @@ func TestWordFiltering(t *testing.T) {
|
|||
{301, true},
|
||||
{500, true},
|
||||
{4, false},
|
||||
{444, false},
|
||||
{444, true},
|
||||
{302, false},
|
||||
{401, false},
|
||||
{402, true},
|
||||
{450, true},
|
||||
{451, false},
|
||||
} {
|
||||
var data []string
|
||||
for i := int64(0); i < test.input; i++ {
|
||||
|
|
Loading…
Reference in a new issue