Skip to content

Commit 231ef17

Browse files
authored
Add headless Chrome as an HTTP client to send requests (#246)
* Refactoring * Fix placeholders * Refactoring, add Chrome support as HTTP client * Fix README.md * Bump required golang version * Fix bugs * Fix chromedp logs * Fix bug with gRPC placeholder * Fix config for tests * Add integration tests for Chrome backend * Change default client back to Chrome * Fix vulnerable dependency * go mod tidy
1 parent 8a1958f commit 231ef17

File tree

1,673 files changed

+13370
-570914
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,673 files changed

+13370
-570914
lines changed

.gitignore

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.idea
22
.DS_store
3-
gotestwaf
4-
main
5-
reports/*
3+
/gotestwaf
4+
/main
5+
/reports/*
66
/modsec_stat_*.txt

Dockerfile

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
# syntax=docker/dockerfile:1
22

33
# Build Stage ==================================================================
4-
FROM golang:1.19-alpine AS build
4+
FROM golang:1.22-alpine AS build
55

66
RUN apk --no-cache add git
77

88
WORKDIR /app
9-
COPY . .
109

11-
RUN go build -o gotestwaf -ldflags "-X github.com/wallarm/gotestwaf/internal/version.Version=$(git describe --tags)" ./cmd/
10+
COPY ./go.mod ./go.sum ./
11+
RUN go mod download
12+
13+
COPY . .
14+
RUN go build -o gotestwaf \
15+
-ldflags "-X github.com/wallarm/gotestwaf/internal/version.Version=$(git describe --tags)" \
16+
./cmd/gotestwaf
1217

1318

1419
# Main Stage ===================================================================

Makefile

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ gotestwaf:
44
DOCKER_BUILDKIT=1 docker build --force-rm -t gotestwaf .
55

66
gotestwaf_bin:
7-
go build -o gotestwaf -ldflags "-X github.com/wallarm/gotestwaf/internal/version.Version=$(GOTESTWAF_VERSION)" ./cmd
7+
go build -o gotestwaf \
8+
-ldflags "-X github.com/wallarm/gotestwaf/internal/version.Version=$(GOTESTWAF_VERSION)" \
9+
./cmd/gotestwaf
810

911
modsec:
1012
docker pull mendhak/http-https-echo:31

README.md

+9-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# GoTestWAF [![Black Hat Arsenal USA 2022](https://img.shields.io/badge/Black%20Hat%20Arsenal-USA%202022-blue)](https://www.blackhat.com/us-22/arsenal/schedule/index.html#gotestwaf---well-known-open-source-waf-tester-now-supports-api-security-hacking-27986)
22

33
GoTestWAF is a tool for API and OWASP attack simulation that supports a wide range of API protocols including
4-
REST, GraphQL, gRPC, WebSockets, SOAP, XMLRPC, and others.
4+
REST, GraphQL, gRPC, SOAP, XMLRPC, and others.
55

66
It was designed to evaluate web application security solutions, such as API security proxies, Web Application Firewalls,
77
IPS, API gateways, and others.
@@ -26,7 +26,6 @@ The results of the security solution evaluation are recorded in the report file
2626
Default conditions for request generation are defined in the `testcases` folder in the YAML files of the following format:
2727

2828
```yaml
29-
---
3029
payload:
3130
- '"union select -7431.1, name, @aaa from u_base--w-'
3231
- "'or 123.22=123.22"
@@ -40,8 +39,7 @@ placeholder:
4039
- UrlParam
4140
- JSUnicode
4241
- Header
43-
type: "SQL Injection"
44-
...
42+
type: SQL Injection
4543
```
4644
4745
* `payload` is a malicious attack sample (e.g XSS payload like ```<script>alert(111)</script>``` or something more sophisticated).
@@ -70,10 +68,6 @@ Since the format of the YAML string is required for payloads, they must be [enco
7068
* XMLBody
7169
* URLParam
7270
* URLPath
73-
* NonCrudUrlPath
74-
* NonCrudUrlParam
75-
* NonCRUDHeader
76-
* NonCRUDRequestBody
7771
* RawRequest
7872

7973
The `RawRequest` placeholder will allow you to do an arbitrary HTTP request. The payload is substituted by replacing the string `{{payload}}` in the URL path, Headers or body. Fields of `RawRequest` placeholder:
@@ -90,7 +84,6 @@ Since the format of the YAML string is required for payloads, they must be [enco
9084
Example:
9185

9286
```yaml
93-
---
9487
payload:
9588
- test
9689
encoder:
@@ -113,8 +106,7 @@ Since the format of the YAML string is required for payloads, they must be [enco
113106
Knock knock.
114107
{{payload}}
115108
--boundary--
116-
type: "RawRequest test"
117-
...
109+
type: RawRequest test
118110
```
119111

120112
* `type` is a name of entire group of the payloads in file. It can be arbitrary, but should reflect the type of attacks in the file.
@@ -376,15 +368,16 @@ Options:
376368
--blockStatusCodes ints HTTP status code that WAF uses while blocking requests (default [403])
377369
--configPath string Path to the config file (default "config.yaml")
378370
--email string E-mail to which the report will be sent
379-
--followCookies If true, use cookies sent by the server. May work only with --maxIdleConns=1
371+
--followCookies If true, use cookies sent by the server. May work only with --maxIdleConns=1 (gohttp only)
380372
--grpcPort uint16 gRPC port to check
381-
--idleConnTimeout int The maximum amount of time a keep-alive connection will live (default 2)
373+
--httpClient string Which HTTP client use to send requests: chrome, gohttp (default "gohttp")
374+
--idleConnTimeout int The maximum amount of time a keep-alive connection will live (gohttp only) (default 2)
382375
--ignoreUnresolved If true, unresolved test cases will be considered as bypassed (affect score and results)
383376
--includePayloads If true, payloads will be included in HTML/PDF report
384377
--logFormat string Set logging format: text, json (default "text")
385378
--logLevel string Logging level: panic, fatal, error, warn, info, debug, trace (default "info")
386-
--maxIdleConns int The maximum number of keep-alive connections (default 2)
387-
--maxRedirects int The maximum number of handling redirects (default 50)
379+
--maxIdleConns int The maximum number of keep-alive connections (gohttp only) (default 2)
380+
--maxRedirects int The maximum number of handling redirects (gohttp only) (default 50)
388381
--noEmailReport Save report locally
389382
--nonBlockedAsPassed If true, count requests that weren't blocked as passed. If false, requests that don't satisfy to PassStatusCodes/PassRegExp as blocked
390383
--openapiFile string Path to openAPI file
@@ -393,7 +386,7 @@ Options:
393386
--proxy string Proxy URL to use
394387
--quiet If true, disable verbose logging
395388
--randomDelay int Random delay in ms in addition to the delay between requests (default 400)
396-
--renewSession Renew cookies before each test. Should be used with --followCookies flag
389+
--renewSession Renew cookies before each test. Should be used with --followCookies flag (gohttp only)
397390
--reportFormat string Export report to one of the following formats: none, pdf, html, json (default "pdf")
398391
--reportName string Report file name. Supports `time' package template format (default "waf-evaluation-report-2006-January-02-15-04-05")
399392
--reportPath string A directory to store reports (default "reports")
@@ -408,7 +401,6 @@ Options:
408401
--version Show GoTestWAF version and exit
409402
--wafName string Name of the WAF product (default "generic")
410403
--workers int The number of workers to scan (default 5)
411-
--wsURL string WebSocket URL to check
412404
```
413405
414406
The listed options can be passed to GoTestWAF as follows:

cmd/flags.go cmd/gotestwaf/flags.go

+42-34
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ const (
3434
jsonLogFormat = "json"
3535

3636
cliDescription = `GoTestWAF is a tool for API and OWASP attack simulation that supports a
37-
wide range of API protocols including REST, GraphQL, gRPC, WebSockets,
38-
SOAP, XMLRPC, and others.
37+
wide range of API protocols including REST, GraphQL, gRPC, SOAP, XMLRPC, and others.
3938
Homepage: https://github.com/wallarm/gotestwaf
4039
4140
Usage: %s [OPTIONS] --url <URL>
@@ -67,21 +66,44 @@ func parseFlags() (args []string, err error) {
6766

6867
flag.Usage = usage
6968

69+
// General parameters
7070
flag.StringVar(&configPath, "configPath", defaultConfigPath, "Path to the config file")
7171
flag.BoolVar(&quiet, "quiet", false, "If true, disable verbose logging")
7272
logLvl := flag.String("logLevel", "info", "Logging level: panic, fatal, error, warn, info, debug, trace")
7373
flag.StringVar(&logFormat, "logFormat", textLogFormat, "Set logging format: text, json")
74+
showVersion := flag.Bool("version", false, "Show GoTestWAF version and exit")
7475

76+
// Target settings
7577
urlParam := flag.String("url", "", "URL to check")
76-
wsURL := flag.String("wsURL", "", "WebSocket URL to check")
7778
flag.Uint16("grpcPort", 0, "gRPC port to check")
78-
flag.String("proxy", "", "Proxy URL to use")
79+
openapiFile := flag.String("openapiFile", "", "Path to openAPI file")
80+
81+
// Test cases settings
82+
flag.String("testCase", "", "If set then only this test case will be run")
83+
flag.String("testCasesPath", testCasesPath, "Path to a folder with test cases")
84+
flag.String("testSet", "", "If set then only this test set's cases will be run")
85+
86+
// HTTP client settings
87+
httpClient := flag.String("httpClient", "gohttp", "Which HTTP client use to send requests: chrome, gohttp")
7988
flag.Bool("tlsVerify", false, "If true, the received TLS certificate will be verified")
80-
flag.Int("maxIdleConns", 2, "The maximum number of keep-alive connections")
81-
flag.Int("maxRedirects", 50, "The maximum number of handling redirects")
82-
flag.Int("idleConnTimeout", 2, "The maximum amount of time a keep-alive connection will live")
83-
flag.Bool("followCookies", false, "If true, use cookies sent by the server. May work only with --maxIdleConns=1")
84-
flag.Bool("renewSession", false, "Renew cookies before each test. Should be used with --followCookies flag")
89+
flag.String("proxy", "", "Proxy URL to use")
90+
flag.String("addHeader", "", "An HTTP header to add to requests")
91+
flag.Bool("addDebugHeader", false, "Add header with a hash of the test information in each request")
92+
93+
// GoHTTP client only settings
94+
flag.Int("maxIdleConns", 2, "The maximum number of keep-alive connections (gohttp only)")
95+
flag.Int("maxRedirects", 50, "The maximum number of handling redirects (gohttp only)")
96+
flag.Int("idleConnTimeout", 2, "The maximum amount of time a keep-alive connection will live (gohttp only)")
97+
flag.Bool("followCookies", false, "If true, use cookies sent by the server. May work only with --maxIdleConns=1 (gohttp only)")
98+
flag.Bool("renewSession", false, "Renew cookies before each test. Should be used with --followCookies flag (gohttp only)")
99+
100+
// Performance settings
101+
flag.Int("workers", 5, "The number of workers to scan")
102+
flag.Int("sendDelay", 400, "Delay in ms between requests")
103+
flag.Int("randomDelay", 400, "Random delay in ms in addition to the delay between requests")
104+
105+
// Analysis settings
106+
flag.Bool("skipWAFBlockCheck", false, "If true, WAF detection tests will be skipped")
85107
flag.Bool("skipWAFIdentification", false, "Skip WAF identification")
86108
flag.IntSlice("blockStatusCodes", []int{403}, "HTTP status code that WAF uses while blocking requests")
87109
flag.IntSlice("passStatusCodes", []int{200, 404}, "HTTP response status code that WAF uses while passing requests")
@@ -91,26 +113,18 @@ func parseFlags() (args []string, err error) {
91113
"Regex to a detect normal (not blocked) web page with the same HTTP status code as a blocked request")
92114
flag.Bool("nonBlockedAsPassed", false,
93115
"If true, count requests that weren't blocked as passed. If false, requests that don't satisfy to PassStatusCodes/PassRegExp as blocked")
94-
flag.Int("workers", 5, "The number of workers to scan")
95-
flag.Int("sendDelay", 400, "Delay in ms between requests")
96-
flag.Int("randomDelay", 400, "Random delay in ms in addition to the delay between requests")
97-
flag.String("testCase", "", "If set then only this test case will be run")
98-
flag.String("testSet", "", "If set then only this test set's cases will be run")
116+
flag.Bool("ignoreUnresolved", false, "If true, unresolved test cases will be considered as bypassed (affect score and results)")
117+
flag.Bool("blockConnReset", false, "If true, connection resets will be considered as block")
118+
119+
// Report settings
120+
flag.String("wafName", wafName, "Name of the WAF product")
121+
flag.Bool("includePayloads", false, "If true, payloads will be included in HTML/PDF report")
99122
flag.String("reportPath", reportPath, "A directory to store reports")
100123
reportName := flag.String("reportName", defaultReportName, "Report file name. Supports `time' package template format")
101124
flag.String("reportFormat", "pdf", "Export report to one of the following formats: none, pdf, html, json")
102-
flag.Bool("includePayloads", false, "If true, payloads will be included in HTML/PDF report")
103125
noEmailReport := flag.Bool("noEmailReport", false, "Save report locally")
104126
email := flag.String("email", "", "E-mail to which the report will be sent")
105-
flag.String("testCasesPath", testCasesPath, "Path to a folder with test cases")
106-
flag.String("wafName", wafName, "Name of the WAF product")
107-
flag.Bool("ignoreUnresolved", false, "If true, unresolved test cases will be considered as bypassed (affect score and results)")
108-
flag.Bool("blockConnReset", false, "If true, connection resets will be considered as block")
109-
flag.Bool("skipWAFBlockCheck", false, "If true, WAF detection tests will be skipped")
110-
flag.String("addHeader", "", "An HTTP header to add to requests")
111-
flag.Bool("addDebugHeader", false, "Add header with a hash of the test information in each request")
112-
flag.String("openapiFile", "", "Path to openAPI file")
113-
showVersion := flag.Bool("version", false, "Show GoTestWAF version and exit")
127+
114128
flag.Parse()
115129

116130
if len(os.Args) == 1 {
@@ -165,16 +179,10 @@ func parseFlags() (args []string, err error) {
165179

166180
*urlParam = validURL.String()
167181

168-
// format WebSocket URL from given HTTP URL
169-
if *wsURL == "" {
170-
wsScheme := "ws"
171-
if validURL.Scheme == "https" {
172-
wsScheme = "wss"
173-
}
174-
validURL.Scheme = wsScheme
175-
validURL.Path = ""
176-
177-
*wsURL = validURL.String()
182+
// Force GoHTTP to be used as the HTTP client
183+
// when scanning against the OpenAPI spec.
184+
if openapiFile != nil && len(*openapiFile) > 0 {
185+
*httpClient = "gohttp"
178186
}
179187

180188
if *blockRegex != "" {

0 commit comments

Comments
 (0)