Added raw request parsing option (#137)

* Added request body option

* Update CHANGELOG.md

* Update CONTRIBUTORS.md

* Removed typo

* Fixed the URL in path issue

* Misc changes to align to codebase
This commit is contained in:
Ice3man 2020-01-15 01:08:24 -08:00 committed by Joona Hoikkala
parent ac2b447dfd
commit 01e516988d
3 changed files with 83 additions and 2 deletions

View file

@ -2,6 +2,7 @@
- master
- New
- New CLI flags `-request` to specify the raw request file to build the actual request from and `-request-proto` to define the new request format.
- New CLI flag `-od` (output directory) to enable writing requests and responses for matched results to a file for postprocessing or debugging purposes.
- New CLI flag `-maxtime` to limit the running time of ffuf
- New CLI flags `-recursion` and `-recursion-depth` to control recursive ffuf jobs if directories are found. This requires the `-u` to end with FUZZ keyword.

View file

@ -15,3 +15,4 @@
* [seblw](https://github.com/seblw)
* [SolomonSklash](https://github.com/SolomonSklash)
* [Shaked](https://github.com/Shaked)
* [Ice3man543](https://github.com/Ice3man543)

83
main.go
View file

@ -1,6 +1,7 @@
package main
import (
"bufio"
"context"
"flag"
"fmt"
@ -32,6 +33,8 @@ type cliOptions struct {
matcherWords string
matcherLines string
proxyURL string
request string
requestProto string
outputFormat string
wordlists multiStringFlag
inputcommands multiStringFlag
@ -89,6 +92,8 @@ func main() {
flag.StringVar(&opts.matcherWords, "mw", "", "Match amount of words in response")
flag.StringVar(&opts.matcherLines, "ml", "", "Match amount of lines in response")
flag.StringVar(&opts.proxyURL, "x", "", "HTTP Proxy URL")
flag.StringVar(&opts.request, "request", "", "File containing the raw http request")
flag.StringVar(&opts.requestProto, "request-proto", "https", "Protocol to use along with raw request")
flag.StringVar(&conf.Method, "X", "GET", "HTTP method to use")
flag.StringVar(&conf.OutputFile, "o", "", "Write output to file")
flag.StringVar(&opts.outputFormat, "of", "json", "Output file format. Available formats: json, ejson, html, md, csv, ecsv")
@ -239,9 +244,10 @@ func prepareConfig(parseOpts *cliOptions, conf *ffuf.Config) error {
var err error
var err2 error
if len(conf.Url) == 0 {
errs.Add(fmt.Errorf("-u flag is required"))
if len(conf.Url) == 0 && parseOpts.request == "" {
errs.Add(fmt.Errorf("-u flag or -request flag is required"))
}
// prepare extensions
if parseOpts.extensions != "" {
extensions := strings.Split(parseOpts.extensions, ",")
@ -293,6 +299,15 @@ func prepareConfig(parseOpts *cliOptions, conf *ffuf.Config) error {
errs.Add(fmt.Errorf("Either -w or --input-cmd flag is required"))
}
// Prepare the request using body
if parseOpts.request != "" {
err := parseRawRequest(parseOpts, conf)
if err != nil {
errmsg := fmt.Sprintf("Could not parse raw request: %s", err)
errs.Add(fmt.Errorf(errmsg))
}
}
//Prepare headers
for _, v := range parseOpts.headers {
hs := strings.SplitN(v, ":", 2)
@ -385,6 +400,70 @@ func prepareConfig(parseOpts *cliOptions, conf *ffuf.Config) error {
return errs.ErrorOrNil()
}
func parseRawRequest(parseOpts *cliOptions, conf *ffuf.Config) error {
file, err := os.Open(parseOpts.request)
if err != nil {
return fmt.Errorf("could not open request file: %s", err)
}
defer file.Close()
r := bufio.NewReader(file)
s, err := r.ReadString('\n')
if err != nil {
return fmt.Errorf("could not read request: %s", err)
}
parts := strings.Split(s, " ")
if len(parts) < 3 {
return fmt.Errorf("malformed request supplied")
}
// Set the request Method
conf.Method = parts[0]
for {
line, err := r.ReadString('\n')
line = strings.TrimSpace(line)
if err != nil || line == "" {
break
}
p := strings.SplitN(line, ":", 2)
if len(p) != 2 {
continue
}
if strings.EqualFold(p[0], "content-length") {
continue
}
conf.Headers[strings.TrimSpace(p[0])] = strings.TrimSpace(p[1])
}
// Handle case with the full http url in path. In that case,
// ignore any host header that we encounter and use the path as request URL
if strings.HasPrefix(parts[1], "http") {
parsed, err := url.Parse(parts[1])
if err != nil {
return fmt.Errorf("could not parse request URL: %s", err)
}
conf.Url = parts[1]
conf.Headers["Host"] = parsed.Host
} else {
// Build the request URL from the request
conf.Url = parseOpts.requestProto + "://" + conf.Headers["Host"] + parts[1]
}
// Set the request body
b, err := ioutil.ReadAll(r)
if err != nil {
return fmt.Errorf("could not read request body: %s", err)
}
conf.Data = string(b)
return nil
}
func keywordPresent(keyword string, conf *ffuf.Config) bool {
//Search for keyword from HTTP method, URL and POST data too
if strings.Index(conf.Method, keyword) != -1 {