Skip to content

Commit

Permalink
merge: pull request #6 from PoCInnovation/connect-match-reqs to main
Browse files Browse the repository at this point in the history
Connect match reqs
  • Loading branch information
louonezime committed Sep 8, 2024
2 parents fec0b20 + be582a2 commit 25108c5
Show file tree
Hide file tree
Showing 14 changed files with 386 additions and 3 deletions.
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

NAME = bruteforce

SRC = src/main.go

all: $(NAME)
Expand All @@ -15,4 +16,8 @@ fclean:

re: fclean all

.PHONY: all clean fclean re
install_program:
echo "source $(pwd)/autocompletion/bash/_bruteforce" >> ~/.bashrc
echo "source $(pwd)/autocompletion/zsh/_bruteforce" >> ~/.zshrc

.PHONY: all clean fclean re install_program
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,27 @@ go run src/main.go

### Usage

No usage so far
```bash
./bruteforce [OPTIONS]
```

### Matching

For matching usage, the following flags are available:

`-status-codes` : match based on a list of status codes.

For example, `./bruteforce -status-codes="200,201,202,401,404"`.

*By default* : 200, 401, 403, 404, 429, 500

`-header` : match based on a header.

For example, `./bruteforce -header="Content-Type: application/json"`.

`-body` : match based on a body.

For example, `./bruteforce -body="Hello World"`.

## Get involved

Expand Down
28 changes: 28 additions & 0 deletions autocompletion/bash/_bruteforce
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash

_bruteforce_completion() {
local cur prev opts
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="--threads -v --status-codes --header --body --wordlist"

if [[ ${COMP_CWORD} -eq 1 ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
elif [[ ${COMP_CWORD} -eq 2 ]]; then
case "${prev}" in
--threads)
COMPREPLY=( $(compgen -W "1 2 4 8 16 32" -- "${cur}") )
;;
--status-codes)
COMPREPLY=( $(compgen -W "200 401 403 404 429 500" -- "${cur}") )
;;
--header|--body|--wordlist)
COMPREPLY=()
;;
esac
else
COMPREPLY=( $(compgen -W "http:// https://" -- "${cur}") )
fi
}

complete -F _bruteforce_completion bruteforce
23 changes: 23 additions & 0 deletions autocompletion/zsh/_bruteforce
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#compdef bruteforce

_bruteforce() {
local -a args

args=(
'-v[verbose mode]'
'--threads=[number of threads]:number of threads:(1 2 4 8 16 32)'
'--status-codes=[Comma-separated list of status codes to match]:status codes:'
'--header=[Header to match]:header:'
'--body=[String to match in response body]:body:'
'--wordlist=[Wordlist to bruteforce URLs with]:wordlist:_files'
'*:url:_bruteforce_urls'
)

_arguments -s $args
}

_bruteforce_urls() {
_urls -p 'http://' 'https://'
}

compdef _bruteforce bruteforce
51 changes: 51 additions & 0 deletions src/cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cli

import (
"bruteforce/src/matching"
"bruteforce/src/models"
"errors"
"flag"
"fmt"
"os"
)

func ParseCliArgs() (models.ForcingParams, error) {
var params models.ForcingParams

UrlError := errors.New("no url given")
ThreadsError := errors.New("wrong number of threads given")
WordListError := errors.New("no wordlist given")

forkptr := flag.Bool("v", false, "Verbose program")
statusPtr := flag.String("status-codes", "200,401,403,404,429,500", "Comma-separated list of status codes to match")
headerPtr := flag.String("header", "", "Header to match, formatted as \"key: value\"")
bodyPtr := flag.String("body", "", "String to match in response body")
wordlistPtr := flag.String("wordlist", "", "Wordlist to bruteforce url with")
flag.IntVar(&params.Workers, "threads", 1, "Number of threads to be used")

flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: bruteforce [options] --wordlist=[./path/to/wordlist] <url>\n")
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
}

flag.Parse()

if len(flag.Args()) < 1 {
return params, UrlError
}

params.Url = flag.Args()[0]
params.BoolFlags.Verbose = *forkptr
params.Wordlist = *wordlistPtr
params.Criteria = matcher.MatchParser(*statusPtr, *headerPtr, *bodyPtr)

if params.Workers < 1 {
return params, ThreadsError
}
if params.Wordlist == "" {
return params, WordListError
}

return params, nil
}
12 changes: 11 additions & 1 deletion src/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package main

import (
"bruteforce/src/cli"
"bruteforce/src/query"
"fmt"
)

func main() {
fmt.Println("Hello World")

forcingParams, err := cli.ParseCliArgs()

if err != nil {
panic(err)
}
fmt.Println(forcingParams)

query.MainRequest(&forcingParams)
}
14 changes: 14 additions & 0 deletions src/matching/body.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package matcher

