Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for using ranges in size, word count or status code matching/filtering #47

Merged
merged 5 commits into from Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions main.go
Expand Up @@ -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)")
Expand Down
21 changes: 13 additions & 8 deletions pkg/filter/size.go
Expand Up @@ -9,24 +9,25 @@ import (
)

type SizeFilter struct {
Value []int64
Value []ValueRange
}

func NewSizeFilter(value string) (ffuf.FilterProvider, error) {
var intvals []int64
var intranges []ValueRange
for _, sv := range strings.Split(value, ",") {
intval, err := strconv.ParseInt(sv, 10, 0)
vr, err := 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
}
}
Expand All @@ -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, ","))
}
12 changes: 8 additions & 4 deletions pkg/filter/size_test.go
Expand Up @@ -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")
}
}

Expand All @@ -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
Expand All @@ -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}
Expand Down
26 changes: 15 additions & 11 deletions pkg/filter/status.go
Expand Up @@ -8,33 +8,35 @@ import (
"github.com/ffuf/ffuf/pkg/ffuf"
)

const AllStatuses = 0

type StatusFilter struct {
Value []int64
Value []ValueRange
}

func NewStatusFilter(value string) (ffuf.FilterProvider, error) {
var intvals []int64
var intranges []ValueRange
for _, sv := range strings.Split(value, ",") {
if sv == "all" {
intvals = append(intvals, 0)
intranges = append(intranges, ValueRange{AllStatuses, AllStatuses})
} else {
intval, err := strconv.ParseInt(sv, 10, 0)
vr, err := 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
}
}
Expand All @@ -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, ","))
Expand Down
15 changes: 9 additions & 6 deletions pkg/filter/status_test.go
Expand Up @@ -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")
}
}

Expand All @@ -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
Expand All @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions pkg/filter/valuerange.go
@@ -0,0 +1,38 @@
package filter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to see this moved to the main library: pkg/ffuf, this way it could be used from outside of the filters as well in the future. WIth the current implementation trying to reference it from the main library would cause a cyclic dependency import.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did things.


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
}
}
20 changes: 12 additions & 8 deletions pkg/filter/words.go
Expand Up @@ -9,25 +9,25 @@ import (
)

type WordFilter struct {
Value []int64
Value []ValueRange
}

func NewWordFilter(value string) (ffuf.FilterProvider, error) {
var intvals []int64
var intranges []ValueRange
for _, sv := range strings.Split(value, ",") {
intval, err := strconv.ParseInt(sv, 10, 0)
vr, err := 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
}
}
Expand All @@ -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, ","))
}
13 changes: 8 additions & 5 deletions pkg/filter/words_test.go
Expand Up @@ -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")
}
}

Expand All @@ -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
Expand All @@ -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++ {
Expand Down