From d700b6b704d7eeff48e70b98af918b4769f01041 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Mon, 26 Aug 2024 01:12:08 +0530 Subject: [PATCH 1/4] handle regex based payloads add os command injection test --- src/cmd/offat/main.go | 1 + src/pkg/tgen/basicSqliTest.go | 16 ++++++++-------- src/pkg/tgen/basicSsrf.go | 8 +++++--- src/pkg/tgen/osCommandInjection.go | 22 ++++++++++++++++++++++ src/pkg/tgen/payloadInjection.go | 15 ++++++++------- src/pkg/tgen/struct.go | 15 +++++++++++++-- src/pkg/tgen/tgen.go | 16 ++++++++++++++++ src/pkg/trunner/postrunner/postrunner.go | 15 ++++++++++----- 8 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 src/pkg/tgen/osCommandInjection.go diff --git a/src/cmd/offat/main.go b/src/cmd/offat/main.go index 6cda80d..c17818f 100644 --- a/src/cmd/offat/main.go +++ b/src/cmd/offat/main.go @@ -115,6 +115,7 @@ func main() { RunUnrestrictedHttpMethodTest: true, RunBasicSQLiTest: true, RunBasicSSRFTest: true, + RunOsCommandInjectionTest: true, // SSRF Test SsrfUrl: *config.SsrfUrl, diff --git a/src/pkg/tgen/basicSqliTest.go b/src/pkg/tgen/basicSqliTest.go index 91e9643..017850a 100644 --- a/src/pkg/tgen/basicSqliTest.go +++ b/src/pkg/tgen/basicSqliTest.go @@ -8,19 +8,19 @@ import ( func BasicSqliTest(baseUrl string, docParams []*parser.DocHttpParams, queryParams map[string]string, headers map[string]string, injectionConfig InjectionConfig) []*ApiTest { testName := "Basic SQLI Test" vulnResponseCodes := []int{500} - immuneResponseCodes := []int{} + // TODO: implement injection in both keys and values - payloads := []string{ - "' OR 1=1 ;--", - "' UNION SELECT 1,2,3 -- -", - "' OR '1'='1--", - "' AND (SELECT * FROM (SELECT(SLEEP(5)))abc)", - "' AND SLEEP(5) --", + payloads := []Payload{ + {InjText: "' OR 1=1 ;--", VulnerableResponseCodes: vulnResponseCodes}, + {InjText: "' UNION SELECT 1,2,3 -- -", VulnerableResponseCodes: vulnResponseCodes}, + {InjText: "' OR '1'='1--", VulnerableResponseCodes: vulnResponseCodes}, + {InjText: "' AND (SELECT * FROM (SELECT(SLEEP(5)))abc)", VulnerableResponseCodes: vulnResponseCodes}, + {InjText: "' AND SLEEP(5) --", VulnerableResponseCodes: vulnResponseCodes}, } injectionConfig.Payloads = payloads - tests := injectParamIntoApiTest(baseUrl, docParams, queryParams, headers, testName, vulnResponseCodes, immuneResponseCodes, injectionConfig) + tests := injectParamIntoApiTest(baseUrl, docParams, queryParams, headers, testName, injectionConfig) return tests } diff --git a/src/pkg/tgen/basicSsrf.go b/src/pkg/tgen/basicSsrf.go index f89475d..ee2379a 100644 --- a/src/pkg/tgen/basicSsrf.go +++ b/src/pkg/tgen/basicSsrf.go @@ -6,12 +6,14 @@ import "github.com/OWASP/OFFAT/src/pkg/parser" func BasicSsrfTest(ssrfUrl, baseUrl string, docParams []*parser.DocHttpParams, queryParams map[string]string, headers map[string]string, injectionConfig InjectionConfig) []*ApiTest { testName := "Basic SSRF Test" vulnResponseCodes := []int{500} - immuneResponseCodes := []int{} - payloads := []string{ssrfUrl} + + payloads := []Payload{ + {InjText: ssrfUrl, VulnerableResponseCodes: vulnResponseCodes}, + } injectionConfig.Payloads = payloads - tests := injectParamIntoApiTest(baseUrl, docParams, queryParams, headers, testName, vulnResponseCodes, immuneResponseCodes, injectionConfig) + tests := injectParamIntoApiTest(baseUrl, docParams, queryParams, headers, testName, injectionConfig) return tests } diff --git a/src/pkg/tgen/osCommandInjection.go b/src/pkg/tgen/osCommandInjection.go new file mode 100644 index 0000000..d1fade7 --- /dev/null +++ b/src/pkg/tgen/osCommandInjection.go @@ -0,0 +1,22 @@ +package tgen + +import ( + "github.com/OWASP/OFFAT/src/pkg/parser" +) + +func BasicOsCommandInjectionTest(baseUrl string, docParams []*parser.DocHttpParams, queryParams map[string]string, headers map[string]string, injectionConfig InjectionConfig) []*ApiTest { + testName := "Basic OS Command Injection Test" + + // TODO: implement injection in both keys and values + payloads := []Payload{ + {InjText: "cat /etc/passwd", Regex: "root:.*"}, + {InjText: "cat /etc/shadow", Regex: "root:.*"}, + {InjText: "ls -la", Regex: "total\\s\\d+"}, + } + + injectionConfig.Payloads = payloads + + tests := injectParamIntoApiTest(baseUrl, docParams, queryParams, headers, testName, injectionConfig) + + return tests +} diff --git a/src/pkg/tgen/payloadInjection.go b/src/pkg/tgen/payloadInjection.go index 9198a8e..ab64aaa 100644 --- a/src/pkg/tgen/payloadInjection.go +++ b/src/pkg/tgen/payloadInjection.go @@ -31,7 +31,7 @@ func injectParamInParam(params *[]parser.Param, payload string) { } // generates Api tests by injecting payloads in values -func injectParamIntoApiTest(url string, docParams []*parser.DocHttpParams, queryParams map[string]string, headers map[string]string, testName string, vulnResponseCodes, immuneResponseCodes []int, injectionConfig InjectionConfig) []*ApiTest { +func injectParamIntoApiTest(url string, docParams []*parser.DocHttpParams, queryParams map[string]string, headers map[string]string, testName string, injectionConfig InjectionConfig) []*ApiTest { var tests []*ApiTest // TODO: only inject payloads if any payload is accepted by the endpoint, else ignore injection // as this will reduce number of tests generated and increase efficiency @@ -40,16 +40,16 @@ func injectParamIntoApiTest(url string, docParams []*parser.DocHttpParams, query for _, docParam := range docParams { // inject payloads into string before converting it to map[string]string if injectionConfig.InBody { - injectParamInParam(&(docParam.BodyParams), payload) + injectParamInParam(&(docParam.BodyParams), payload.InjText) } if injectionConfig.InQuery { - injectParamInParam(&(docParam.QueryParams), payload) + injectParamInParam(&(docParam.QueryParams), payload.InjText) } if injectionConfig.InCookie { - injectParamInParam(&(docParam.CookieParams), payload) + injectParamInParam(&(docParam.CookieParams), payload.InjText) } if injectionConfig.InHeader { - injectParamInParam(&(docParam.HeaderParams), payload) + injectParamInParam(&(docParam.HeaderParams), payload.InjText) } // parse maps @@ -75,8 +75,9 @@ func injectParamIntoApiTest(url string, docParams []*parser.DocHttpParams, query Request: request, Path: docParam.Path, PathWithParams: pathWithParams, - VulnerableResponseCodes: vulnResponseCodes, - ImmuneResponseCodes: immuneResponseCodes, + VulnerableResponseCodes: payload.VulnerableResponseCodes, + ImmuneResponseCodes: payload.ImmuneResponseCodes, + MatchRegex: payload.Regex, } tests = append(tests, &test) } diff --git a/src/pkg/tgen/struct.go b/src/pkg/tgen/struct.go index 3e093d5..71a1efa 100644 --- a/src/pkg/tgen/struct.go +++ b/src/pkg/tgen/struct.go @@ -11,11 +11,12 @@ type ApiTest struct { Request *client.Request `json:"request"` Path string `json:"path"` PathWithParams string `json:"path_with_params"` + MatchRegex string `json:"match_regex"` // regex used in post processing for detecting injection // Fields to be populated after making HTTP request IsVulnerable bool `json:"is_vulnerable"` IsDataLeak bool `json:"is_data_leak"` - Response *client.ConcurrentResponse `json:"response"` + Response *client.ConcurrentResponse `json:"concurrent_response"` // Post Request Process VulnerableResponseCodes []int `json:"vulnerable_response_codes"` @@ -28,9 +29,19 @@ type InjectionConfig struct { InBody bool InHeader bool InCookie bool - Payloads []string + Payloads []Payload // for vulnerable ssrf endpoint inject endpoint in query param // example: https://ssrf-website.com?offat_test_endpoint=/api/v1/users InjectUriInQuery bool } + +// Struct used for injecting payloads while generating tests +type Payload struct { + InjText string // text to be injected + + // Post Processors + VulnerableResponseCodes []int // status code indicating API endpoint is vulnerable + ImmuneResponseCodes []int // status code indicating API endpoint is not vulnerable + Regex string // regex to be used for post processing +} diff --git a/src/pkg/tgen/tgen.go b/src/pkg/tgen/tgen.go index 8681479..1ba6fb1 100644 --- a/src/pkg/tgen/tgen.go +++ b/src/pkg/tgen/tgen.go @@ -17,6 +17,7 @@ type TGenHandler struct { RunUnrestrictedHttpMethodTest bool RunBasicSQLiTest bool RunBasicSSRFTest bool + RunOsCommandInjectionTest bool // SSRF Test related data SsrfUrl string @@ -46,6 +47,21 @@ func (t *TGenHandler) GenerateTests() []*ApiTest { log.Info().Msgf("%d tests generated for Basic SQLI", len(newTests)) } + // Basic OS Command Injection Test + if t.RunOsCommandInjectionTest { + injectionConfig := InjectionConfig{ + InBody: true, + InCookie: true, + InHeader: true, + InPath: true, + InQuery: true, + } + newTests := BasicOsCommandInjectionTest(t.BaseUrl, t.Doc, t.DefaultQueryParams, t.DefaultHeaders, injectionConfig) + tests = append(tests, newTests...) + + log.Info().Msgf("%d tests generated for Basic OS Command Injection", len(newTests)) + } + if t.RunBasicSSRFTest && utils.ValidateURL(t.SsrfUrl) { injectionConfig := InjectionConfig{ InBody: true, diff --git a/src/pkg/trunner/postrunner/postrunner.go b/src/pkg/trunner/postrunner/postrunner.go index b7e9847..4b1d792 100644 --- a/src/pkg/trunner/postrunner/postrunner.go +++ b/src/pkg/trunner/postrunner/postrunner.go @@ -1,9 +1,12 @@ package postrunner import ( + "regexp" + _ "github.com/OWASP/OFFAT/src/pkg/logging" "github.com/OWASP/OFFAT/src/pkg/tgen" "github.com/OWASP/OFFAT/src/pkg/utils" + "github.com/rs/zerolog/log" ) // removes immune endpoints from the api tests slice @@ -29,13 +32,15 @@ func UpdateStatusCodeBasedResult(apiTests *[]*tgen.ApiTest) { } if len(apiTest.ImmuneResponseCodes) > 0 { - if !utils.SearchInSlice(apiTest.ImmuneResponseCodes, apiTest.Response.Response.StatusCode) { - apiTest.IsVulnerable = true - } + apiTest.IsVulnerable = !utils.SearchInSlice(apiTest.ImmuneResponseCodes, apiTest.Response.Response.StatusCode) } else if len(apiTest.VulnerableResponseCodes) > 0 { - if utils.SearchInSlice(apiTest.VulnerableResponseCodes, apiTest.Response.Response.StatusCode) { - apiTest.IsVulnerable = true + apiTest.IsVulnerable = utils.SearchInSlice(apiTest.VulnerableResponseCodes, apiTest.Response.Response.StatusCode) + } else if len(apiTest.MatchRegex) > 0 { + isVuln, err := regexp.Match(apiTest.MatchRegex, apiTest.Response.Response.Body) + if err != nil { + log.Error().Stack().Err(err).Msg("failed to validate match regex against response body") } + apiTest.IsVulnerable = isVuln } } } From 980188f19fdd833d108566b92c47a2c6352617f9 Mon Sep 17 00:00:00 2001 From: Dhrumil Mistry <56185972+dmdhrumilmistry@users.noreply.github.com> Date: Mon, 26 Aug 2024 01:28:45 +0530 Subject: [PATCH 2/4] add xss/html and ssti injection tests --- src/cmd/offat/main.go | 2 ++ .../tgen/{basicSqliTest.go => basicSqli.go} | 0 src/pkg/tgen/sstiInjection.go | 28 ++++++++++++++++ src/pkg/tgen/tgen.go | 32 +++++++++++++++++++ src/pkg/tgen/xssHtmlInjection.go | 22 +++++++++++++ 5 files changed, 84 insertions(+) rename src/pkg/tgen/{basicSqliTest.go => basicSqli.go} (100%) create mode 100644 src/pkg/tgen/sstiInjection.go create mode 100644 src/pkg/tgen/xssHtmlInjection.go diff --git a/src/cmd/offat/main.go b/src/cmd/offat/main.go index c17818f..ceb996d 100644 --- a/src/cmd/offat/main.go +++ b/src/cmd/offat/main.go @@ -116,6 +116,8 @@ func main() { RunBasicSQLiTest: true, RunBasicSSRFTest: true, RunOsCommandInjectionTest: true, + RunXssHtmlInjectionTest: true, + RunSstiInjectionTest: true, // SSRF Test SsrfUrl: *config.SsrfUrl, diff --git a/src/pkg/tgen/basicSqliTest.go b/src/pkg/tgen/basicSqli.go similarity index 100% rename from src/pkg/tgen/basicSqliTest.go rename to src/pkg/tgen/basicSqli.go diff --git a/src/pkg/tgen/sstiInjection.go b/src/pkg/tgen/sstiInjection.go new file mode 100644 index 0000000..bc9e33c --- /dev/null +++ b/src/pkg/tgen/sstiInjection.go @@ -0,0 +1,28 @@ +package tgen + +import ( + "github.com/OWASP/OFFAT/src/pkg/parser" +) + +func BasicSstiInjectionTest(baseUrl string, docParams []*parser.DocHttpParams, queryParams map[string]string, headers map[string]string, injectionConfig InjectionConfig) []*ApiTest { + testName := "Basic SSTI Injection Test" + + // TODO: implement injection in both keys and values + payloads := []Payload{ + {InjText: `${7777+99999}`, Regex: "107776"}, + {InjText: `{{7*'7'}}`, Regex: "49"}, + {InjText: `*{7*7}`, Regex: "49"}, + {InjText: `{{7*'7'}}`, Regex: "7777777"}, + {InjText: `{{ '' }}`, Regex: ``}, + {InjText: `{{ '' | safe }}`, Regex: ``}, + {InjText: `{{'owasp offat'.toUpperCase()}}`, Regex: `OWASP OFFAT`}, + {InjText: `{{'owasp offat' | upper }}`, Regex: `OWASP OFFAT`}, + {InjText: `<%= system('cat /etc/passwd') %>`, Regex: `root:.*`}, + } + + injectionConfig.Payloads = payloads + + tests := injectParamIntoApiTest(baseUrl, docParams, queryParams, headers, testName, injectionConfig) + + return tests +} diff --git a/src/pkg/tgen/tgen.go b/src/pkg/tgen/tgen.go index 1ba6fb1..3946f97 100644 --- a/src/pkg/tgen/tgen.go +++ b/src/pkg/tgen/tgen.go @@ -18,6 +18,8 @@ type TGenHandler struct { RunBasicSQLiTest bool RunBasicSSRFTest bool RunOsCommandInjectionTest bool + RunXssHtmlInjectionTest bool + RunSstiInjectionTest bool // SSRF Test related data SsrfUrl string @@ -62,6 +64,36 @@ func (t *TGenHandler) GenerateTests() []*ApiTest { log.Info().Msgf("%d tests generated for Basic OS Command Injection", len(newTests)) } + // Basic XSS/HTML Injection Test + if t.RunXssHtmlInjectionTest { + injectionConfig := InjectionConfig{ + InBody: true, + InCookie: true, + InHeader: true, + InPath: true, + InQuery: true, + } + newTests := BasicXssHtmlInjectionTest(t.BaseUrl, t.Doc, t.DefaultQueryParams, t.DefaultHeaders, injectionConfig) + tests = append(tests, newTests...) + + log.Info().Msgf("%d tests generated for Basic XSS/HTML Injection", len(newTests)) + } + + // Basic SSTI Command Injection Test + if t.RunSstiInjectionTest { + injectionConfig := InjectionConfig{ + InBody: true, + InCookie: true, + InHeader: true, + InPath: true, + InQuery: true, + } + newTests := BasicSstiInjectionTest(t.BaseUrl, t.Doc, t.DefaultQueryParams, t.DefaultHeaders, injectionConfig) + tests = append(tests, newTests...) + + log.Info().Msgf("%d tests generated for Basic OS Command Injection", len(newTests)) + } + if t.RunBasicSSRFTest && utils.ValidateURL(t.SsrfUrl) { injectionConfig := InjectionConfig{ InBody: true, diff --git a/src/pkg/tgen/xssHtmlInjection.go b/src/pkg/tgen/xssHtmlInjection.go new file mode 100644 index 0000000..2d3b024 --- /dev/null +++ b/src/pkg/tgen/xssHtmlInjection.go @@ -0,0 +1,22 @@ +package tgen + +import ( + "github.com/OWASP/OFFAT/src/pkg/parser" +) + +func BasicXssHtmlInjectionTest(baseUrl string, docParams []*parser.DocHttpParams, queryParams map[string]string, headers map[string]string, injectionConfig InjectionConfig) []*ApiTest { + testName := "Basic XSS/HTML Injection Test" + + // TODO: implement injection in both keys and values + payloads := []Payload{ + {InjText: "", Regex: `]*>.*<\/script>`}, + {InjText: "", Regex: `]*>.*<\/script>`}, + {InjText: " Date: Mon, 26 Aug 2024 01:35:16 +0530 Subject: [PATCH 4/4] makefile fix for cicd --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index a7ce75b..651c557 100644 --- a/src/Makefile +++ b/src/Makefile @@ -10,7 +10,7 @@ scan-vulns: docker: dbuild scan-vulns build: - @go build -o bin/offat cmd/offat/main.go + @go build -o bin/offat cmd/offat/* test: @go test -cover -v ./...