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: `", Regex: `