mirror of
https://github.com/BishopFox/sj.git
synced 2026-11-07 10:26:01 -10:00
Made some improvements for greater flexibility when encountering improperly defined API specifications
This commit is contained in:
1 parent
fd602b9ca8
commit
77b11f40e6
12 files changed
+661
-978
No files matched your search
+101
-86
@@ -5,8 +5,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -21,96 +19,113 @@ var (
|
||||
bearerToken string
|
||||
)
|
||||
|
||||
func CheckSecDefs(doc3 openapi3.T) (apiInQuery bool, apiKey string, apiKeyName string) {
|
||||
|
||||
if doc3.Components == nil || doc3.Components.SecuritySchemes == nil || len(doc3.Components.SecuritySchemes) == 0 {
|
||||
if !quiet {
|
||||
log.Warn("No security schemes defined in the OpenAPI specification.")
|
||||
}
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
if outputFormat == "json" && !quiet {
|
||||
if len(doc3.Components.SecuritySchemes) != 0 {
|
||||
log.Warnf("The following authentication mechanisms are supported. If necessary, supply these manually when using the JSON output format:\n")
|
||||
}
|
||||
for mechanism := range doc3.Components.SecuritySchemes {
|
||||
log.Infof(`Type: %s, Scheme: %s`, doc3.Components.SecuritySchemes[mechanism].Value.Type, doc3.Components.SecuritySchemes[mechanism].Value.Scheme)
|
||||
}
|
||||
log.Warnln("Note: To avoid these messages, pass the quiet (-q) flag.")
|
||||
} else if !quiet {
|
||||
if len(doc3.Components.SecuritySchemes) > 0 {
|
||||
log.Info("Available authentication mechanisms: ")
|
||||
}
|
||||
|
||||
for mechanism := range doc3.Components.SecuritySchemes {
|
||||
if doc3.Components.SecuritySchemes[mechanism].Value == nil {
|
||||
log.Error("Authorization schema structure unsupported. Manual testing is required.")
|
||||
return false, "", ""
|
||||
}
|
||||
if doc3.Components.SecuritySchemes[mechanism].Value.Scheme != "" {
|
||||
fmt.Printf(" - %s (%s)\n", mechanism, doc3.Components.SecuritySchemes[mechanism].Value.Scheme)
|
||||
} else if outputFormat != "json" {
|
||||
fmt.Printf(" - %s\n", mechanism)
|
||||
func CheckSecuritySchemes(spec map[string]interface{}) {
|
||||
components, ok := spec["components"].(map[string]interface{})
|
||||
if ok && components != nil {
|
||||
securitySchemes, ok := components["securitySchemes"].(map[string]interface{})
|
||||
if !ok || len(securitySchemes) == 0 {
|
||||
fmt.Println("No security schemes defined.")
|
||||
} else {
|
||||
if outputFormat != "json" {
|
||||
fmt.Println("Found security schemes:")
|
||||
}
|
||||
var apiKey string
|
||||
var apiKeyName string
|
||||
|
||||
if doc3.Components.SecuritySchemes[mechanism].Value.Type == "http" {
|
||||
if doc3.Components.SecuritySchemes[mechanism].Value.Scheme == "basic" {
|
||||
log.Infof("Basic Authentication is accepted. Supply a username and password? (y/N)")
|
||||
fmt.Scanln(&autoApplyBasicAuth)
|
||||
autoApplyBasicAuth = strings.ToLower(autoApplyBasicAuth)
|
||||
if autoApplyBasicAuth == "y" {
|
||||
fmt.Printf("Enter a username.")
|
||||
fmt.Scanln(&basicAuthUser)
|
||||
fmt.Printf("Enter a password.")
|
||||
fmt.Scanln(&basicAuthPass)
|
||||
basicAuth = []byte(basicAuthUser + ":" + basicAuthPass)
|
||||
basicAuthString = base64.StdEncoding.EncodeToString(basicAuth)
|
||||
log.Infof("Using %s as the Basic Auth value.", 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.")
|
||||
}
|
||||
} else if strings.ToLower(doc3.Components.SecuritySchemes[mechanism].Value.Scheme) == "bearer" {
|
||||
log.Warn("A bearer token is accepted. Review the spec and craft a token manually using the -H flag.")
|
||||
for mechanism, value := range securitySchemes {
|
||||
fmt.Printf(" - %s\n", mechanism)
|
||||
scheme, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
} else if doc3.Components.SecuritySchemes[mechanism].Value.Type == "apiKey" && doc3.Components.SecuritySchemes[mechanism].Value.In == "query" {
|
||||
apiInQuery = true
|
||||
log.Infof("An API key can be provided via a parameter string. Would you like to apply one? (y/N)")
|
||||
fmt.Scanln(&autoApplyAPIKey)
|
||||
autoApplyAPIKey = strings.ToLower(autoApplyAPIKey)
|
||||
if autoApplyAPIKey == "y" {
|
||||
apiKeyName = doc3.Components.SecuritySchemes[mechanism].Value.Name
|
||||
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)
|
||||
|
||||
if typ, ok := scheme["type"].(string); ok {
|
||||
switch typ {
|
||||
case "http":
|
||||
if schemeType := scheme["scheme"]; schemeType != nil {
|
||||
switch schemeType {
|
||||
case "basic":
|
||||
fmt.Println("Basic Authentication is accepted. Supply a username and password? (y/N)")
|
||||
fmt.Scanln(&autoApplyBasicAuth)
|
||||
autoApplyBasicAuth = strings.ToLower(autoApplyBasicAuth)
|
||||
if autoApplyBasicAuth == "y" {
|
||||
fmt.Printf("Enter a username.")
|
||||
fmt.Scanln(&basicAuthUser)
|
||||
fmt.Printf("Enter a password.")
|
||||
fmt.Scanln(&basicAuthPass)
|
||||
basicAuth = []byte(basicAuthUser + ":" + basicAuthPass)
|
||||
basicAuthString = base64.StdEncoding.EncodeToString(basicAuth)
|
||||
log.Infof("Using %s as the Basic Auth value.", 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.")
|
||||
}
|
||||
case "bearer":
|
||||
log.Warn("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)")
|
||||
if quiet {
|
||||
autoApplyAPIKey = "n"
|
||||
} else {
|
||||
fmt.Scanln(&autoApplyAPIKey)
|
||||
autoApplyAPIKey = strings.ToLower(autoApplyAPIKey)
|
||||
}
|
||||
|
||||
if autoApplyAPIKey == "y" {
|
||||
if nameVal, ok := scheme["name"].(string); ok {
|
||||
apiKeyName = nameVal
|
||||
}
|
||||
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)
|
||||
}
|
||||
case "header":
|
||||
if mechanism == "bearer" {
|
||||
log.Infof("A bearer token is accepted. Would you like to provide one? (y/N)")
|
||||
if quiet {
|
||||
autoApplyBearer = "n"
|
||||
} else {
|
||||
fmt.Scanln(&autoApplyBearer)
|
||||
autoApplyBearer = strings.ToLower(autoApplyBearer)
|
||||
}
|
||||
if autoApplyBearer == "y" {
|
||||
fmt.Printf("What value would you like to use for the Bearer Token? ")
|
||||
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.")
|
||||
}
|
||||
} 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)
|
||||
if quiet {
|
||||
autoApplyAPIKey = "n"
|
||||
} else {
|
||||
fmt.Scanln(&autoApplyAPIKey)
|
||||
autoApplyAPIKey = strings.ToLower(autoApplyAPIKey)
|
||||
}
|
||||
if autoApplyAPIKey == "y" {
|
||||
apiKeyName = nameVal
|
||||
fmt.Printf("What value would you like to use for the API key (%s)?", apiKeyName)
|
||||
fmt.Scanln(&apiKey)
|
||||
Headers = append(Headers, nameVal+": "+apiKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if doc3.Components.SecuritySchemes[mechanism].Value.Type == "apiKey" && doc3.Components.SecuritySchemes[mechanism].Value.In == "header" {
|
||||
if mechanism == "bearer" {
|
||||
log.Infof("A bearer token is accepted. Would you like to provide one? (y/N)")
|
||||
fmt.Scanln(&autoApplyBearer)
|
||||
autoApplyBearer = strings.ToLower(autoApplyBearer)
|
||||
if autoApplyBearer == "y" {
|
||||
fmt.Printf("What value would you like to use for the Bearer Token? ")
|
||||
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.")
|
||||
}
|
||||
} else {
|
||||
log.Infof("An API key can be provided via the header %s. Would you like to apply one? (y/N)", doc3.Components.SecuritySchemes[mechanism].Value.Name)
|
||||
fmt.Scanln(&autoApplyAPIKey)
|
||||
autoApplyAPIKey = strings.ToLower(autoApplyAPIKey)
|
||||
if autoApplyAPIKey == "y" {
|
||||
apiKeyName = doc3.Components.SecuritySchemes[mechanism].Value.Name
|
||||
fmt.Printf("What value would you like to use for the API key (%s)?", apiKeyName)
|
||||
fmt.Scanln(&apiKey)
|
||||
Headers = append(Headers, doc3.Components.SecuritySchemes[mechanism].Value.Name+": "+apiKey)
|
||||
}
|
||||
|
||||
if bearerFormat, ok := scheme["bearerFormat"].(string); ok {
|
||||
fmt.Println(" - bearerFormat:", bearerFormat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
return apiInQuery, apiKey, apiKeyName
|
||||
}
|
||||
@@ -31,9 +31,11 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
|
||||
}
|
||||
}
|
||||
|
||||
/* // NEED TO RE-IMPLEMENT RATE LIMIT
|
||||
if rateLimit <= 0 {
|
||||
log.Fatal("Invalid rate supplied. Must be a positive number")
|
||||
}
|
||||
*/
|
||||
|
||||
if randomUserAgent {
|
||||
if UserAgent != "Swagger Jacker (github.com/BishopFox/sj)" {
|
||||
@@ -60,9 +62,11 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
|
||||
|
||||
bodyBytes, _ = io.ReadAll(specFile)
|
||||
}
|
||||
/* // NEED TO RE-IMPLEMENT RATE LIMIT
|
||||
if rateLimit > 0 && strings.ToLower(outputFormat) != "json" {
|
||||
log.Info("Sending requests at a rate of ", rateLimit, " requests per second.")
|
||||
}
|
||||
*/
|
||||
GenerateRequests(bodyBytes, client)
|
||||
},
|
||||
}
|
||||
|
||||
+121
-39
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -11,15 +10,17 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi2"
|
||||
"github.com/getkin/kin-openapi/openapi2conv"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/time/rate"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var endpointOnly bool
|
||||
var endpointWordlist string
|
||||
|
||||
var prefixDirs []string = []string{"", "/swagger", "/swagger/docs", "/swagger/latest", "/swagger/v1", "/swagger/v2", "/swagger/v3", "/swagger/static", "/swagger/ui", "/swagger-ui", "/api-docs", "/api-docs/v1", "/api-docs/v2", "/apidocs", "/api", "/api/v1", "/api/v2", "/api/v3", "/v1", "/v2", "/v3", "/doc", "/docs", "/docs/swagger", "/docs/swagger/v1", "/docs/swagger/v2", "/docs/swagger-ui", "/docs/swagger-ui/v1", "/docs/swagger-ui/v2", "/docs/v1", "/docs/v2", "/docs/v3", "/public", "/redoc"}
|
||||
@@ -32,9 +33,11 @@ var bruteCmd = &cobra.Command{
|
||||
Long: `The brute command sends requests to the target to find operation definitions based on commonly used file locations.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
/* // NEED TO RE-IMPLEMENT RATE LIMIT
|
||||
if rateLimit <= 0 {
|
||||
log.Fatal("Invalid rate supplied. Must be a positive number")
|
||||
}
|
||||
*/
|
||||
|
||||
if randomUserAgent {
|
||||
if UserAgent != "Swagger Jacker (github.com/BishopFox/sj)" {
|
||||
@@ -103,7 +106,11 @@ var bruteCmd = &cobra.Command{
|
||||
log.Infof("Wrote file to %s\n", f)
|
||||
}
|
||||
} else {
|
||||
fmt.Println(string(definedOperations))
|
||||
if endpointOnly {
|
||||
return
|
||||
} else {
|
||||
fmt.Println(string(definedOperations))
|
||||
}
|
||||
}
|
||||
// TODO: Check if (future implementation) automate flag is true and if so than call the 'sj automate' command with the discovered definition file.
|
||||
} else {
|
||||
@@ -128,45 +135,38 @@ func makeURLs(target string, endpoints []string, fileExtension string) []string
|
||||
}
|
||||
|
||||
func findDefinitionFile(urls []string, client http.Client) (bool, *openapi3.T) {
|
||||
var rateLimiter = rate.NewLimiter(rate.Every(time.Second/(time.Duration(rateLimit))), 1)
|
||||
|
||||
for i, url := range urls {
|
||||
if rateLimit > 0 {
|
||||
if err := rateLimiter.Wait(context.Background()); err != nil {
|
||||
fmt.Println("Rate limit error...")
|
||||
} else {
|
||||
ct := CheckContentType(client, url)
|
||||
if strings.Contains(ct, "application/json") {
|
||||
bodyBytes, _, _ := MakeRequest(client, "GET", url, timeout, nil)
|
||||
if bodyBytes != nil {
|
||||
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)
|
||||
return true, checkSpec
|
||||
}
|
||||
}
|
||||
} else if strings.Contains(ct, "application/javascript") {
|
||||
bodyBytes, bodyString, _ := MakeRequest(client, "GET", url, timeout, nil)
|
||||
if bodyBytes != nil {
|
||||
regexPattern := regexp.MustCompile(`(?s)let\s+(\w+)\s*=\s*({.*?});`)
|
||||
matches := regexPattern.FindAllStringSubmatch(bodyString, -1)
|
||||
for _, match := range matches {
|
||||
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)
|
||||
return true, checkSpec
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if i == len(urls) {
|
||||
fmt.Printf("\033[2K\r%s%d\n", "Request: ", i+1)
|
||||
} else {
|
||||
fmt.Printf("\033[2K\r%s%d", "Request: ", i+1)
|
||||
ct := CheckContentType(client, url)
|
||||
if strings.Contains(ct, "application/json") {
|
||||
bodyBytes, _, _ := MakeRequest(client, "GET", url, timeout, nil)
|
||||
if bodyBytes != nil {
|
||||
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)
|
||||
return true, checkSpec
|
||||
}
|
||||
}
|
||||
} else if strings.Contains(ct, "application/javascript") {
|
||||
bodyBytes, bodyString, _ := MakeRequest(client, "GET", url, timeout, nil)
|
||||
if bodyBytes != nil {
|
||||
regexPattern := regexp.MustCompile(`(?s)let\s+(\w+)\s*=\s*({.*?});`)
|
||||
matches := regexPattern.FindAllStringSubmatch(bodyString, -1)
|
||||
for _, match := range matches {
|
||||
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)
|
||||
return true, checkSpec
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if i == len(urls) {
|
||||
fmt.Printf("\033[2K\r%s%d\n", "Request: ", i+1)
|
||||
} else {
|
||||
fmt.Printf("\033[2K\r%s%d", "Request: ", i+1)
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
@@ -175,4 +175,86 @@ func findDefinitionFile(urls []string, client http.Client) (bool, *openapi3.T) {
|
||||
func init() {
|
||||
// TODO: Add a flag here (boolean) that defaults to false that will cause the program to execute 'sj automate' on the discovered definition file automatically.
|
||||
bruteCmd.PersistentFlags().StringVarP(&endpointWordlist, "wordlist", "w", "", "The file containing a list of paths to brute force for discovery.")
|
||||
bruteCmd.Flags().BoolVarP(&endpointOnly, "endpoint-only", "e", false, "Only return the identified endpoint")
|
||||
}
|
||||
|
||||
func ExtractSpecFromJS(bodyBytes []byte) []byte {
|
||||
var openApiIndex int
|
||||
var specClose int
|
||||
var bodyString, spec string
|
||||
|
||||
bodyString = string(bodyBytes)
|
||||
spec = strings.ReplaceAll(bodyString, "\n", "")
|
||||
spec = strings.ReplaceAll(spec, "\t", "")
|
||||
spec = strings.ReplaceAll(spec, " ", "")
|
||||
|
||||
if strings.Contains(strings.ReplaceAll(bodyString, " ", ""), `"swagger":"2.0"`) {
|
||||
openApiIndex = strings.Index(spec, `"swagger":`) - 1
|
||||
specClose = strings.LastIndex(spec, "]}") + 2
|
||||
|
||||
var doc2 openapi2.T
|
||||
bodyBytes = []byte(spec[openApiIndex:specClose])
|
||||
_ = json.Unmarshal(bodyBytes, &doc2)
|
||||
if !strings.Contains(doc2.Swagger, "2") {
|
||||
specClose = strings.LastIndex(spec, "}") + 1
|
||||
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.")
|
||||
}
|
||||
}
|
||||
} else if strings.Contains(strings.ReplaceAll(bodyString, " ", ""), `"openapi":"3`) {
|
||||
openApiIndex = strings.Index(spec, `"openapi":`) - 1
|
||||
|
||||
specClose = strings.LastIndex(spec, "]}") + 2
|
||||
|
||||
var doc3 openapi3.T
|
||||
bodyBytes = []byte(spec[openApiIndex:specClose])
|
||||
_ = json.Unmarshal(bodyBytes, &doc3)
|
||||
if !strings.Contains(doc3.OpenAPI, "3") {
|
||||
specClose = strings.LastIndex(spec, "}") + 1
|
||||
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.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Error("Error parsing JavaScript file for spec. Try saving the object as a JSON file and reference it locally.")
|
||||
}
|
||||
|
||||
return bodyBytes
|
||||
}
|
||||
|
||||
func UnmarshalSpec(bodyBytes []byte) (newDoc *openapi3.T) {
|
||||
var doc openapi2.T
|
||||
var doc3 openapi3.T
|
||||
|
||||
format = strings.ToLower(format)
|
||||
if format == "js" || strings.HasSuffix(swaggerURL, ".js") {
|
||||
bodyBytes = ExtractSpecFromJS(bodyBytes)
|
||||
} else if format == "yaml" || format == "yml" || strings.HasSuffix(swaggerURL, ".yaml") || strings.HasSuffix(swaggerURL, ".yml") {
|
||||
_ = yaml.Unmarshal(bodyBytes, &doc)
|
||||
_ = yaml.Unmarshal(bodyBytes, &doc3)
|
||||
}
|
||||
|
||||
_ = json.Unmarshal(bodyBytes, &doc)
|
||||
_ = json.Unmarshal(bodyBytes, &doc3)
|
||||
|
||||
if strings.HasPrefix(doc3.OpenAPI, "3") {
|
||||
newDoc := &doc3
|
||||
return newDoc
|
||||
} else if strings.HasPrefix(doc.Swagger, "2") {
|
||||
newDoc, err := openapi2conv.ToV3(&doc)
|
||||
if err != nil {
|
||||
fmt.Printf("Error converting v2 document to v3: %s\n", err)
|
||||
}
|
||||
return newDoc
|
||||
} else if os.Args[1] == "brute" {
|
||||
var noDoc openapi3.T
|
||||
return &noDoc
|
||||
} else {
|
||||
log.Fatal("Error parsing definition file.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -69,7 +69,8 @@ var convertCmd = &cobra.Command{
|
||||
fmt.Printf("Error converting v2 document to v3: %s\n", err)
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
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).")
|
||||
}
|
||||
@@ -91,7 +92,7 @@ var convertCmd = &cobra.Command{
|
||||
WriteConvertedDefinitionFile(converted)
|
||||
}
|
||||
}
|
||||
} else if format == "yaml" || format == "yml" {
|
||||
case "yaml", "yml":
|
||||
converted, err := yaml.Marshal(newDoc)
|
||||
if err != nil {
|
||||
log.Fatal("Error converting definition file to v3:", err)
|
||||
|
||||
+2
-8
@@ -5,7 +5,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/mpvl/unique"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -25,25 +24,20 @@ This list contains the raw endpoints (parameter values will not be appended or m
|
||||
client := CheckAndConfigureProxy()
|
||||
|
||||
var bodyBytes []byte
|
||||
var paths []string
|
||||
|
||||
fmt.Printf("\n")
|
||||
log.Infof("Gathering endpoints.\n\n")
|
||||
|
||||
if swaggerURL != "" {
|
||||
bodyBytes, _, _ := MakeRequest(client, "GET", swaggerURL, timeout, nil)
|
||||
paths = GenerateRequests(bodyBytes, client)
|
||||
GenerateRequests(bodyBytes, client)
|
||||
} else {
|
||||
specFile, err := os.Open(localFile)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening file:", err)
|
||||
}
|
||||
bodyBytes, _ = io.ReadAll(specFile)
|
||||
paths = GenerateRequests(bodyBytes, client)
|
||||
}
|
||||
unique.Sort(unique.StringSlice{&paths})
|
||||
for _, v := range paths {
|
||||
fmt.Println(v)
|
||||
GenerateRequests(bodyBytes, client)
|
||||
}
|
||||
},
|
||||
}
|
||||
+3
-3
@@ -66,7 +66,7 @@ func writeLog(sc int, target, method, errorMsg, response string) {
|
||||
} else if sc == 1 {
|
||||
logDangerous(target, method, tempLogger)
|
||||
} else if sc == 8899 {
|
||||
logVerboseJSON(target, method)
|
||||
logVerboseJSON(specTitle, specDescription)
|
||||
} else {
|
||||
logVerboseManual(sc, target, method, errorMsg, response, tempLogger)
|
||||
}
|
||||
@@ -86,13 +86,13 @@ func writeLog(sc int, target, method, errorMsg, response string) {
|
||||
} else if sc == 1 {
|
||||
logDangerous(target, method, tempLogger)
|
||||
} else if sc == 8899 {
|
||||
logJSON(target, method)
|
||||
logJSON(specTitle, specDescription)
|
||||
} else {
|
||||
logManual(sc, target, method, errorMsg, tempLogger)
|
||||
}
|
||||
} else {
|
||||
if sc == 8899 {
|
||||
logJSON(target, method)
|
||||
logJSON(specTitle, specDescription)
|
||||
} else {
|
||||
logAccessible(sc, target, method, tempLogger)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -43,33 +42,6 @@ This enables you to test specific API functions for common vulnerabilities or mi
|
||||
}
|
||||
var prepareFor string
|
||||
|
||||
func (s SwaggerRequest) PrintPreparedCommands(method string) {
|
||||
var preparedCommand string
|
||||
if strings.ToLower(prepareFor) == "curl" {
|
||||
preparedCommand = fmt.Sprintf("curl -sk -X %s '%s'", method, s.URL.String())
|
||||
} else if strings.ToLower(prepareFor) == "sqlmap" {
|
||||
preparedCommand = fmt.Sprintf("sqlmap --method=%s -u %s", method, s.URL.String())
|
||||
} else if strings.ToLower(prepareFor) == "ffuf" {
|
||||
log.Fatal("ffuf is not supported yet :(")
|
||||
// TODO
|
||||
} else {
|
||||
log.Fatal("External tool not supported. Only 'curl' and 'sqlmap' are supported options for the '-e' flag at this time.")
|
||||
}
|
||||
|
||||
if s.BodyData != nil {
|
||||
if strings.ToLower(prepareFor) == "sqlmap" {
|
||||
preparedCommand = preparedCommand + fmt.Sprintf(" --data='%s'", s.BodyData)
|
||||
} else {
|
||||
preparedCommand = preparedCommand + fmt.Sprintf(" -d '%s'", s.BodyData)
|
||||
}
|
||||
}
|
||||
if len(Headers) != 0 {
|
||||
preparedCommand = preparedCommand + fmt.Sprintf(" -H '%s'", strings.Join(Headers, "' -H '"))
|
||||
}
|
||||
|
||||
fmt.Println(preparedCommand)
|
||||
}
|
||||
|
||||
func init() {
|
||||
prepareCmd.PersistentFlags().StringVarP(&prepareFor, "external-tool", "e", "curl", "The external tool to prepare commands for. Generates syntax for 'curl' by default.")
|
||||
}
|
||||
+2
-2
@@ -49,7 +49,7 @@ $ sj convert -u https://petstore.swagger.io/v2/swagger.json -o openapi.json`,
|
||||
log.Error("Command not specified. See the --help flag for usage.")
|
||||
}
|
||||
},
|
||||
Version: "1.10.3",
|
||||
Version: "2.0.0",
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
@@ -72,7 +72,7 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&proxy, "proxy", "p", "NOPROXY", "Proxy host and port. Example: http://127.0.0.1:8080")
|
||||
rootCmd.PersistentFlags().BoolVarP(&quiet, "quiet", "q", false, "Do not prompt for user input - uses default values for all requests.")
|
||||
rootCmd.PersistentFlags().BoolVar(&randomUserAgent, "randomize-user-agent", false, "Randomizes the user agent string. Default is 'false'.")
|
||||
rootCmd.PersistentFlags().IntVarP(&rateLimit, "rate", "r", 15, "Limit the number of requests per second.")
|
||||
// rootCmd.PersistentFlags().IntVarP(&rateLimit, "rate", "r", 15, "Limit the number of requests per second.") // NEED TO RE-IMPLEMENT RATE LIMIT
|
||||
rootCmd.PersistentFlags().StringArrayVarP(&safeWords, "safe-word", "s", nil, "Avoids 'dangerous word' check for the specified word(s). Multiple flags are accepted.")
|
||||
rootCmd.PersistentFlags().StringVarP(&apiTarget, "target", "T", "", "Manually set a target for the requests to be made if separate from the host the documentation resides on.")
|
||||
rootCmd.PersistentFlags().Int64VarP(&timeout, "timeout", "t", 30, "Set the request timeout period.")
|
||||
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type SchemaReference struct {
|
||||
Required []string
|
||||
Type string
|
||||
Properties struct {
|
||||
Property struct {
|
||||
Type string
|
||||
Format string
|
||||
Example string
|
||||
Reference string
|
||||
XML struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Prefix string
|
||||
Attribute bool
|
||||
Wrapped bool
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HandleSchemaReference(schema *openapi3.SchemaRef) {
|
||||
|
||||
}
|
||||
|
||||
func (s SwaggerRequest) SetParametersFromSchema(param *openapi3.ParameterRef, location, schemaRef string, req *openapi3.RequestBodyRef, counter int) SwaggerRequest {
|
||||
if param != nil {
|
||||
name := strings.TrimPrefix(schemaRef, "#/components/schemas/")
|
||||
if s.Def.Components.Schemas[name] != nil {
|
||||
schema := s.Def.Components.Schemas[name]
|
||||
if schema.Value.Properties != nil {
|
||||
for property := range schema.Value.Properties {
|
||||
if schema.Value.Properties[property].Ref != "" {
|
||||
if counter < 3 {
|
||||
s = s.SetParametersFromSchema(param, location, schema.Value.Properties[property].Ref, req, counter+1)
|
||||
} else {
|
||||
log.Warnf("Nested reference encountered for %s (Property: %s). Test this endpoint manually.\n", s.URL.Scheme+"://"+s.URL.Host+s.URL.Path, property)
|
||||
break
|
||||
}
|
||||
} else if location == "path" {
|
||||
if schema.Value.Properties[property].Value.Example != "" && schema.Value.Properties[property].Value.Example != nil {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", schema.Value.Properties[property].Value.Example.(string))
|
||||
} else if schema.Value.Properties[property].Value.Type == "string" {
|
||||
if strings.Contains(s.URL.Path, param.Value.Name) {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", "test")
|
||||
} else {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+strings.ToLower(param.Value.Name)+"}", "test")
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(s.URL.Path, param.Value.Name) {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", "1")
|
||||
} else {
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+strings.ToLower(param.Value.Name)+"}", "1")
|
||||
}
|
||||
}
|
||||
} else if location == "query" {
|
||||
if schema.Value.Properties[property].Value.Example != "" && schema.Value.Properties[property].Value.Example != nil {
|
||||
s.Query.Add(param.Value.Name, (schema.Value.Properties[property].Value.Example).(string))
|
||||
} else if schema.Value.Properties[property].Value.Type == "string" {
|
||||
s.Query.Add(param.Value.Name, "test")
|
||||
} else {
|
||||
s.Query.Add(param.Value.Name, "1")
|
||||
}
|
||||
} else if location == "body" {
|
||||
if schema.Value.Properties[property].Value.Example != "" && schema.Value.Properties[property].Value.Example != nil {
|
||||
s.Body[param.Value.Name] = schema.Value.Properties[property].Value.Example
|
||||
} else if schema.Value.Properties[property].Value.Type == "string" {
|
||||
s.Body[param.Value.Name] = "test"
|
||||
} else {
|
||||
s.Body[param.Value.Name] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if schema.Value.Enum != nil {
|
||||
if location == "path" {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.URL.Path = strings.ReplaceAll(s.URL.Path, "{"+param.Value.Name+"}", fmt.Sprint(value))
|
||||
} else if location == "query" {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.Query.Add(param.Value.Name, fmt.Sprint(value))
|
||||
} else if location == "body" {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.Body[param.Value.Name] = value
|
||||
}
|
||||
} else if schema.Value.AllOf != nil {
|
||||
for i := range schema.Value.AllOf {
|
||||
if schema.Value.AllOf[i].Ref != "" {
|
||||
if counter < 3 {
|
||||
s = s.SetParametersFromSchema(param, location, schema.Value.AllOf[i].Ref, req, counter+1)
|
||||
} else {
|
||||
log.Warnf("Nested reference encountered for %s. Test this endpoint manually.\n", s.URL.Scheme+"://"+s.URL.Host+s.URL.Path)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if schema.Value.OneOf != nil && schema.Value.OneOf[0].Ref != "" {
|
||||
if counter < 3 {
|
||||
s = s.SetParametersFromSchema(param, location, schema.Value.OneOf[0].Ref, req, counter+1)
|
||||
} else {
|
||||
log.Warnf("Nested reference encountered for %s. Test this endpoint manually.\n", s.URL.Scheme+"://"+s.URL.Host+s.URL.Path)
|
||||
}
|
||||
} else if schema.Value.AnyOf != nil && schema.Value.AnyOf[0].Ref != "" {
|
||||
if counter < 3 {
|
||||
s = s.SetParametersFromSchema(param, location, schema.Value.AnyOf[0].Ref, req, counter+1)
|
||||
} else {
|
||||
log.Warnf("Nested reference encountered for %s. Test this endpoint manually.\n", s.URL.Scheme+"://"+s.URL.Host+s.URL.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
name := strings.TrimPrefix(schemaRef, "#/components/schemas/")
|
||||
if s.Def.Components.Schemas[name] != nil {
|
||||
schema := s.Def.Components.Schemas[name]
|
||||
if schema.Value.Properties != nil {
|
||||
for property := range schema.Value.Properties {
|
||||
if schema.Value.Properties[property].Ref != "" {
|
||||
if counter < 3 {
|
||||
s = s.SetParametersFromSchema(param, location, schema.Value.Properties[property].Ref, req, counter+1)
|
||||
} else {
|
||||
log.Warnf("Nested reference encountered for %s (Property: %s). Test this endpoint manually.\n", s.URL.Scheme+"://"+s.URL.Host+s.URL.Path, property)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if schema.Value.Properties[property].Value.Example != "" {
|
||||
s.Body[property] = schema.Value.Properties[property].Value.Example
|
||||
} else if schema.Value.Properties[property].Value.Type == "string" {
|
||||
s.Body[property] = "test"
|
||||
} else {
|
||||
s.Body[property] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if schema.Value.Enum != nil {
|
||||
value := schema.Value.Enum[rand.Intn(len(schema.Value.Enum))]
|
||||
s.Body[name] = value
|
||||
} else {
|
||||
if schema.Value.AllOf != nil {
|
||||
for i := range schema.Value.AllOf {
|
||||
if schema.Value.AllOf[i].Ref != "" {
|
||||
if counter < 3 {
|
||||
s = s.SetParametersFromSchema(param, location, schema.Value.AllOf[i].Ref, req, counter+1)
|
||||
} else {
|
||||
log.Warnf("Nested reference encountered for %s. Test this endpoint manually.\n", s.URL.Scheme+"://"+s.URL.Host+s.URL.Path)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if schema.Value.OneOf != nil && schema.Value.OneOf[0].Ref != "" {
|
||||
if counter < 3 {
|
||||
s = s.SetParametersFromSchema(param, location, schema.Value.OneOf[0].Ref, req, counter+1)
|
||||
} else {
|
||||
log.Warnf("Nested reference encountered for %s. Test this endpoint manually.\n", s.URL.Scheme+"://"+s.URL.Host+s.URL.Path)
|
||||
}
|
||||
} else if schema.Value.AnyOf != nil && schema.Value.AnyOf[0].Ref != "" {
|
||||
if counter < 3 {
|
||||
s = s.SetParametersFromSchema(param, location, schema.Value.AnyOf[0].Ref, req, counter+1)
|
||||
} else {
|
||||
log.Warnf("Nested reference encountered for %s. Test this endpoint manually.\n", s.URL.Scheme+"://"+s.URL.Host+s.URL.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
+394
-589
File diff suppressed because it is too large.
Load diff
@@ -1,26 +1,26 @@
|
||||
module github.com/BishopFox/sj
|
||||
|
||||
go 1.20
|
||||
go 1.22.5
|
||||
|
||||
toolchain go1.23.4
|
||||
|
||||
require (
|
||||
github.com/getkin/kin-openapi v0.115.0
|
||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de
|
||||
github.com/getkin/kin-openapi v0.132.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
golang.org/x/time v0.8.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/invopop/yaml v0.1.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.4 // 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
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
@@ -2,38 +2,36 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
|
||||
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.115.0 h1:c8WHRLVY3G8m9jQTy0/DnIuljgRwTCB5twZytQS4JyU=
|
||||
github.com/getkin/kin-openapi v0.115.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
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=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
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/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
|
||||
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
||||
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.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
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/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/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto=
|
||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY=
|
||||
github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw=
|
||||
github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
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/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=
|
||||
@@ -42,29 +40,16 @@ github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUq
|
||||
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
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/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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=
|
||||
Reference in new issue
Block a user