Compare commits

..
3 Commits
13 changed files with 269 additions and 297 deletions

No files matched your search

+63 -19
View File
@@ -37,28 +37,72 @@ $ export PATH=$PATH:~/go/bin
> Use the `automate` command to send a series of requests to each defined endpoint and analyze the status code of each response.
```bash
$ sj automate -u https://petstore.swagger.io/v2/swagger.json -qi -p http://127.0.0.1:8080
$ sj automate -u https://petstore.swagger.io/v2/swagger.json -qi -p http://127.0.0.1:8080
INFO[0000] Gathering API details.
Gathering API details.
Title: Swagger Petstore
Description: This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
INFO Endpoint accessible! Method=GET Status=200 Target=/v2/v2/pet/findByTags
INFO Endpoint accessible! Method=GET Status=200 Target=/v2/v2/pet/1
WARN Manual testing may be required. Method=POST Status=415 Target=/v2/v2/pet/1
WARN Manual testing may be required. Method=POST Status=400 Target=/v2/v2/store/order
WARN Manual testing may be required. Method=POST Status=400 Target=/v2/v2/user/createWithList
INFO Endpoint accessible! Method=GET Status=200 Target=/v2/v2/user/login
INFO Endpoint accessible! Method=GET Status=200 Target=/v2/v2/user/logout
WARN Manual testing may be required. Method=POST Status=415 Target=/v2/v2/pet/1/uploadImage
WARN Manual testing may be required. Method=POST Status=400 Target=/v2/v2/pet
WARN Manual testing may be required. Method=PUT Status=415 Target=/v2/v2/pet
INFO Endpoint accessible! Method=GET Status=200 Target=/v2/v2/pet/findByStatus
INFO Endpoint accessible! Method=GET Status=200 Target=/v2/v2/store/inventory
INFO Endpoint accessible! Method=GET Status=200 Target=/v2/v2/store/order/1
ERRO Endpoint not found. Method=GET Status=404 Target=/v2/v2/user/bishopfox
WARN Manual testing may be required. Method=PUT Status=415 Target=/v2/v2/user/bishopfox
WARN Manual testing may be required. Method=POST Status=400 Target=/v2/v2/user/createWithArray
WARN Manual testing may be required. Method=POST Status=400 Target=/v2/v2/user
✓ GET 200 /v2/pet/findByStatus
✓ GET 200 /v2/user/logout
⚠ POST 400 /v2/user/createWithArray
⚠ POST 400 /v2/store/order
✗ GET 404 /v2/store/order/1
⚠ POST 400 /v2/pet
⚠ PUT 415 /v2/pet
⚠ POST 400 /v2/user/createWithList
✗ GET 404 /v2/user/bishopfox
⚠ PUT 415 /v2/user/bishopfox
⚠ POST 400 /v2/user
⚠ POST 415 /v2/pet/1/uploadImage
✓ GET 200 /v2/pet/findByTags
✗ GET 404 /v2/pet/1
⚠ POST 415 /v2/pet/1
✓ GET 200 /v2/store/inventory
✓ GET 200 /v2/user/login
```
You can also request verbose output to see the partial (or full) response:
```bash
$ sj automate -u https://petstore.swagger.io/v2/swagger.json -qi -p http://127.0.0.1:8080 -v
Gathering API details.
Title: Swagger Petstore
Description: This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.
✗ GET 404 /v2/user/bishopfox
{"code":1,"type":"error","message":"User not found
⚠ PUT 415 /v2/user/bishopfox
{"code":415,"type":"unknown","message":"com.sun.je
✓ GET 200 /v2/user/logout
{"code":200,"type":"unknown","message":"ok"}
⚠ POST 400 /v2/user/createWithArray
{"code":400,"type":"unknown","message":"bad input"
⚠ POST 400 /v2/user/createWithList
{"code":400,"type":"unknown","message":"bad input"
✗ GET 404 /v2/pet/1
{"code":1,"type":"error","message":"Pet not found"
⚠ POST 415 /v2/pet/1
{"code":415,"type":"unknown"}
✓ GET 200 /v2/store/inventory
{"sold":117,"string":26,"invalidStatus":1,"-1":1,"
⚠ POST 400 /v2/store/order
{"code":400,"type":"unknown","message":"bad input"
✓ GET 200 /v2/user/login
{"code":200,"type":"unknown","message":"logged in
⚠ POST 400 /v2/pet
{"code":400,"type":"unknown","message":"bad input"
⚠ PUT 415 /v2/pet
{"code":415,"type":"unknown","message":"com.sun.je
✓ GET 200 /v2/pet/findByStatus
[]
✓ GET 200 /v2/pet/findByTags
[]
✗ GET 404 /v2/store/order/1
{"code":1,"type":"error","message":"Order not foun
⚠ POST 400 /v2/user
{"code":400,"type":"unknown","message":"bad input"
⚠ POST 415 /v2/pet/1/uploadImage
{"code":415,"type":"unknown"}
```
> Use the `prepare` command to prepare a list of commands for manual testing. Currently supports both `curl` and `sqlmap`. You will likely have to modify these slightly.
+9 -10
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"strings"
log "github.com/sirupsen/logrus"
)
var (
@@ -47,7 +46,7 @@ func CheckSecuritySchemes(spec map[string]interface{}) {
case "basic":
if quiet {
autoApplyBasicAuth = "n"
log.Warn("A basic authentication header is accepted. Review the spec and craft a header manually using the -H flag.")
printWarn("A basic authentication header is accepted. Review the spec and craft a header manually using the -H flag.")
} else {
fmt.Println("Basic Authentication is accepted. Supply a username and password? (y/N)")
fmt.Scanln(&autoApplyBasicAuth)
@@ -59,21 +58,21 @@ func CheckSecuritySchemes(spec map[string]interface{}) {
fmt.Scanln(&basicAuthPass)
basicAuth = []byte(basicAuthUser + ":" + basicAuthPass)
basicAuthString = base64.StdEncoding.EncodeToString(basicAuth)
log.Infof("Using %s as the Basic Auth value.", basicAuthString)
printInfo("Using %s as the Basic Auth value.\n", basicAuthString)
Headers = append(Headers, "Authorization: Basic "+basicAuthString)
} else {
log.Warn("A basic authentication header is accepted. Review the spec and craft a header manually using the -H flag.")
printWarn("A basic authentication header is accepted. Review the spec and craft a header manually using the -H flag.")
}
}
case "bearer":
log.Warn("A bearer token is accepted. Review the spec and craft a token manually using the -H flag.")
printWarn("A bearer token is accepted. Review the spec and craft a token manually using the -H flag.")
}
}
case "apiKey":
if inVal, ok := scheme["in"].(string); ok {
switch inVal {
case "query":
log.Infof("An API key can be provided via a parameter string. Would you like to apply one? (y/N)")
printInfo("An API key can be provided via a parameter string. Would you like to apply one? (y/N)\n")
if quiet {
autoApplyAPIKey = "n"
} else {
@@ -87,11 +86,11 @@ func CheckSecuritySchemes(spec map[string]interface{}) {
}
fmt.Printf("What value would you like to use for the API key (%s)?", apiKeyName)
fmt.Scanln(&apiKey)
log.Infof("Using %s=%s as the API key in all requests.", apiKeyName, apiKey)
printInfo("Using %s=%s as the API key in all requests.\n", apiKeyName, apiKey)
}
case "header":
if mechanism == "bearer" {
log.Infof("A bearer token is accepted. Would you like to provide one? (y/N)")
printInfo("A bearer token is accepted. Would you like to provide one? (y/N)\n")
if quiet {
autoApplyBearer = "n"
} else {
@@ -103,11 +102,11 @@ func CheckSecuritySchemes(spec map[string]interface{}) {
fmt.Scanln(&bearerToken)
Headers = append(Headers, "Authorization: Bearer "+bearerToken)
} else {
log.Warn("A bearer token is accepted. Review the spec and craft a header manually using the -H flag.")
printWarn("A bearer token is accepted. Review the spec and craft a header manually using the -H flag.")
}
} else {
if nameVal, ok := scheme["name"].(string); ok {
log.Infof("An API key can be provided via the header %s. Would you like to apply one? (y/N)", nameVal)
printInfo("An API key can be provided via the header %s. Would you like to apply one? (y/N)\n", nameVal)
if quiet {
autoApplyAPIKey = "n"
} else {
+4 -6
View File
@@ -8,8 +8,6 @@ import (
"time"
"github.com/spf13/cobra"
log "github.com/sirupsen/logrus"
)
var getAccessibleEndpoints bool
@@ -27,7 +25,7 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
Run: func(cmd *cobra.Command, args []string) {
if outfile != "" && strings.ToLower(outputFormat) != "" {
if !strings.HasSuffix(strings.ToLower(outfile), "json") && strings.ToLower(outputFormat) != "json" {
log.Fatal("Only the JSON output format is supported at the moment.")
die("Only the JSON output format is supported at the moment.")
} else if strings.HasSuffix(strings.ToLower(outfile), "json") && strings.ToLower(outputFormat) == "console" {
outputFormat = "json"
}
@@ -41,7 +39,7 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
if randomUserAgent {
if UserAgent != "Swagger Jacker (github.com/BishopFox/sj)" {
log.Warnf("A supplied User Agent was detected (%s) while supplying the 'random-user-agent' flag.", UserAgent)
printWarn("A supplied User Agent was detected (%s) while supplying the 'random-user-agent' flag.", UserAgent)
}
}
@@ -57,7 +55,7 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
if strings.ToLower(outputFormat) != "json" {
fmt.Printf("\n")
log.Infof("Gathering API details.\n")
printInfo("Gathering API details.\n")
}
if swaggerURL != "" {
@@ -65,7 +63,7 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
} else {
specFile, err := os.Open(localFile)
if err != nil {
log.Fatal("Error opening file:", err)
die("Error opening file: %v", err)
}
bodyBytes, _ = io.ReadAll(specFile)
+18 -20
View File
@@ -16,8 +16,6 @@ import (
"github.com/getkin/kin-openapi/openapi3"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
log "github.com/sirupsen/logrus"
)
var endpointOnly bool
@@ -36,13 +34,13 @@ var bruteCmd = &cobra.Command{
/* // NEED TO RE-IMPLEMENT RATE LIMIT
if rateLimit <= 0 {
log.Fatal("Invalid rate supplied. Must be a positive number")
die("Invalid rate supplied. Must be a positive number")
}
*/
if randomUserAgent {
if UserAgent != "Swagger Jacker (github.com/BishopFox/sj)" {
log.Warnf("A supplied User Agent was detected (%s) while supplying the 'random-user-agent' flag.", UserAgent)
printWarn("A supplied User Agent was detected (%s) while supplying the 'random-user-agent' flag.", UserAgent)
}
}
@@ -51,7 +49,7 @@ var bruteCmd = &cobra.Command{
var allURLs []string
u, err := url.Parse(swaggerURL)
if err != nil {
log.Warnf("Error parsing URL:%s\n", err)
printWarn("Error parsing URL: %s", err)
}
target := u.Scheme + "://" + u.Host
if endpointWordlist == "" {
@@ -63,7 +61,7 @@ var bruteCmd = &cobra.Command{
} else {
endpointList, err := os.Open(endpointWordlist)
if err != nil {
log.Fatalf("failed to open file: %s", err)
die("failed to open file: %s", err)
}
defer endpointList.Close()
@@ -75,37 +73,37 @@ var bruteCmd = &cobra.Command{
}
if err := scanner.Err(); err != nil {
log.Fatalf("failed to read words from file: %s", err)
die("failed to read words from file: %s", err)
}
}
if rateLimit > 0 && strings.ToLower(outputFormat) != "json" {
log.Infof("Sending %d requests at a rate of %d requests per second. This could take a while...\n", len(allURLs), rateLimit)
printInfo("Sending %d requests at a rate of %d requests per second. This could take a while...\n", len(allURLs), rateLimit)
} else {
log.Infof("Sending %d requests. This could take a while...\n", len(allURLs))
printInfo("Sending %d requests. This could take a while...\n", len(allURLs))
}
specFound, definitionFile := findDefinitionFile(allURLs, client)
if specFound {
definedOperations, err := json.Marshal(definitionFile)
if err != nil {
log.Errorf("Error parsing definition file:%s\n", err)
printErr("Error parsing definition file: %s", err)
}
if outfile != "" {
file, err := os.OpenFile(outfile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Errorf("Error opening file: %s\n", err)
printErr("Error opening file: %s", err)
}
defer file.Close()
_, err = file.Write(definedOperations)
if err != nil {
log.Errorf("Error writing file: %s\n", err)
printErr("Error writing file: %s", err)
} else {
f, _ := filepath.Abs(outfile)
log.Infof("Wrote file to %s\n", f)
printInfo("Wrote file to %s\n", f)
}
} else {
if endpointOnly {
@@ -116,7 +114,7 @@ var bruteCmd = &cobra.Command{
}
// TODO: Check if (future implementation) automate flag is true and if so than call the 'sj automate' command with the discovered definition file.
} else {
log.Errorf("\nNo definition file found for:\t%s\n", swaggerURL)
printErr("\nNo definition file found for:\t%s", swaggerURL)
}
},
}
@@ -156,7 +154,7 @@ func findDefinitionFile(urls []string, client http.Client) (bool, *openapi3.T) {
checkSpec := UnmarshalSpec(bodyBytes)
if (strings.HasPrefix(checkSpec.OpenAPI, "2") || strings.HasPrefix(checkSpec.OpenAPI, "3")) && checkSpec.Paths != nil {
fmt.Println("")
log.Infof("Definition file found: %s\n", url)
printInfo("Definition file found: %s\n", url)
return true, checkSpec
}
}
@@ -172,7 +170,7 @@ func findDefinitionFile(urls []string, client http.Client) (bool, *openapi3.T) {
jsonContent := match[2]
checkSpec := UnmarshalSpec([]byte(jsonContent))
if strings.HasPrefix(checkSpec.OpenAPI, "2") || strings.HasPrefix(checkSpec.OpenAPI, "3") {
log.Infof("\nFound operation definitions embedded in JavaScript file at %s\n", url)
printInfo("\nFound operation definitions embedded in JavaScript file at %s\n", url)
return true, checkSpec
}
}
@@ -215,7 +213,7 @@ func ExtractSpecFromJS(bodyBytes []byte) []byte {
bodyBytes = []byte(spec[openApiIndex:specClose])
_ = json.Unmarshal(bodyBytes, &doc2)
if !strings.Contains(doc2.Swagger, "2") {
log.Error("Error parsing JavaScript file for spec. Try saving the object as a JSON file and reference it locally.")
printErr("Error parsing JavaScript file for spec. Try saving the object as a JSON file and reference it locally.")
}
}
} else if strings.Contains(strings.ReplaceAll(bodyString, " ", ""), `"openapi":"3`) {
@@ -231,11 +229,11 @@ func ExtractSpecFromJS(bodyBytes []byte) []byte {
bodyBytes = []byte(spec[openApiIndex:specClose])
_ = json.Unmarshal(bodyBytes, &doc3)
if !strings.Contains(doc3.OpenAPI, "3") {
log.Error("Error parsing JavaScript file for spec. Try saving the object as a JSON file and reference it locally.")
printErr("Error parsing JavaScript file for spec. Try saving the object as a JSON file and reference it locally.")
}
}
} else {
log.Error("Error parsing JavaScript file for spec. Try saving the object as a JSON file and reference it locally.")
printErr("Error parsing JavaScript file for spec. Try saving the object as a JSON file and reference it locally.")
}
return bodyBytes
@@ -269,7 +267,7 @@ func UnmarshalSpec(bodyBytes []byte) (newDoc *openapi3.T) {
var noDoc openapi3.T
return &noDoc
} else {
log.Fatal("Error parsing definition file.")
die("Error parsing definition file.")
return nil
}
}
+11 -12
View File
@@ -11,7 +11,6 @@ import (
"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi2conv"
"github.com/getkin/kin-openapi/openapi3"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
@@ -26,7 +25,7 @@ var convertCmd = &cobra.Command{
if randomUserAgent {
if UserAgent != "Swagger Jacker (github.com/BishopFox/sj)" {
log.Warnf("A supplied User Agent was detected (%s) while supplying the 'random-user-agent' flag.", UserAgent)
printWarn("A supplied User Agent was detected (%s) while supplying the 'random-user-agent' flag.", UserAgent)
}
}
@@ -34,7 +33,7 @@ var convertCmd = &cobra.Command{
if strings.ToLower(outputFormat) != "json" {
fmt.Printf("\n")
log.Infof("Gathering API details.\n\n")
printInfo("Gathering API details.\n\n")
}
if swaggerURL != "" {
@@ -42,7 +41,7 @@ var convertCmd = &cobra.Command{
} else {
specFile, err := os.Open(localFile)
if err != nil {
log.Fatal("Error opening definition file:", err)
die("Error opening definition file: %v", err)
}
bodyBytes, _ = io.ReadAll(specFile)
@@ -61,7 +60,7 @@ var convertCmd = &cobra.Command{
}
if strings.HasPrefix(doc3.OpenAPI, "3") {
log.Warnln("Definition file is already version 3.")
printWarn("Definition file is already version 3.")
WriteConvertedDefinitionFile(bodyBytes)
} else if strings.HasPrefix(doc.Swagger, "2") {
newDoc, err := openapi2conv.ToV3(&doc)
@@ -72,11 +71,11 @@ var convertCmd = &cobra.Command{
switch format {
case "json":
if strings.HasSuffix(outfile, "yaml") || strings.HasSuffix(outfile, "yml") {
log.Warn("It looks like you're trying to save the file in YAML format. Supply the '-f yaml' option to do so (default: json).")
printWarn("It looks like you're trying to save the file in YAML format. Supply the '-f yaml' option to do so (default: json).")
}
converted, err := json.Marshal(newDoc)
if err != nil {
log.Fatal("Error converting definition file to v3:", err)
die("Error converting definition file to v3: %v", err)
}
if !strings.HasSuffix(string(converted), "}") {
if outfile == "" {
@@ -95,7 +94,7 @@ var convertCmd = &cobra.Command{
case "yaml", "yml":
converted, err := yaml.Marshal(newDoc)
if err != nil {
log.Fatal("Error converting definition file to v3:", err)
die("Error converting definition file to v3: %v", err)
}
if outfile == "" {
fmt.Println(string(converted))
@@ -105,7 +104,7 @@ var convertCmd = &cobra.Command{
}
} else {
log.Fatal("Error parsing definition file.")
die("Error parsing definition file.")
}
},
}
@@ -113,16 +112,16 @@ var convertCmd = &cobra.Command{
func WriteConvertedDefinitionFile(data []byte) {
file, err := os.OpenFile(outfile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Errorf("Error opening file: %s\n", err)
printErr("Error opening file: %s", err)
}
defer file.Close()
_, err = file.Write(data)
if err != nil {
log.Errorf("Error writing file: %s\n", err)
printErr("Error writing file: %s", err)
} else {
f, _ := filepath.Abs(outfile)
log.Infof("Wrote file to %s\n", f)
printInfo("Wrote file to %s\n", f)
}
}
+3 -4
View File
@@ -5,7 +5,6 @@ import (
"io"
"os"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -17,7 +16,7 @@ This list contains the raw endpoints (parameter values will not be appended or m
Run: func(cmd *cobra.Command, args []string) {
if randomUserAgent {
if UserAgent != "Swagger Jacker (github.com/BishopFox/sj)" {
log.Warnf("A supplied User Agent was detected (%s) while supplying the 'random-user-agent' flag.", UserAgent)
printWarn("A supplied User Agent was detected (%s) while supplying the 'random-user-agent' flag.", UserAgent)
}
}
@@ -26,7 +25,7 @@ This list contains the raw endpoints (parameter values will not be appended or m
var bodyBytes []byte
fmt.Printf("\n")
log.Infof("Gathering endpoints.\n\n")
printInfo("Gathering endpoints.\n\n")
if swaggerURL != "" {
bodyBytes, _, _ = MakeRequest(client, "GET", swaggerURL, timeout, nil)
@@ -34,7 +33,7 @@ This list contains the raw endpoints (parameter values will not be appended or m
} else {
specFile, err := os.Open(localFile)
if err != nil {
log.Fatal("Error opening file:", err)
die("Error opening file: %v", err)
}
bodyBytes, _ = io.ReadAll(specFile)
GenerateRequests(bodyBytes, client)
+105 -183
View File
@@ -1,11 +1,12 @@
package cmd
import (
"encoding/json"
"fmt"
"io"
"os"
"strings"
log "github.com/sirupsen/logrus"
"github.com/fatih/color"
)
type Result struct {
@@ -22,11 +23,35 @@ type VerboseResult struct {
Curl string `json:"curl"`
}
var tempLogger *log.Logger
// Diagnostic helpers — all write to stderr so stdout stays clean for piping.
var green = color.New(color.FgGreen, color.Bold).SprintFunc()
var yellow = color.New(color.FgYellow, color.Bold).SprintFunc()
var red = color.New(color.FgRed, color.Bold).SprintFunc()
var faint = color.New(color.Faint).SprintFunc()
func printInfo(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format, args...)
}
func printWarn(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
fmt.Fprintf(os.Stderr, "%s %s\n", yellow("[!]"), msg)
}
func printErr(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
fmt.Fprintf(os.Stderr, "%s %s\n", red("[✗]"), msg)
}
func die(format string, args ...interface{}) {
printErr(format, args...)
os.Exit(1)
}
// writeLog is the main dispatch function for endpoint results.
func writeLog(sc int, target, method, errorMsg, response string) {
var file *os.File
tempLogger = log.New()
var out io.Writer = os.Stdout
tempResponsePreviewLength := responsePreviewLength
if len(response) < responsePreviewLength {
@@ -34,205 +59,102 @@ func writeLog(sc int, target, method, errorMsg, response string) {
}
if outfile != "" {
var err error
file, err = os.OpenFile(outfile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644)
file, err := os.OpenFile(outfile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
fmt.Println("Output file does not exist or cannot be created")
fmt.Fprintln(os.Stderr, "Output file does not exist or cannot be created")
os.Exit(1)
}
defer file.Close()
tempLogger.SetOutput(file)
}
if outputFormat == "console" {
if strings.Contains(response, "\"") {
tempLogger.SetFormatter(&log.TextFormatter{DisableQuote: true, DisableTimestamp: true})
} else {
tempLogger.SetFormatter(&log.TextFormatter{DisableTimestamp: true})
}
} else {
tempLogger.SetFormatter(&log.JSONFormatter{DisableHTMLEscape: true, DisableTimestamp: true, PrettyPrint: true})
out = file
// Disable color when writing to a file.
color.NoColor = true
defer func() { color.NoColor = false }()
}
preview := ""
if verbose {
switch sc {
case 0:
logVerboseBad(sc, target, method, response, tempLogger)
case 1:
logDangerous(target, method, tempLogger)
case 200:
logVerboseAccessible(sc, target, method, response, tempLogger)
case 301:
logVerboseRedirect(sc, target, method, response, tempLogger)
case 302:
logVerboseRedirect(sc, target, method, response, tempLogger)
case 401:
logVerboseUnauth(sc, target, method, errorMsg, response, tempLogger)
case 403:
logVerboseUnauth(sc, target, method, errorMsg, response, tempLogger)
case 404:
logNotFound(sc, target, method, errorMsg, tempLogger)
case 8899:
logVerboseJSON(specTitle, specDescription)
default:
logVerboseManual(sc, target, method, errorMsg, response, tempLogger)
}
} else {
switch sc {
case 0:
logBad(sc, target, method, tempLogger)
case 1:
logDangerous(target, method, tempLogger)
case 200:
logAccessible(sc, target, method, tempLogger)
case 301:
logRedirect(sc, target, method, tempLogger)
case 302:
logRedirect(sc, target, method, tempLogger)
case 401:
logUnauth(sc, target, method, errorMsg, tempLogger)
case 403:
logUnauth(sc, target, method, errorMsg, tempLogger)
case 404:
logNotFound(sc, target, method, errorMsg, tempLogger)
case 8899:
logJSON(specTitle, specDescription)
default:
logManual(sc, target, method, errorMsg, tempLogger)
}
preview = response[:responsePreviewLength]
}
switch sc {
case 8899:
if verbose {
logVerboseJSON(specTitle, specDescription, out)
} else {
logJSON(specTitle, specDescription, out)
}
default:
logResult(sc, target, method, errorMsg, preview, out)
}
responsePreviewLength = tempResponsePreviewLength
}
func logAccessible(status int, target, method string, logger *log.Logger) {
logger.WithFields(log.Fields{
"Status": status,
"Target": target,
"Method": method,
}).Print("Endpoint accessible!")
}
// logResult renders a single endpoint result line.
func logResult(sc int, target, method, errorMsg, preview string, out io.Writer) {
var sym string
var painter func(a ...interface{}) string
func logVerboseAccessible(status int, target, method, response string, logger *log.Logger) {
logger.WithFields(log.Fields{
"Status": status,
"Target": target,
"Method": method,
"Preview": response[:responsePreviewLength],
}).Print("Endpoint accessible!")
}
func logDangerous(target, method string, logger *log.Logger) {
logger.WithFields(log.Fields{
"Status": "skipped",
"Target": target,
"Method": method,
}).Warn("Endpoint skipped due to dangerous keyword (or request cancelled due to timeout).")
}
func logManual(status int, target, method, errorMsg string, logger *log.Logger) {
if errorMsg == "" {
errorMsg = "Manual testing may be required."
switch sc {
case 200:
sym = "✓"
painter = green
case 301, 302, 0, 1:
sym = "⚠"
painter = yellow
case 401, 403, 404:
sym = "✗"
painter = red
default:
sym = "⚠"
painter = yellow
}
logger.WithFields(log.Fields{
"Status": status,
"Target": target,
"Method": method,
}).Warn(errorMsg)
}
func logVerboseManual(status int, target, method, errorMsg, response string, logger *log.Logger) {
if errorMsg == "" {
errorMsg = "Manual testing may be required."
statusStr := fmt.Sprintf("%d", sc)
switch sc {
case 0:
statusStr = "N/A"
case 1:
statusStr = "---"
}
logger.WithFields(log.Fields{
"Status": status,
"Target": target,
"Method": method,
"Preview": response[:responsePreviewLength],
}).Warn(errorMsg)
}
func logNotFound(status int, target, method, errorMsg string, logger *log.Logger) {
if errorMsg == "" {
errorMsg = "Endpoint not found."
line := fmt.Sprintf("%s %-7s %-3s %s\n",
painter(sym),
painter(method),
painter(statusStr),
target,
)
fmt.Fprint(out, line)
if preview != "" {
fmt.Fprintf(out, " %s\n", faint(preview))
}
logger.WithFields(log.Fields{
"Status": status,
"Target": target,
"Method": method,
}).Error(errorMsg)
}
func logRedirect(status int, target, method string, logger *log.Logger) {
logger.WithFields(log.Fields{
"Status": status,
"Target": target,
"Method": method,
}).Error("Redirect detected. This likely requires authentication.")
}
func logVerboseRedirect(status int, target, method, response string, logger *log.Logger) {
logger.WithFields(log.Fields{
"Status": status,
"Target": target,
"Method": method,
"Preview": response[0:responsePreviewLength],
}).Error("Redirect detected. This likely requires authentication.")
}
func logBad(status int, target, method string, logger *log.Logger) {
logger.WithFields(log.Fields{
"Status": "N/A",
"Target": target,
"Method": method,
}).Warn("Bad request (could not reach the target).")
}
func logVerboseBad(status int, target, method, response string, logger *log.Logger) {
logger.WithFields(log.Fields{
"Status": "N/A",
"Target": target,
"Method": method,
"Preview": response[:responsePreviewLength],
}).Warn("Bad request (could not reach the target).")
}
func logUnauth(status int, target, method, errorMsg string, logger *log.Logger) {
if errorMsg == "" {
errorMsg = "Unauthorized."
func logJSON(title, description string, out io.Writer) {
output := struct {
APITitle string `json:"apiTitle"`
Description string `json:"description"`
Results []Result `json:"results"`
}{
APITitle: title,
Description: description,
Results: jsonResultArray,
}
logger.WithFields(log.Fields{
"Status": status,
"Target": target,
"Method": method,
}).Error(errorMsg)
data, _ := json.MarshalIndent(output, "", " ")
fmt.Fprintln(out, string(data))
}
func logVerboseUnauth(status int, target, method, errorMsg, response string, logger *log.Logger) {
if errorMsg == "" {
errorMsg = "Unauthorized."
func logVerboseJSON(title, description string, out io.Writer) {
output := struct {
APITitle string `json:"apiTitle"`
Description string `json:"description"`
Results []VerboseResult `json:"results"`
}{
APITitle: title,
Description: description,
Results: jsonVerboseResultArray,
}
logger.WithFields(log.Fields{
"Status": status,
"Target": target,
"Method": method,
"Preview": response[:responsePreviewLength],
}).Error(errorMsg)
}
func logJSON(title, description string) {
tempLogger.WithFields(log.Fields{
"apiTitle": title,
"description": description,
"results": jsonResultArray,
}).Println("Done")
}
func logVerboseJSON(title, description string) {
tempLogger.WithFields(log.Fields{
"apiTitle": title,
"description": description,
"results": jsonVerboseResultArray,
}).Println("Done")
data, _ := json.MarshalIndent(output, "", " ")
fmt.Fprintln(out, string(data))
}
+3 -4
View File
@@ -6,7 +6,6 @@ import (
"os"
"time"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -19,7 +18,7 @@ This enables you to test specific API functions for common vulnerabilities or mi
if randomUserAgent {
if UserAgent != "Swagger Jacker (github.com/BishopFox/sj)" {
log.Warnf("A supplied User Agent was detected (%s) while supplying the 'random-user-agent' flag.", UserAgent)
printWarn("A supplied User Agent was detected (%s) while supplying the 'random-user-agent' flag.", UserAgent)
}
}
@@ -32,14 +31,14 @@ This enables you to test specific API functions for common vulnerabilities or mi
client := CheckAndConfigureProxy()
fmt.Printf("\n")
log.Infof("Gathering API details.\n\n")
printInfo("Gathering API details.\n\n")
if swaggerURL != "" {
bodyBytes, _, _ := MakeRequest(client, "GET", swaggerURL, timeout, nil)
GenerateRequests(bodyBytes, client)
} else {
specFile, err := os.Open(localFile)
if err != nil {
log.Fatal("Error opening file:", err)
die("Error opening file: %v", err)
}
specBytes, _ := io.ReadAll(specFile)
+15 -16
View File
@@ -12,7 +12,6 @@ import (
"strings"
"time"
log "github.com/sirupsen/logrus"
)
var (
@@ -62,7 +61,7 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
// Handling of dangerous keywords
u, err := url.Parse(target)
if err != nil || u == nil {
log.Printf("Error parsing URL '%s': %v - skipping request.", target, err)
printWarn("Error parsing URL '%s': %v - skipping request.", target, err)
return nil, "", 0
}
endpoint := u.RawPath + "?" + u.RawQuery
@@ -72,12 +71,12 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
if avoidDangerousRequests == "y" {
return nil, "", 0
} else {
log.Warnf("Dangerous keyword '%s' detected in URL (%s). Do you still want to test this endpoint? (y/N)", v, target)
printWarn("Dangerous keyword '%s' detected in URL (%s). Do you still want to test this endpoint? (y/N)", v, target)
fmt.Scanln(&userChoice)
if strings.ToLower(userChoice) != "y" {
if !riskSurveyed {
avoidDangerousRequests = "y"
log.Warnf("Do you want to avoid all dangerous requests? (Y/n)")
printWarn("Do you want to avoid all dangerous requests? (Y/n)")
fmt.Scanln(&avoidDangerousRequests)
avoidDangerousRequests = strings.ToLower(avoidDangerousRequests)
riskSurveyed = true
@@ -94,7 +93,7 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
req, err := http.NewRequest(method, target, reqData)
if err != nil {
if err != context.Canceled && err != io.EOF {
log.Fatal("Error: could not create HTTP request - ", err)
die("Error: could not create HTTP request - %v", err)
}
return nil, "", 0
}
@@ -102,7 +101,7 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
for i := range Headers {
delimIndex := strings.Index(Headers[i], ":")
if delimIndex == -1 {
log.Warnf("Header provided (%s) cannot be used. Headers must be in 'Key: Value' format (this may be caused by a header declared within the definition file).\n", Headers[i])
printWarn("Header provided (%s) cannot be used. Headers must be in 'Key: Value' format (this may be caused by a header declared within the definition file).", Headers[i])
continue
}
@@ -146,17 +145,17 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
resp, err := client.Do(req.WithContext(ctx))
if err == context.DeadlineExceeded {
log.Printf("Error: %s - skipping request.", err)
printWarn("Error: %s - skipping request.", err)
return nil, "", 0
} else if err != nil && err != context.Canceled && err != io.EOF {
if (strings.Contains(fmt.Sprint(err), "tls") || strings.Contains(fmt.Sprint(err), "x509")) && !strings.Contains(fmt.Sprint(err), "user canceled") {
log.Fatal("Try supplying the --insecure flag.")
die("Try supplying the --insecure flag.")
} else if strings.Contains(fmt.Sprint(err), "tcp") && strings.Contains(fmt.Sprint(err), "no such host") {
log.Fatalf("The target '%s' is not reachable. Check the declared host(s) and supply a target manually using -T if needed.", u.Scheme+"://"+u.Host)
die("The target '%s' is not reachable. Check the declared host(s) and supply a target manually using -T if needed.", u.Scheme+"://"+u.Host)
} else if strings.Contains(fmt.Sprint(err), "user canceled") {
return nil, "skipped", 1
} else {
log.Error("Error: response not received.\n", err)
printErr("Error: response not received.\n%v", err)
}
return nil, "", 0
}
@@ -180,7 +179,7 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
func CheckContentType(client http.Client, target string) string {
u, err := url.Parse(target)
if err != nil || u == nil {
log.Printf("Error parsing URL '%s': %v - skipping request.", target, err)
printWarn("Error parsing URL '%s': %v - skipping request.", target, err)
return ""
}
@@ -190,7 +189,7 @@ func CheckContentType(client http.Client, target string) string {
req, err := http.NewRequest("GET", target, nil)
if err != nil {
if err != context.Canceled && err != io.EOF {
log.Fatal("Error: could not create HTTP request - ", err)
die("Error: could not create HTTP request - %v", err)
}
return ""
}
@@ -206,15 +205,15 @@ func CheckContentType(client http.Client, target string) string {
resp, err := client.Do(req.WithContext(ctx))
if err == context.DeadlineExceeded {
log.Printf("Error: %s - skipping request.", err)
printWarn("Error: %s - skipping request.", err)
return ""
} else if err != nil && err != context.Canceled && err != io.EOF {
if (strings.Contains(fmt.Sprint(err), "tls") || strings.Contains(fmt.Sprint(err), "x509")) && !strings.Contains(fmt.Sprint(err), "user canceled") {
log.Fatal("Try supplying the --insecure flag.")
die("Try supplying the --insecure flag.")
} else if strings.Contains(fmt.Sprint(err), "tcp") && strings.Contains(fmt.Sprint(err), "no such host") {
log.Fatalf("The target '%s' is not reachable. Check the declared host(s) and supply a target manually using -T if needed.", u.Scheme+"://"+u.Host)
die("The target '%s' is not reachable. Check the declared host(s) and supply a target manually using -T if needed.", u.Scheme+"://"+u.Host)
} else {
log.Error("Error: response not received.\n", err)
printErr("Error: response not received.\n%v", err)
}
return ""
}
+2 -3
View File
@@ -1,7 +1,6 @@
package cmd
import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -49,10 +48,10 @@ $ sj convert -u https://petstore.swagger.io/v2/swagger.json -o openapi.json`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
log.Error("Command not specified. See the --help flag for usage.")
printErr("Command not specified. See the --help flag for usage.")
}
},
Version: "2.3.2",
Version: "2.4.1",
}
func Execute() {
+21 -10
View File
@@ -10,7 +10,6 @@ import (
"slices"
"strings"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
@@ -34,13 +33,26 @@ type SchemaNode struct {
func BuildRequestsFromPaths(spec map[string]interface{}, client http.Client) {
paths, ok := spec["paths"].(map[string]interface{})
if !ok || paths == nil {
log.Fatalf("Could not find any defined operations. Review the file manually.")
die("Could not find any defined operations. Review the file manually.")
}
pathKeys := make([]string, 0, len(paths))
for k := range paths {
pathKeys = append(pathKeys, k)
}
slices.Sort(pathKeys)
var errorDescriptions = make(map[any]string)
for pathName, pathItem := range paths {
for _, pathName := range pathKeys {
pathItem := paths[pathName]
if ops, ok := pathItem.(map[string]interface{}); ok {
for method, op := range ops {
methodKeys := make([]string, 0, len(ops))
for k := range ops {
methodKeys = append(methodKeys, k)
}
slices.Sort(methodKeys)
for _, method := range methodKeys {
op := ops[method]
switch strings.ToLower(method) {
// SKIPS THE "DELETE" AND "PATCH" METHODS FOR SAFETY
case "delete":
@@ -200,7 +212,7 @@ func BuildRequestsFromPaths(spec map[string]interface{}, client http.Client) {
logURL, parseErr := url.Parse(targetURL)
if parseErr != nil || logURL == nil {
log.Printf("Error parsing URL '%s': %v - skipping endpoint.", targetURL, parseErr)
printWarn("Error parsing URL '%s': %v - skipping endpoint.", targetURL, parseErr)
continue
}
switch os.Args[1] {
@@ -271,20 +283,19 @@ func BuildRequestsFromPaths(spec map[string]interface{}, client http.Client) {
}
}
if os.Args[1] == "automate" && outputFormat == "json" {
slices.Sort(jsonResultsStringArray)
for r := range jsonResultsStringArray {
var result Result
var verboseResult VerboseResult
if verbose {
err := json.Unmarshal([]byte(strings.TrimPrefix(jsonResultsStringArray[r], ",")), &verboseResult)
if err != nil {
log.Fatal("Error marshalling JSON:", err)
die("Error marshalling JSON: %v", err)
}
jsonVerboseResultArray = append(jsonVerboseResultArray, verboseResult)
} else {
err := json.Unmarshal([]byte(strings.TrimPrefix(jsonResultsStringArray[r], ",")), &result)
if err != nil {
log.Fatal("Error marshalling JSON:", err)
die("Error marshalling JSON: %v", err)
}
jsonResultArray = append(jsonResultArray, result)
}
@@ -436,7 +447,7 @@ func GenerateRequests(bodyBytes []byte, client http.Client) {
if servers, ok := spec["servers"].([]interface{}); ok && len(servers) > 0 {
if len(servers) > 1 {
if !quiet && (os.Args[1] != "endpoints") && apiTarget == "" {
log.Warn("Multiple servers detected in documentation. You can manually set a server to test with the -T flag.\nThe detected servers are as follows:")
printWarn("Multiple servers detected in documentation. You can manually set a server to test with the -T flag.\nThe detected servers are as follows:")
for i := range servers {
if srv, ok := servers[i].(map[string]interface{}); ok {
if serverURL, ok := srv["url"].(string); ok {
@@ -504,7 +515,7 @@ func ResolveRef(spec map[string]interface{}, ref string) map[string]interface{}
func PrintSpecInfo(spec map[string]interface{}) {
info, ok := spec["info"].(map[string]interface{})
if !ok || info == nil {
log.Info("No information defined in the documentation.")
printInfo("No information defined in the documentation.\n")
} else {
title, ok := info["title"].(string)
if ok && title != "" {
+4 -2
View File
@@ -5,8 +5,8 @@ go 1.22.5
toolchain go1.23.4
require (
github.com/fatih/color v1.18.0
github.com/getkin/kin-openapi v0.132.0
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1
gopkg.in/yaml.v3 v3.0.1
)
@@ -17,10 +17,12 @@ require (
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
golang.org/x/sys v0.25.0 // indirect
)
+11 -8
View File
@@ -1,7 +1,8 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
@@ -20,6 +21,11 @@ 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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
@@ -33,23 +39,20 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
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/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=