Skip to content

Commit

Permalink
feat: add http basic support
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanuelgautier committed Dec 7, 2024
1 parent 5f0543c commit 6671cbb
Show file tree
Hide file tree
Showing 20 changed files with 424 additions and 68 deletions.
37 changes: 34 additions & 3 deletions .github/workflows/scans.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:

run-header-strong-api-key-scan:
name: Strong API Key Scan
runs-on: ubuntu-latest
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -105,7 +105,7 @@ jobs:

run-header-api-key-scan:
name: API Key in header Scan
runs-on: ubuntu-latest
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -141,7 +141,7 @@ jobs:

run-bearer-api-key-scan:
name: Bearer API Key Scan
runs-on: ubuntu-latest
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -175,6 +175,37 @@ jobs:
if: ${{ always() }}
run: docker stop $(docker ps -q --filter ancestor=ghcr.io/cerberauth/api-vulns-challenges/auth-not-verified:latest)

run-header-strong-http-basic-scan:
name: Strong HTTP Basic Scan
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Run Server
run: docker run -d -p 8080:8080 ghcr.io/cerberauth/api-vulns-challenges/strong-http-basic:latest

- name: Setup Go environment
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}

- name: VulnAPI
id: vulnapi
run: |
go run main.go scan curl http://localhost:8080 -H "X-API-Key: abcdef1234" --sqa-opt-out
- name: Stop Server
if: ${{ always() }}
run: docker stop $(docker ps -q --filter ancestor=ghcr.io/cerberauth/api-vulns-challenges/strong-http-basic:latest)

run-http-misconfigurations-scans:
name: HTTP Misconfigurations Scans
runs-on: ubuntu-latest
Expand Down
53 changes: 53 additions & 0 deletions internal/auth/basic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package auth

import "encoding/base64"

type HTTPBasicCredentials struct {
Username string `json:"username" yaml:"username"`
Password string `json:"password" yaml:"password"`
}

func NewHTTPBasicCredentials(username string, password string) *HTTPBasicCredentials {
return &HTTPBasicCredentials{
Username: username,
Password: password,
}
}

func (credentials *HTTPBasicCredentials) GetUsername() string {
return credentials.Username
}

func (credentials *HTTPBasicCredentials) GetPassword() string {
return credentials.Password
}

func (credentials *HTTPBasicCredentials) Encode() string {
return base64.StdEncoding.EncodeToString([]byte(credentials.GetUsername() + ":" + credentials.GetPassword()))
}

func NewAuthorizationBasicSecurityScheme(name string, credentials *HTTPBasicCredentials) (*SecurityScheme, error) {
in := InHeader
securityScheme, err := NewSecurityScheme(name, nil, HttpType, BasicScheme, &in, nil)
if err != nil {
return nil, err
}

Check warning on line 34 in internal/auth/basic.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/basic.go#L33-L34

Added lines #L33 - L34 were not covered by tests

if credentials != nil {
err = securityScheme.SetValidValue(credentials)
if err != nil {
return nil, err
}

Check warning on line 40 in internal/auth/basic.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/basic.go#L39-L40

Added lines #L39 - L40 were not covered by tests
}

return securityScheme, nil
}

func MustNewAuthorizationBasicSecurityScheme(name string, credentials *HTTPBasicCredentials) *SecurityScheme {
securityScheme, err := NewAuthorizationBasicSecurityScheme(name, credentials)
if err != nil {
panic(err)

Check warning on line 49 in internal/auth/basic.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/basic.go#L49

Added line #L49 was not covered by tests
}

return securityScheme
}
47 changes: 47 additions & 0 deletions internal/auth/basic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package auth_test

import (
"testing"

"github.com/cerberauth/vulnapi/internal/auth"
"github.com/stretchr/testify/assert"
)

func TestNewAuthorizationBasicSecurityScheme(t *testing.T) {
name := "basic"
basicCredentials := auth.NewHTTPBasicCredentials("admin", "password")

securityScheme, err := auth.NewAuthorizationBasicSecurityScheme(name, basicCredentials)

assert.NoError(t, err)
assert.Equal(t, auth.HttpType, securityScheme.GetType())
assert.Equal(t, auth.BasicScheme, securityScheme.GetScheme())
assert.Equal(t, auth.InHeader, *securityScheme.GetIn())
assert.Equal(t, name, securityScheme.GetName())
assert.Equal(t, basicCredentials, securityScheme.GetValidValue())
assert.Equal(t, nil, securityScheme.GetAttackValue())
}

