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:
parent
ac2b447dfd
commit
01e516988d
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
- master
|
- master
|
||||||
- New
|
- 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 `-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 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.
|
- 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.
|
||||||
|
|||||||
@ -15,3 +15,4 @@
|
|||||||
* [seblw](https://github.com/seblw)
|
* [seblw](https://github.com/seblw)
|
||||||
* [SolomonSklash](https://github.com/SolomonSklash)
|
* [SolomonSklash](https://github.com/SolomonSklash)
|
||||||
* [Shaked](https://github.com/Shaked)
|
* [Shaked](https://github.com/Shaked)
|
||||||
|
* [Ice3man543](https://github.com/Ice3man543)
|
||||||
|
|||||||
83
main.go
83
main.go
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -32,6 +33,8 @@ type cliOptions struct {
|
|||||||
matcherWords string
|
matcherWords string
|
||||||
matcherLines string
|
matcherLines string
|
||||||
proxyURL string
|
proxyURL string
|
||||||
|
request string
|
||||||
|
requestProto string
|
||||||
outputFormat string
|
outputFormat string
|
||||||
wordlists multiStringFlag
|
wordlists multiStringFlag
|
||||||
inputcommands multiStringFlag
|
inputcommands multiStringFlag
|
||||||
@ -89,6 +92,8 @@ func main() {
|
|||||||
flag.StringVar(&opts.matcherWords, "mw", "", "Match amount of words in response")
|
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.matcherLines, "ml", "", "Match amount of lines in response")
|
||||||
flag.StringVar(&opts.proxyURL, "x", "", "HTTP Proxy URL")
|
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.Method, "X", "GET", "HTTP method to use")
|
||||||
flag.StringVar(&conf.OutputFile, "o", "", "Write output to file")
|
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")
|
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 err error
|
||||||
var err2 error
|
var err2 error
|
||||||
if len(conf.Url) == 0 {
|
if len(conf.Url) == 0 && parseOpts.request == "" {
|
||||||
errs.Add(fmt.Errorf("-u flag is required"))
|
errs.Add(fmt.Errorf("-u flag or -request flag is required"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare extensions
|
// prepare extensions
|
||||||
if parseOpts.extensions != "" {
|
if parseOpts.extensions != "" {
|
||||||
extensions := strings.Split(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"))
|
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
|
//Prepare headers
|
||||||
for _, v := range parseOpts.headers {
|
for _, v := range parseOpts.headers {
|
||||||
hs := strings.SplitN(v, ":", 2)
|
hs := strings.SplitN(v, ":", 2)
|
||||||
@ -385,6 +400,70 @@ func prepareConfig(parseOpts *cliOptions, conf *ffuf.Config) error {
|
|||||||
return errs.ErrorOrNil()
|
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 {
|
func keywordPresent(keyword string, conf *ffuf.Config) bool {
|
||||||
//Search for keyword from HTTP method, URL and POST data too
|
//Search for keyword from HTTP method, URL and POST data too
|
||||||
if strings.Index(conf.Method, keyword) != -1 {
|
if strings.Index(conf.Method, keyword) != -1 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user