forked from wallarm/gotestwaf
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d273d2e
commit b17276d
Showing
11 changed files
with
331 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,36 @@ | ||
package config | ||
|
||
type Config struct { | ||
URL string `mapstructure:"url"` | ||
WebSocketURL string `mapstructure:"wsURL"` | ||
GRPCPort uint16 `mapstructure:"grpcPort"` | ||
HTTPHeaders map[string]string `mapstructure:"headers"` | ||
TLSVerify bool `mapstructure:"tlsVerify"` | ||
Proxy string `mapstructure:"proxy"` | ||
MaxIdleConns int `mapstructure:"maxIdleConns"` | ||
MaxRedirects int `mapstructure:"maxRedirects"` | ||
IdleConnTimeout int `mapstructure:"idleConnTimeout"` | ||
FollowCookies bool `mapstructure:"followCookies"` | ||
RenewSession bool `mapstructure:"renewSession"` | ||
BlockStatusCode int `mapstructure:"blockStatusCode"` | ||
PassStatusCode []int `mapstructure:"passStatusCode"` | ||
BlockRegex string `mapstructure:"blockRegex"` | ||
PassRegex string `mapstructure:"passRegex"` | ||
NonBlockedAsPassed bool `mapstructure:"nonBlockedAsPassed"` | ||
Workers int `mapstructure:"workers"` | ||
RandomDelay int `mapstructure:"randomDelay"` | ||
SendDelay int `mapstructure:"sendDelay"` | ||
ReportPath string `mapstructure:"reportPath"` | ||
ReportName string `mapstructure:"reportName"` | ||
ReportFormat string `mapstructure:"reportFormat"` | ||
TestCase string `mapstructure:"testCase"` | ||
TestCasesPath string `mapstructure:"testCasesPath"` | ||
TestSet string `mapstructure:"testSet"` | ||
WAFName string `mapstructure:"wafName"` | ||
IgnoreUnresolved bool `mapstructure:"ignoreUnresolved"` | ||
BlockConnReset bool `mapstructure:"blockConnReset"` | ||
SkipWAFBlockCheck bool `mapstructure:"skipWAFBlockCheck"` | ||
AddHeader string `mapstructure:"addHeader"` | ||
OpenAPIFile string `mapstructure:"openapiFile"` | ||
URL string `mapstructure:"url"` | ||
WebSocketURL string `mapstructure:"wsURL"` | ||
GRPCPort uint16 `mapstructure:"grpcPort"` | ||
HTTPHeaders map[string]string `mapstructure:"headers"` | ||
TLSVerify bool `mapstructure:"tlsVerify"` | ||
Proxy string `mapstructure:"proxy"` | ||
MaxIdleConns int `mapstructure:"maxIdleConns"` | ||
MaxRedirects int `mapstructure:"maxRedirects"` | ||
IdleConnTimeout int `mapstructure:"idleConnTimeout"` | ||
FollowCookies bool `mapstructure:"followCookies"` | ||
RenewSession bool `mapstructure:"renewSession"` | ||
DisableWafIdentification bool `mapstructure:"disableWafIdentification"` | ||
BlockStatusCode int `mapstructure:"blockStatusCode"` | ||
PassStatusCode []int `mapstructure:"passStatusCode"` | ||
BlockRegex string `mapstructure:"blockRegex"` | ||
PassRegex string `mapstructure:"passRegex"` | ||
NonBlockedAsPassed bool `mapstructure:"nonBlockedAsPassed"` | ||
Workers int `mapstructure:"workers"` | ||
RandomDelay int `mapstructure:"randomDelay"` | ||
SendDelay int `mapstructure:"sendDelay"` | ||
ReportPath string `mapstructure:"reportPath"` | ||
ReportName string `mapstructure:"reportName"` | ||
ReportFormat string `mapstructure:"reportFormat"` | ||
TestCase string `mapstructure:"testCase"` | ||
TestCasesPath string `mapstructure:"testCasesPath"` | ||
TestSet string `mapstructure:"testSet"` | ||
WAFName string `mapstructure:"wafName"` | ||
IgnoreUnresolved bool `mapstructure:"ignoreUnresolved"` | ||
BlockConnReset bool `mapstructure:"blockConnReset"` | ||
SkipWAFBlockCheck bool `mapstructure:"skipWAFBlockCheck"` | ||
AddHeader string `mapstructure:"addHeader"` | ||
OpenAPIFile string `mapstructure:"openapiFile"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package scanner | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
|
||
"github.com/pkg/errors" | ||
|
||
"github.com/wallarm/gotestwaf/internal/config" | ||
"github.com/wallarm/gotestwaf/internal/scanner/detectors" | ||
) | ||
|
||
const ( | ||
xssPayload = `<script>alert("XSS");</script>` | ||
sqliPayload = `UNION SELECT ALL FROM information_schema AND ' or SLEEP(5) or '` | ||
lfiPayload = `../../../../etc/passwd` | ||
rcePayload = `/bin/cat /etc/passwd; ping 127.0.0.1; curl google.com` | ||
xxePayload = `<!ENTITY xxe SYSTEM "file:///etc/shadow">]><pwn>&hack;</pwn>` | ||
) | ||
|
||
type WAFDetector struct { | ||
client *http.Client | ||
target string | ||
} | ||
|
||
func NewDetector(cfg *config.Config) (*WAFDetector, error) { | ||
tr := &http.Transport{ | ||
TLSClientConfig: &tls.Config{InsecureSkipVerify: !cfg.TLSVerify}, | ||
IdleConnTimeout: time.Duration(cfg.IdleConnTimeout) * time.Second, | ||
MaxIdleConns: cfg.MaxIdleConns, | ||
MaxIdleConnsPerHost: cfg.MaxIdleConns, // net.http hardcodes DefaultMaxIdleConnsPerHost to 2! | ||
} | ||
|
||
if cfg.Proxy != "" { | ||
proxyURL, err := url.Parse(cfg.Proxy) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "couldn't parse proxy URL") | ||
} | ||
|
||
tr.Proxy = http.ProxyURL(proxyURL) | ||
} | ||
|
||
client := &http.Client{ | ||
Transport: tr, | ||
CheckRedirect: func(req *http.Request, via []*http.Request) error { | ||
return http.ErrUseLastResponse | ||
}, | ||
} | ||
|
||
target, err := url.Parse(cfg.URL) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "couldn't parse URL") | ||
} | ||
|
||
return &WAFDetector{ | ||
client: client, | ||
target: GetTargetURL(target), | ||
}, nil | ||
} | ||
|
||
// doRequest sends HTTP-request with malicious payload to trigger WAF. | ||
func (w *WAFDetector) doRequest(ctx context.Context) (*http.Response, error) { | ||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, w.target, nil) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "couldn't create request") | ||
} | ||
|
||
queryParams := req.URL.Query() | ||
queryParams.Add("a", xssPayload) | ||
queryParams.Add("b", sqliPayload) | ||
queryParams.Add("c", lfiPayload) | ||
queryParams.Add("d", rcePayload) | ||
queryParams.Add("d", xxePayload) | ||
|
||
req.URL.RawQuery = queryParams.Encode() | ||
|
||
resp, err := w.client.Do(req) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to sent request") | ||
} | ||
|
||
return resp, nil | ||
} | ||
|
||
// DetectWAF performs WAF identification. Returns WAF name and vendor after | ||
// the first positive match. | ||
func (w *WAFDetector) DetectWAF(ctx context.Context) (name, vendor string, err error) { | ||
resp, err := w.doRequest(ctx) | ||
if err != nil { | ||
return "", "", errors.Wrap(err, "couldn't identify WAF") | ||
} | ||
|
||
for _, d := range detectors.Detectors { | ||
if d.IsWAF(resp) { | ||
return d.WAFName, d.Vendor, nil | ||
} | ||
} | ||
|
||
return "", "", nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package detectors | ||
|
||
func KonaSiteDefender() *Detector { | ||
d := &Detector{ | ||
WAFName: "Kona SiteDefender", | ||
Vendor: "Akamai", | ||
} | ||
|
||
d.Checks = []Check{ | ||
CheckHeader("Server", "AkamaiGHost"), | ||
} | ||
|
||
return d | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package detectors | ||
|
||
import ( | ||
"net/http" | ||
"regexp" | ||
) | ||
|
||
// Check performs some check on the response with a fixed condition. | ||
type Check func(resp *http.Response) bool | ||
|
||
// CheckStatusCode compare response status code with given value. | ||
func CheckStatusCode(status int) Check { | ||
f := func(resp *http.Response) bool { | ||
if resp.StatusCode == status { | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
return f | ||
} | ||
|
||
// CheckHeader match header value with regex. | ||
func CheckHeader(header, regex string) Check { | ||
re := regexp.MustCompile(regex) | ||
|
||
f := func(resp *http.Response) bool { | ||
values := resp.Header.Values(header) | ||
if values == nil { | ||
return false | ||
} | ||
|
||
for i := range values { | ||
if re.MatchString(values[i]) { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
return f | ||
} | ||
|
||
// CheckCookie match Set-Cookie header values with regex. | ||
func CheckCookie(regex string) Check { | ||
return CheckHeader("Set-Cookie", regex) | ||
} | ||
|
||
// CheckContent match body value with regex. | ||
func CheckContent(regex string) Check { | ||
re := regexp.MustCompile(regex) | ||
|
||
f := func(resp *http.Response) bool { | ||
var body []byte | ||
|
||
_, err := resp.Body.Read(body) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
if re.Match(body) { | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
return f | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package detectors | ||
|
||
import "net/http" | ||
|
||
// Detector contains names of WAF solution and vendor, and checks to detect that | ||
// solution by response. | ||
type Detector struct { | ||
WAFName string | ||
Vendor string | ||
|
||
Checks []Check | ||
} | ||
|
||
func (d *Detector) GetWAFName() string { | ||
return d.WAFName | ||
} | ||
|
||
func (d *Detector) GetVendor() string { | ||
return d.Vendor | ||
} | ||
|
||
func (d *Detector) IsWAF(resp *http.Response) bool { | ||
for _, check := range d.Checks { | ||
if check(resp) { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
// Detectors is the list of all available WAF detectors. The checks are performed | ||
// in the given order. | ||
var Detectors = []*Detector{ | ||
KonaSiteDefender(), | ||
Incapsula(), | ||
SecureSphere(), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package detectors | ||
|
||
func SecureSphere() *Detector { | ||
d := &Detector{ | ||
WAFName: "SecureSphere", | ||
Vendor: "Imperva Inc.", | ||
} | ||
|
||
d.Checks = []Check{ | ||
CheckContent("<(title|h2)>Error"), | ||
CheckContent("The incident ID is"), | ||
CheckContent("This page can't be displayed"), | ||
CheckContent("Contact support for additional information"), | ||
} | ||
|
||
return d | ||
} | ||
|
||
func Incapsula() *Detector { | ||
d := &Detector{ | ||
WAFName: "Incapsula", | ||
Vendor: "Imperva Inc.", | ||
} | ||
|
||
d.Checks = []Check{ | ||
CheckCookie("^incap_ses.*?="), | ||
CheckCookie("^visid_incap.*?="), | ||
CheckContent("incapsula incident id"), | ||
CheckContent("powered by incapsula"), | ||
CheckContent("/_Incapsula_Resource"), | ||
} | ||
|
||
return d | ||
} |
Oops, something went wrong.