Skip to content

Commit

Permalink
Added raw request parsing option (#137)
Browse files Browse the repository at this point in the history
* Added request body option

* Update CHANGELOG.md

* Update CONTRIBUTORS.md

* Removed typo

* Fixed the URL in path issue

* Misc changes to align to codebase
  • Loading branch information
Ice3man543 authored and joohoi committed Jan 15, 2020
1 parent ac2b447 commit 01e5169
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Expand Up @@ -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 changes: 81 additions & 2 deletions main.go
@@ -1,6 +1,7 @@
package main

import (
"bufio"
"context"
"flag"
"fmt"
Expand Down Expand Up @@ -32,6 +33,8 @@ type cliOptions struct {
matcherWords string
matcherLines string
proxyURL string
request string
requestProto string
outputFormat string
wordlists multiStringFlag
inputcommands multiStringFlag
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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, ",")
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 01e5169

Please sign in to comment.