Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handle regex based payloads #133

Merged
merged 4 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
}
Loading