Skip to content

Commit

Permalink
feat: add openapi scan support
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanuelgautier committed Feb 11, 2024
1 parent 1093f32 commit ff27dbf
Show file tree
Hide file tree
Showing 24 changed files with 964 additions and 121 deletions.
72 changes: 72 additions & 0 deletions cmd/scan/curl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package scan

import (
"log"
"net/http"
"strings"

"github.com/cerberauth/vulnapi/scan"
"github.com/spf13/cobra"
)

var (
url string
method string
headers []string
cookies []string
)

func NewCURLScanCmd() (scanCmd *cobra.Command) {
scanCmd = &cobra.Command{
Use: "curl [URL]",
Short: "URL Scan in CURL style",
Args: cobra.ExactArgs(1),
FParseErrWhitelist: cobra.FParseErrWhitelist{
UnknownFlags: true,
},
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
url = args[0]
}

httpHeaders := http.Header{}
for _, h := range headers {
parts := strings.SplitN(h, ":", 2)
httpHeaders.Add(parts[0], parts[1])
}

var httpCookies []http.Cookie
for _, c := range cookies {
parts := strings.SplitN(c, ":", 2)
httpCookies = append(httpCookies, http.Cookie{
Name: parts[0],
Value: parts[1],
})
}

scan, err := scan.NewURLScan(method, url, &httpHeaders, httpCookies, nil)
if err != nil {
log.Fatal(err)
}

rpr, _, err := scan.WithAllVulnsScans().Execute()
if err != nil {
log.Fatal(err)
}

if !rpr.HasVulnerability() {
log.Println("Congratulations! No vulnerability has been discovered!")
}

for _, r := range rpr.GetVulnerabilityReports() {
log.Println(r)
}
},
}

scanCmd.PersistentFlags().StringVarP(&method, "request", "X", "GET", "Specify request method to use")
scanCmd.PersistentFlags().StringArrayVarP(&headers, "header", "H", nil, "Pass custom header(s) to target API")
scanCmd.PersistentFlags().StringArrayVarP(&cookies, "cookie", "b", nil, "Send cookies from string")

return scanCmd
}
63 changes: 63 additions & 0 deletions cmd/scan/openapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package scan

import (
"log"

"github.com/cerberauth/vulnapi/scan"
"github.com/spf13/cobra"
)

var openapiUrlOrPath string

// func isStdinOpen() bool {
// stat, _ := os.Stdin.Stat()
// return (stat.Mode() & os.ModeCharDevice) == 0
// }

// func readStdin() *string {
// scanner := bufio.NewScanner(os.Stdin)
// if scanner.Scan() {
// t := scanner.Text()
// return &t
// }

// return nil
// }

func NewOpenAPIScanCmd() (scanCmd *cobra.Command) {
scanCmd = &cobra.Command{
Use: "openapi [OpenAPIPAth]",
Short: "Full OpenAPI operations scan",
Run: func(cmd *cobra.Command, args []string) {
scan, err := scan.NewOpenAPIScan(openapiUrlOrPath, nil)
if err != nil {
log.Fatal(err)
}

// if isStdinOpen() {
// stdin := readStdin()
// if stdin != nil {
// bearerSecurityScheme := auth.NewAuthorizationBearerSecurityScheme("default", stdin)
// scanner.AddSecurityScheme(bearerSecurityScheme)
// }
// }

rpr, _, err := scan.WithAllVulnsScans().Execute()
if err != nil {
log.Fatal(err)
}

if !rpr.HasVulnerability() {
log.Println("Congratulations! No vulnerability has been discovered!")
}

for _, r := range rpr.GetVulnerabilityReports() {
log.Println(r)
}
},
}

scanCmd.PersistentFlags().StringVarP(&openapiUrlOrPath, "openapi", "", "", "OpenAPI URL or Path. The scan will be performed against the operations listed in OpenAPI file.")

return scanCmd
}
44 changes: 3 additions & 41 deletions cmd/scan/root.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,16 @@
package scan

import (
"bufio"
"fmt"
"log"

"github.com/cerberauth/vulnapi/scan"
"github.com/spf13/cobra"
)

var (
url string
jwt string
)

func NewScanCmd() (scanCmd *cobra.Command) {
scanCmd = &cobra.Command{
Use: "scan [URL]",
Use: "scan [type]",
Short: "API Scan",
// Full API scan coming (not only one URL)
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
url = args[0]
}

if jwt == "" {
stdin, err := bufio.NewReader(cmd.InOrStdin()).ReadString('\n')
if err != nil {
log.Fatal(fmt.Errorf("failed process input: %v", err))
}
jwt = stdin
}

rpr, _, err := scan.NewScanner(url, &jwt).WithAllScans().Execute()
if err != nil {
log.Fatal(err)
}

if !rpr.HasVulnerability() {
println("Congratulations! No vulnerability has been discovered!")
}

for _, r := range rpr.GetVulnerabilityReports() {
log.Println(r)
}
},
}

scanCmd.PersistentFlags().StringVarP(&url, "url", "u", "", "URL")
scanCmd.PersistentFlags().StringVarP(&jwt, "jwt", "j", "", "Valid JWT")
scanCmd.AddCommand(NewCURLScanCmd())
scanCmd.AddCommand(NewOpenAPIScanCmd())

return scanCmd
}
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,24 @@ module github.com/cerberauth/vulnapi
go 1.22

require (
github.com/getkin/kin-openapi v0.120.0
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/jarcoal/httpmock v1.3.1
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.2
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
50 changes: 50 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,62 @@
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg=
github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
29 changes: 29 additions & 0 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package auth

import "net/http"

type Type string

const (
HttpType Type = "http"
OAuth2 Type = "oauth2"
OpenIdConnect Type = "openIdConnect"
ApiKey Type = "apiKey"
)

type SecurityScheme interface {
GetHeaders() http.Header
GetCookies() []*http.Cookie
GetValidValue() interface{}
SetAttackValue(v interface{})
GetAttackValue() interface{}
}

type Operation struct {
Url string
Method string
Headers *http.Header
Cookies []http.Cookie

SecuritySchemes []SecurityScheme
}
51 changes: 51 additions & 0 deletions internal/auth/bearer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package auth

import (
"fmt"
"net/http"
)

type BearerSecurityScheme struct {
Type Type
Scheme SchemeName
In SchemeIn
Name string
ValidValue *string
AttackValue string
}

func NewAuthorizationBearerSecurityScheme(name string, value *string) *BearerSecurityScheme {
return &BearerSecurityScheme{
Type: HttpType,
Scheme: BearerScheme,
In: InHeader,
Name: name,
ValidValue: value,
AttackValue: "",
}
}

func (ss *BearerSecurityScheme) GetHeaders() http.Header {
header := http.Header{}
if ss.ValidValue != nil {
header.Set(AuthorizationHeader, fmt.Sprintf("%s %s", BearerPrefix, *ss.ValidValue))
}

return header
}

func (ss *BearerSecurityScheme) GetCookies() []*http.Cookie {
return []*http.Cookie{}
}

func (ss *BearerSecurityScheme) GetValidValue() interface{} {
return *ss.ValidValue
}

func (ss *BearerSecurityScheme) SetAttackValue(v interface{}) {
ss.AttackValue = v.(string)
}

func (ss *BearerSecurityScheme) GetAttackValue() interface{} {
return ss.AttackValue
}
4 changes: 4 additions & 0 deletions internal/auth/headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package auth

const AuthorizationHeader = "Authorization"
const BearerPrefix = "Bearer"
Loading

0 comments on commit ff27dbf

Please sign in to comment.