Skip to content

Commit

Permalink
More improvements and additions before release
Browse files Browse the repository at this point in the history
  • Loading branch information
ameenmaali committed Mar 19, 2020
1 parent 75f0ae6 commit 7e9b45e
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 71 deletions.
12 changes: 6 additions & 6 deletions config-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ rules:
CallbackFuzz:
description: Test for open redirects and potential SSRFs by checking for certain responses or callbacks to your server
injections:
- https://rzu97r6tmyzmgth4lgeb265ylprgf5.burpcollaborator.net
- //rzu97r6tmyzmgth4lgeb265ylprgf5.burpcollaborator.net
- https://swiftype.com.rzu97r6tmyzmgth4lgeb265ylprgf5.burpcollaborator.net
- https://jl81tjsl8qle2l3w7803oyrq7hda1z.burpcollaborator.net
- //jl81tjsl8qle2l3w7803oyrq7hda1z.burpcollaborator.net
- https://n33ma.com.jl81tjsl8qle2l3w7803oyrq7hda1z.burpcollaborator.net
expectation:
responseContents:
- a0aru8x5g9zt3w4lngnvk8zjjgza0aru8x5g9zt3w4lngnvk8zjjgz
- a0aru8x5g9zt3w4lngnvk8zjlgz
SqlInjectionCheck:
description: Test for potential SQL injections by injecting characters to break SQL statements
injections:
Expand All @@ -19,9 +19,9 @@ rules:
XssDetection:
description: Test for XSS by discovering potentially unsanitized/encoded input in responses
injections:
- '"><asd>'
- '"><h2>asd</h2>'
expectation:
responseContents:
- '"><asd>'
- '"><h2>asd</h2>'
responseHeaders:
Content-Type: html
10 changes: 8 additions & 2 deletions httpUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,16 @@ func sendRequest(u string) (Response, error) {
return response, err
}