func TestNewAuthorizationBasicSecurityScheme_WhenNilValue(t *testing.T) {
name := "basic"

securityScheme, err := auth.NewAuthorizationBasicSecurityScheme(name, nil)

assert.NoError(t, err)
assert.Equal(t, nil, securityScheme.GetValidValue())
assert.Equal(t, nil, securityScheme.GetAttackValue())
}

func TestMustNewAuthorizationBasicSecurityScheme(t *testing.T) {
name := "basic"
basicCredentials := auth.NewHTTPBasicCredentials("admin", "password")

securityScheme := auth.MustNewAuthorizationBasicSecurityScheme(name, basicCredentials)

assert.Equal(t, auth.HttpType, securityScheme.GetType())
assert.Equal(t, auth.BasicScheme, securityScheme.GetScheme())
assert.Equal(t, auth.InHeader, *securityScheme.GetIn())
assert.Equal(t, name, securityScheme.GetName())
assert.Equal(t, basicCredentials, securityScheme.GetValidValue())
assert.Equal(t, nil, securityScheme.GetAttackValue())
}
1 change: 1 addition & 0 deletions internal/auth/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ package auth

const AuthorizationHeader = "Authorization"
const BearerPrefix = "Bearer"
const BasicPrefix = "Basic"
49 changes: 34 additions & 15 deletions internal/auth/security_scheme.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ func (securityScheme *SecurityScheme) GetToken() string {
if !securityScheme.HasValidValue() {
return ""
}

switch securityScheme.GetType() {
case OAuth2:
return securityScheme.GetValidValue().(*OAuthValue).GetAccessToken()
Expand Down Expand Up @@ -121,17 +120,25 @@ func (securityScheme *SecurityScheme) validateValue(value interface{}) error {
return nil

case HttpType:
val, ok := value.(string)
if !ok {
return fmt.Errorf("invalid value for http security scheme")
}

if securityScheme.GetTokenFormat() != nil && *securityScheme.GetTokenFormat() == JWTTokenFormat {
if _, err := jwt.NewJWTWriter(val); err != nil {
return err
switch securityScheme.GetScheme() {
case BasicScheme:
_, ok := value.(*HTTPBasicCredentials)
if !ok {
return fmt.Errorf("invalid value for http basic security scheme")
}

Check warning on line 128 in internal/auth/security_scheme.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/security_scheme.go#L127-L128

Added lines #L127 - L128 were not covered by tests
return nil
default:
val, ok := value.(string)
if !ok {
return fmt.Errorf("invalid value for http security scheme")

Check warning on line 133 in internal/auth/security_scheme.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/security_scheme.go#L133

Added line #L133 was not covered by tests
}
if securityScheme.GetTokenFormat() != nil && *securityScheme.GetTokenFormat() == JWTTokenFormat {
if _, err := jwt.NewJWTWriter(val); err != nil {
return err
}
}
return nil
}
return nil

case OAuth2:
_, ok := value.(*OAuthValue)
Expand Down Expand Up @@ -186,23 +193,35 @@ func (securityScheme *SecurityScheme) GetAttackValue() interface{} {

func (securityScheme *SecurityScheme) GetHeaders() http.Header {
header := http.Header{}
if securityScheme.GetIn() == nil || *securityScheme.GetIn() != InHeader {
return header
}

Check warning on line 198 in internal/auth/security_scheme.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/security_scheme.go#L197-L198

Added lines #L197 - L198 were not covered by tests

attackValue := securityScheme.GetAttackValue()
if attackValue == nil && securityScheme.HasValidValue() {
attackValue = securityScheme.GetValidValue()
}

if attackValue == nil {
return header
}

if (securityScheme.GetType() == ApiKey || securityScheme.GetType() == HttpType) && *securityScheme.GetIn() == InHeader {
switch securityScheme.GetType() {
case HttpType:
var val string
if securityScheme.GetType() == HttpType && securityScheme.GetScheme() == BearerScheme {
switch securityScheme.GetScheme() {
case BasicScheme:
credentials := attackValue.(*HTTPBasicCredentials)
val = fmt.Sprintf("%s %s", BasicPrefix, credentials.Encode())
case BearerScheme:
val = fmt.Sprintf("%s %s", BearerPrefix, attackValue)
header.Set(AuthorizationHeader, val)
} else {
default:

Check warning on line 217 in internal/auth/security_scheme.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/security_scheme.go#L217

Added line #L217 was not covered by tests
val = fmt.Sprintf("%s", attackValue)
}
if val != "" {
header.Set(AuthorizationHeader, val)
}
case ApiKey:
if val := fmt.Sprintf("%s", attackValue); val != "" {
header.Set(securityScheme.GetName(), val)
}
}
Expand Down
22 changes: 22 additions & 0 deletions internal/auth/security_scheme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,28 @@ func TestGetHeaders(t *testing.T) {
attackValue: nil,
expectedHeaders: http.Header{},
},
{
name: "HTTP Basic with valid credentials",
schemeName: "Basic",
schemeType: auth.HttpType,
scheme: auth.BasicScheme,
in: &inHeader,
tokenFormat: nil,
validValue: auth.NewHTTPBasicCredentials("user", "password"),
attackValue: nil,
expectedHeaders: http.Header{"Authorization": []string{fmt.Sprintf("%s %s", auth.BasicPrefix, "dXNlcjpwYXNzd29yZA==")}},
},
{
name: "HTTP Basic with attack credentials",
schemeName: "Basic",
schemeType: auth.HttpType,
scheme: auth.BasicScheme,
in: &inHeader,
tokenFormat: nil,
validValue: auth.NewHTTPBasicCredentials("user", "password"),
attackValue: auth.NewHTTPBasicCredentials("user", "attack-password"),
expectedHeaders: http.Header{"Authorization": []string{fmt.Sprintf("%s %s", auth.BasicPrefix, "dXNlcjphdHRhY2stcGFzc3dvcmQ=")}},
},
}

for _, tt := range tests {
Expand Down
45 changes: 41 additions & 4 deletions internal/cmd/args.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package cmd

import "github.com/spf13/cobra"
import (
"strings"

"github.com/cerberauth/vulnapi/internal/auth"
"github.com/spf13/cobra"
)

var (
headers []string
cookies []string
headers []string = []string{}
cookies []string = []string{}
authUser string
rateLimit string
proxy string

Expand All @@ -30,6 +36,7 @@ func AddCommonArgs(cmd *cobra.Command) {
cmd.Flags().StringVarP(&proxy, "proxy", "p", "", "Proxy URL for requests")
cmd.Flags().StringArrayVarP(&headers, "header", "H", headers, "Headers to include in requests")
cmd.Flags().StringArrayVarP(&cookies, "cookie", "c", cookies, "Cookies to include in requests")
cmd.Flags().StringVarP(&authUser, "user", "u", "", "Specify the user name and password to use for server authentication")

cmd.Flags().StringArrayVarP(&includeScans, "scans", "", includeScans, "Include specific scans")
cmd.Flags().StringArrayVarP(&excludeScans, "exclude-scans", "e", excludeScans, "Exclude specific scans")
Expand Down Expand Up @@ -57,18 +64,24 @@ func AddPlaceholderArgs(cmd *cobra.Command) {
cmd.Flags().BoolVarP(&placeholderBool, "remote-name", "O", false, "Write output to a file named as the remote file")
cmd.Flags().BoolVarP(&placeholderBool, "silent", "s", false, "Run in silent mode")
cmd.Flags().StringVarP(&placeholderString, "upload-file", "T", "", "Transfer file to target API")
cmd.Flags().StringVarP(&placeholderString, "user", "u", "", "Specify the user name and password to use for server authentication")
cmd.Flags().StringVarP(&placeholderString, "user-agent", "A", "", "User-Agent to send to server")
}

func GetHeaders() []string {
if basicCredentials := basicAuth(GetAuthUser()); basicCredentials != "" {
headers = append(headers, "Authorization: Basic "+basicCredentials)
}
return headers
}

func GetCookies() []string {
return cookies
}

func GetAuthUser() string {
return authUser
}

func GetRateLimit() string {
return rateLimit
}
Expand Down Expand Up @@ -112,3 +125,27 @@ func GetNoProgress() bool {
func GetSeverityThreshold() float64 {
return severityThreshold
}

func basicAuth(user string) string {
credentials := strings.Split(user, ":")
if len(credentials) != 2 || credentials[0] == "" {
return ""
}
return auth.NewHTTPBasicCredentials(credentials[0], credentials[1]).Encode()
}

func ClearValues() {
headers = []string{}
cookies = []string{}
authUser = ""
rateLimit = defaultRateLimit
proxy = ""
includeScans = []string{}
excludeScans = []string{}
reportFormat = "table"
reportTransport = "file"
reportFile = ""
reportURL = ""
noProgress = false
severityThreshold = 1
}
Loading

0 comments on commit 6671cbb

Please sign in to comment.