import (
"bruteforce/src/models"
"errors"
"strings"
)

func matchContents(body []byte, criteria models.MatchCriteria) error {
if criteria.BodyContains != "" && !strings.Contains(string(body), criteria.BodyContains) {
return errors.New("body content mismatch")
}
return nil
}
37 changes: 37 additions & 0 deletions src/matching/headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package matcher

import (
"bruteforce/src/models"
"fmt"
"log"
"net/http"
"strings"
)

func matchHeaders(resp *http.Response, criteria models.MatchCriteria) error {
for key, value := range criteria.Headers {
if resp.Header.Get(key) != value {
return fmt.Errorf("header mismatch: %s=%s\nheaders: %s", key, value, resp.Header)
}
}
return nil
}

func parseHeaders(headersList string) map[string]string {
if headersList == "" {
return nil
}

headers := make(map[string]string)
headerPairs := strings.Split(headersList, ",")

for _, pair := range headerPairs {
parts := strings.SplitN(pair, ":", 2)
if len(parts) == 2 {
headers[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
} else {
log.Printf("[WARN] Invalid header format: %s", pair)
}
}
return headers
}
37 changes: 37 additions & 0 deletions src/matching/matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package matcher

import (
"bruteforce/src/models"
"log"
"net/http"
)

func MatchResponse(response *http.Response, body []byte, criteria models.MatchCriteria) error {
if err := matchStatusCode(response, criteria); err != nil {
return err
}
if err := matchHeaders(response, criteria); err != nil {
return err
}
if err := matchContents(body, criteria); err != nil {
return err
}

return nil
}

func MatchParser(statusPtr string, headerPtr string, bodyPtr string) models.MatchCriteria {
matchCodes, err := parseStatusCodes(statusPtr)
if err != nil {
log.Fatal("Error parsing status codes:", err)
}

matchHeaders := parseHeaders(headerPtr)
criteria := models.MatchCriteria{
StatusCodes: matchCodes,
Headers: matchHeaders,
BodyContains: bodyPtr,
}

return criteria
}
51 changes: 51 additions & 0 deletions src/matching/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package matcher

import (
"bruteforce/src/models"
"fmt"
"log"
"net/http"
"strings"
)

func matchStatusCode(resp *http.Response, criteria models.MatchCriteria) error {
isAll := false

if criteria.StatusCodes[0] == 0 {
isAll = !isAll
} else {
log.Printf("Matching status codes %d...", criteria.StatusCodes)
}
for _, code := range criteria.StatusCodes {
if resp.StatusCode == code || isAll {
return nil
}
}
return fmt.Errorf("status code is %d", resp.StatusCode)
}

func parseStatusCodes(statusCodeList string) ([]int, error) {
codeStrs := strings.Split(statusCodeList, ",")
if statusCodeList == "all" {
log.Println("Matching all status codes")
return []int{0}, nil
}

var codes []int
for _, codeStr := range codeStrs {
var code int
if _, err := fmt.Sscanf(codeStr, "%d", &code); err != nil {
return nil, err
}
if code < 600 && code >= 100 {
codes = append(codes, code)
} else {
log.Printf("[WARN] `%d` not considered, invalid status code.", code)
}
}

if len(codes) == 1 && codes[0] == 0 {
return nil, fmt.Errorf("no valid status code given")
}
return codes, nil
}
19 changes: 19 additions & 0 deletions src/models/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package models

type boolflags struct {
Verbose bool
}

type MatchCriteria struct {
StatusCodes []int
Headers map[string]string
BodyContains string
}

type ForcingParams struct {
Workers int
Url string
Wordlist string
BoolFlags boolflags
Criteria MatchCriteria
}
32 changes: 32 additions & 0 deletions src/query/callWorker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package query

import (
"bruteforce/src/models"
"bruteforce/src/utils"
"sync"
)

func executeQueryFromFile(wg *sync.WaitGroup, params *models.ForcingParams, currentPath chan string) {
defer wg.Done()
for taskData := range currentPath {
QueryExecute(params, taskData, "GET")
}
}

func MainRequest(params *models.ForcingParams) {
wg := &sync.WaitGroup{}
wg.Add(params.Workers)
channel := make(chan string)
wordArray := utils.GetFileContent(params.Wordlist)

for i := 0; i < params.Workers; i++ {
go executeQueryFromFile(wg, params, channel)
}

for i := 0; i < len(wordArray); i++ {
channel <- wordArray[i]
}

close(channel)
wg.Wait()
}
Loading

0 comments on commit 25108c5

Please sign in to comment.