// temporary. Needs to be an option
request.Header.Add("User-Agent", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36")

// Add headers passed in as arguments
for header, value := range config.Headers {
request.Header.Add(header, value)
}

// Add cookies passed in as arguments
request.Header.Add("Cookie", config.Cookies)

resp, err := httpClient.Do(request)
if err != nil {
return response, err
Expand All @@ -54,4 +61,3 @@ func sendRequest(u string) (Response, error) {

return response, err
}

145 changes: 99 additions & 46 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,25 @@ import (
"github.com/fatih/color"
"github.com/spf13/viper"
"net/http"
"net/url"
"os"
"strings"
"sync"
"time"
)

type cliOptions struct {
configFilename string
cookies string
headers string
verbose bool
type CliOptions struct {
ConfigFile string
Cookies string
Headers string
Verbose bool
Concurrency int
DecodedParams bool
SilentMode bool
}

type Config struct {
Rules map[string]Rule `mapstructure:"rules"`
Rules map[string]Rule `mapstructure:"rules"`
Cookies string
Headers map[string]string
}
Expand All @@ -31,31 +36,39 @@ type Rule struct {
}

type ExpectedResponse struct {
Contents []string `mapstructure:"responseContents"`
Codes []int `mapstructure:"responseCodes"`
Contents []string `mapstructure:"responseContents"`
Codes []int `mapstructure:"responseCodes"`
Headers map[string]string `mapstructure:"responseHeaders"`
}

type Response struct {
StatusCode int
Body string
Body string
Headers http.Header
}

type settings struct {
SupportEnabled bool
ThirdPartyCrawlEnabled bool
CrawlTimeThreshold int
}

type RuleEvaluation struct {
ChecksMatched int
ChecksMatched int
SuccessMessage string
Successful bool
Successful bool
}

type EvaluationResult struct {
RuleName string
RuleDescription string
InjectedUrl string
}

type TaskData struct {
InjectedUrl string
RuleData Rule
RuleName string
}

var requestsSent int
var config Config
var Settings settings
var opts CliOptions
var evaluationResults []EvaluationResult

func loadConfig(configFile string) error {
// In order to ensure dots (.) are not considered as delimiters, set delimiter
Expand All @@ -77,7 +90,7 @@ func loadConfig(configFile string) error {
return nil
}

func runEvaluations(resp Response, ruleData Rule, injectedUrl string, ruleName string) RuleEvaluation {
func runEvaluation(resp Response, ruleData Rule, injectedUrl string, ruleName string) RuleEvaluation {
headersExpected := false
bodyExpected := false
codeExpected := false
Expand Down Expand Up @@ -127,28 +140,30 @@ func runEvaluations(resp Response, ruleData Rule, injectedUrl string, ruleName s

if ruleEvaluation.ChecksMatched >= numOfChecks {
ruleEvaluation.Successful = true
ruleEvaluation.SuccessMessage = fmt.Sprintf("[%v] successful match for %v\n", ruleName, injectedUrl)
u, err := url.QueryUnescape(injectedUrl)
if err != nil {
u = injectedUrl
}
ruleEvaluation.SuccessMessage = fmt.Sprintf("[%v] successful match for %v\n", ruleName, u)
evaluationResults = append(evaluationResults, EvaluationResult{RuleName: ruleName, RuleDescription: ruleData.Description, InjectedUrl: injectedUrl})
}

return ruleEvaluation
}


func main() {
printGreen := color.New(color.FgGreen).PrintfFunc()
printRed := color.New(color.FgRed).PrintfFunc()

opts := cliOptions{}

err := VerifyFlags(&opts)
if err != nil {
fmt.Println(err)
flag.Usage()
os.Exit(1)
}

if err := loadConfig(opts.configFilename); err != nil {
fmt.Println("Failed loading settings:", err)
if err := loadConfig(opts.ConfigFile); err != nil {
fmt.Println("Failed loading config:", err)
os.Exit(1)
}

Expand All @@ -158,40 +173,78 @@ func main() {
os.Exit(1)
}

if !opts.SilentMode {
fmt.Fprintf(os.Stderr, "There are %v unique URL/Query String combinations. Time to inject each query string, 1 at a time!\n", len(urls))
}

tasks := make(chan TaskData)

var wg sync.WaitGroup
for _, u := range urls {

startTime := time.Now()

for i := 0; i < opts.Concurrency; i++ {
wg.Add(1)
go func(site string) {
go func() {
defer wg.Done()
for rule, ruleData := range config.Rules {
injectedUrls, err := getInjectedUrls(site, ruleData.Injections)
if err != nil {
if opts.verbose {
printRed("[%v] error parsing URL or query parameters for\n", rule)
}
continue

for {
task, ok := <- tasks
// Return if tasks are complete
if !ok {
return
}
if injectedUrls == nil {

resp, err := sendRequest(task.InjectedUrl)
if err != nil {
continue
}
//fmt.Println(task.InjectedUrl)

for _, injectedUrl := range injectedUrls {
resp, err := sendRequest(injectedUrl)
if err != nil {
if opts.verbose {
printRed("error sending HTTP request (%v)\n", injectedUrl)
}
continue
requestsSent += 1

// Send an update every 1,000 requests
if !opts.SilentMode {
if requestsSent % 1000 == 0 {
secondsElapsed := time.Since(startTime).Seconds()
fmt.Fprintf(os.Stderr, "%v requests sent: %v requests per second\n", requestsSent, int(float64(requestsSent) / secondsElapsed))
}
}

ruleEvaluation := runEvaluations(resp, ruleData, injectedUrl, rule)
if ruleEvaluation.Successful {
printGreen(ruleEvaluation.SuccessMessage)
if err != nil {
if opts.Verbose {
printRed("error sending HTTP request (%v)\n", task.InjectedUrl)
}
continue
}

ruleEvaluation := runEvaluation(resp, task.RuleData, task.InjectedUrl, task.RuleName)
if ruleEvaluation.Successful {
printGreen(ruleEvaluation.SuccessMessage)
}
}
}()
}

for _, u := range urls {
for rule, ruleData := range config.Rules {
injectedUrls, err := getInjectedUrls(u, ruleData.Injections)
if err != nil {
if opts.Verbose {
printRed("[%v] error parsing URL or query parameters for\n", rule)
}
continue
}
if injectedUrls == nil {
continue
}
}(u)

for _, injectedUrl := range injectedUrls {
tasks <- TaskData{RuleName: rule, RuleData: ruleData, InjectedUrl: injectedUrl}
}
}
}

close(tasks)
wg.Wait()
}
48 changes: 31 additions & 17 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,39 @@ import (
"strings"
)

func VerifyFlags(options *cliOptions) error {
flag.StringVar(&options.configFilename, "c", "", "File path to config file, which contains fuzz rules")
flag.StringVar(&options.configFilename, "config", "", "File path to config file, which contains fuzz rules")
func VerifyFlags(options *CliOptions) error {
flag.StringVar(&options.ConfigFile, "c", "", "File path to config file, which contains fuzz rules")
flag.StringVar(&options.ConfigFile, "config", "", "File path to config file, which contains fuzz rules")

flag.StringVar(&options.cookies, "cookies", "", "Cookies to add in all requests")
flag.StringVar(&options.Cookies, "cookies", "", "Cookies to add in all requests")

flag.StringVar(&options.headers, "H", "", "Headers to add in all requests. Multiple should be separated by semi-colon")
flag.StringVar(&options.headers, "headers", "", "Headers to add in all requests. Multiple should be separated by semi-colon")
flag.StringVar(&options.Headers, "H", "", "Headers to add in all requests. Multiple should be separated by semi-colon")
flag.StringVar(&options.Headers, "headers", "", "Headers to add in all requests. Multiple should be separated by semi-colon")

flag.BoolVar(&options.verbose, "v", false, "Verbose mode to print more info for failed/malformed URLs or requests")
flag.BoolVar(&options.verbose, "verbose", false, "Verbose mode to print more info for failed/malformed URLs or requests")
flag.BoolVar(&options.Verbose, "debug", false, "Debug/verbose mode to print more info for failed/malformed URLs or requests")

flag.BoolVar(&options.SilentMode, "s", false, "Only print successful evaluations (i.e. mute status updates). Note these updates print to stderr, and won't be saved if saving stdout to files")
flag.BoolVar(&options.SilentMode, "silent", false, "Only print successful evaluations (i.e. mute status updates). Note these updates print to stderr, and won't be saved if saving stdout to files")

flag.BoolVar(&options.DecodedParams, "d", false, "Send requests with decoded query strings/parameters (this could cause many errors/bad requests)")
flag.BoolVar(&options.DecodedParams, "decode", false, "Send requests with decoded query strings/parameters (this could cause many errors/bad requests)")

flag.IntVar(&options.Concurrency, "w", 25, "Set the concurrency/worker count")
flag.IntVar(&options.Concurrency, "workers", 25, "Set the concurrency/worker count")

flag.Parse()

if options.configFilename == "" {
if options.ConfigFile == "" {
return errors.New("config file flag is required")
}

if options.cookies != "" {
config.Cookies = options.cookies
if options.Cookies != "" {
config.Cookies = options.Cookies
}

if options.headers != "" {
if options.Headers != "" {
headers := make(map[string]string)
rawHeaders := strings.Split(options.headers, ";")
rawHeaders := strings.Split(options.Headers, ";")
for _, header := range rawHeaders {
var parts []string
if strings.Contains(header, ": ") {
Expand Down Expand Up @@ -64,9 +72,7 @@ func GetUrlsFromFile() ([]string, error) {
continue
}

// Go's maps aren't ordered, but we want to use all the param names
// as part of the key to output only unique requests. To do that, put
// them into a slice and then sort it.
// Use query string keys when sorting in order to get unique URL & Query String combinations
params := make([]string, 0)
for param, _ := range u.Query() {
params = append(params, param)
Expand Down Expand Up @@ -113,7 +119,11 @@ func getInjectedUrls(fullUrl string, ruleInjections []string) ([]string, error)
continue
}

u.RawQuery = decodedQs
if opts.DecodedParams {
u.RawQuery = decodedQs
} else {
u.RawQuery = queryStrings.Encode()
}

replacedUrls = append(replacedUrls, u.String())

Expand All @@ -124,3 +134,7 @@ func getInjectedUrls(fullUrl string, ruleInjections []string) ([]string, error)
}
return replacedUrls, nil
}

func toHtml() {

}

0 comments on commit 7e9b45e

Please sign in to comment.