package testutils import ( "crypto/tls" "errors" "fmt" "net" "net/http" "net/http/httptest" "os" "os/exec" "regexp" "strings" "github.com/gobwas/ws" "github.com/julienschmidt/httprouter" "github.com/projectdiscovery/utils/conversion" ) // ExtraArgs var ( ExtraDebugArgs = []string{} ExtraEnvVars = []string{ "DISABLE_CLOUD_UPLOAD_WRN=true", "DISABLE_CLOUD_UPLOAD=true", } ) // RunNucleiTemplateAndGetResults returns a list of results for a template func RunNucleiTemplateAndGetResults(template, url string, debug bool, extra ...string) ([]string, error) { return RunNucleiAndGetResults(true, template, url, debug, extra...) } // RunNucleiWorkflowAndGetResults returns a list of results for a workflow func RunNucleiWorkflowAndGetResults(template, url string, debug bool, extra ...string) ([]string, error) { return RunNucleiAndGetResults(false, template, url, debug, extra...) } func RunNucleiAndGetResults(isTemplate bool, template, url string, debug bool, extra ...string) ([]string, error) { var templateOrWorkflowFlag string if isTemplate { templateOrWorkflowFlag = "-t" } else { templateOrWorkflowFlag = "-w" } return RunNucleiBareArgsAndGetResults(debug, nil, append([]string{ templateOrWorkflowFlag, template, "-target", url, }, extra...)...) } func RunNucleiBareArgsAndGetResults(debug bool, env []string, extra ...string) ([]string, error) { cmd := exec.Command("./nuclei") extra = append(extra, ExtraDebugArgs...) cmd.Args = append(cmd.Args, extra...) cmd.Args = append(cmd.Args, "-duc") // disable auto updates cmd.Args = append(cmd.Args, "-interactions-poll-duration", "1") cmd.Args = append(cmd.Args, "-interactions-cooldown-period", "10") cmd.Args = append(cmd.Args, "-allow-local-file-access") if env != nil { cmd.Env = append(os.Environ(), env...) } cmd.Env = append(cmd.Env, ExtraEnvVars...) if debug { cmd.Args = append(cmd.Args, "-debug") cmd.Stderr = os.Stderr fmt.Println(cmd.String()) } else { cmd.Args = append(cmd.Args, "-silent") } output, err := cmd.Output() var data string if len(output) > 0 { data = strings.TrimSpace(conversion.String(output)) } if debug { fmt.Println(data) } var parts []string items := strings.Split(data, "\n") for _, i := range items { if i != "" { parts = append(parts, i) } } if (data == "" || len(parts) == 0) && err != nil { return nil, fmt.Errorf("%v: %v", err.Error(), data) } return parts, nil } // RunNucleiWithArgsAndGetResults returns result,and runtime errors func RunNucleiWithArgsAndGetResults(debug bool, args ...string) ([]string, error) { cmd := exec.Command("./nuclei", args...) cmd.Env = append(cmd.Env, ExtraEnvVars...) if debug { cmd.Args = append(cmd.Args, "-debug") cmd.Stderr = os.Stderr fmt.Println(cmd.String()) } else { cmd.Args = append(cmd.Args, "-silent") } output, err := cmd.Output() var data string if len(output) > 0 { data = strings.TrimSpace(conversion.String(output)) } if debug { fmt.Println(data) } var parts []string items := strings.Split(data, "\n") for _, i := range items { if i != "" { parts = append(parts, i) } } if (data == "" || len(parts) == 0) && err != nil { return nil, fmt.Errorf("%v: %v", err.Error(), data) } return parts, nil } // RunNucleiArgsAndGetErrors returns a list of errors in nuclei output (ERR,WRN,FTL) func RunNucleiArgsAndGetErrors(debug bool, env []string, extra ...string) ([]string, error) { cmd := exec.Command("./nuclei") extra = append(extra, ExtraDebugArgs...) cmd.Env = append(os.Environ(), env...) cmd.Args = append(cmd.Args, extra...) cmd.Args = append(cmd.Args, "-duc") // disable auto updates cmd.Args = append(cmd.Args, "-interactions-poll-duration", "1") cmd.Args = append(cmd.Args, "-interactions-cooldown-period", "10") cmd.Args = append(cmd.Args, "-allow-local-file-access") cmd.Args = append(cmd.Args, "-nc") // disable color cmd.Env = append(cmd.Env, ExtraEnvVars...) dataOutput, err := cmd.CombinedOutput() if debug { fmt.Println(string(dataOutput)) } var data string if len(dataOutput) > 0 { data = strings.TrimSpace(conversion.String(dataOutput)) } results := []string{} for _, v := range strings.Split(data, "\n") { line := strings.TrimSpace(v) switch { case strings.HasPrefix(line, "[ERR]"): results = append(results, line) case strings.HasPrefix(line, "[WRN]"): results = append(results, line) case strings.HasPrefix(line, "[FTL]"): results = append(results, line) } } return results, err } // RunNucleiArgsWithEnvAndGetResults returns a list of results in nuclei output (ERR,WRN,FTL) func RunNucleiArgsWithEnvAndGetResults(debug bool, env []string, extra ...string) ([]string, error) { cmd := exec.Command("./nuclei") extra = append(extra, ExtraDebugArgs...) cmd.Env = append(os.Environ(), env...) cmd.Env = append(cmd.Env, ExtraEnvVars...) cmd.Args = append(cmd.Args, extra...) cmd.Args = append(cmd.Args, "-duc") // disable auto updates cmd.Args = append(cmd.Args, "-interactions-poll-duration", "5") cmd.Args = append(cmd.Args, "-interactions-cooldown-period", "10") cmd.Args = append(cmd.Args, "-allow-local-file-access") if debug { cmd.Args = append(cmd.Args, "-debug") cmd.Stderr = os.Stderr fmt.Println(cmd.String()) } else { cmd.Args = append(cmd.Args, "-silent") } dataOutput, err := cmd.Output() var data string if len(dataOutput) > 0 { data = strings.TrimSpace(conversion.String(dataOutput)) } if debug { fmt.Println(data) } var parts []string items := strings.Split(data, "\n") for _, i := range items { if i != "" { parts = append(parts, i) } } if (data == "" || len(parts) == 0) && err != nil { return nil, fmt.Errorf("%v: %v", err.Error(), data) } return parts, nil } var templateLoaded = regexp.MustCompile(`(?:Templates|Workflows) loaded[^:]*: (\d+)`) // RunNucleiBinaryAndGetLoadedTemplates returns a list of results for a template func RunNucleiBinaryAndGetLoadedTemplates(nucleiBinary string, debug bool, args []string) (string, error) { cmd := exec.Command(nucleiBinary, args...) cmd.Env = append(cmd.Env, ExtraEnvVars...) cmd.Args = append(cmd.Args, "-duc") // disable auto updates if debug { cmd.Args = append(cmd.Args, "-debug") fmt.Println(cmd.String()) } data, err := cmd.CombinedOutput() if debug { fmt.Println(string(data)) } if err != nil { return "", err } matches := templateLoaded.FindAllStringSubmatch(string(data), -1) if len(matches) == 0 { return "", errors.New("no matches found") } return matches[0][1], nil } func RunNucleiBinaryAndGetCombinedOutput(debug bool, args []string) (string, error) { args = append(args, "-interactions-cooldown-period", "10", "-interactions-poll-duration", "1") cmd := exec.Command("./nuclei", args...) cmd.Env = append(cmd.Env, ExtraEnvVars...) if debug { cmd.Args = append(cmd.Args, "-debug") fmt.Println(cmd.String()) } data, err := cmd.CombinedOutput() if debug { fmt.Println(string(data)) } if err != nil { return "", err } return string(data), nil } // TestCase is a single integration test case type TestCase interface { // Execute executes a test case and returns any errors if occurred Execute(filePath string) error } // TCPServer creates a new tcp server that returns a response type TCPServer struct { URL string listener net.Listener } // keys taken from https://pascal.bach.ch/2015/12/17/from-tcp-to-tls-in-go/ const serverKey = `-----BEGIN EC PARAMETERS----- BgUrgQQAIg== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- MIGkAgEBBDBJazGwuqgOLsCMr7P56w26JBEHQokiuAy2iCQfCnmOWm7S9FveQ/DP qB69zvUPs26gBwYFK4EEACKhZANiAARehvy96ygCAsJ6iQvthzl/Nvq4P3c4MGyx UMLMe0L10OCxeCl5ZY2CuFf8UnBgV1u414U4+yjIrS57w1/3utBKC9TVRGj+Vcls 2NZ4+8Jh6/M/Jf/Mpd8QyIy0WesEUM4= -----END EC PRIVATE KEY----- ` const serverCert = `-----BEGIN CERTIFICATE----- MIICJDCCAakCCQDFa0/D9jJw6DAKBggqhkjOPQQDAjB7MQswCQYDVQQGEwJVUzEP MA0GA1UECAwGcGRsYW5kMQ8wDQYDVQQHDAZwZGNpdHkxCzAJBgNVBAoMAnBkMQsw CQYDVQQLDAJwZDELMAkGA1UEAwwCcGQxIzAhBgkqhkiG9w0BCQEWFGFueXRoaW5n QGFueXRoaW5nLnBkMB4XDTIyMDEyNzIyMDUwNFoXDTMyMDEyNTIyMDUwNFowezEL MAkGA1UEBhMCVVMxDzANBgNVBAgMBnBkbGFuZDEPMA0GA1UEBwwGcGRjaXR5MQsw CQYDVQQKDAJwZDELMAkGA1UECwwCcGQxCzAJBgNVBAMMAnBkMSMwIQYJKoZIhvcN AQkBFhRhbnl0aGluZ0Bhbnl0aGluZy5wZDB2MBAGByqGSM49AgEGBSuBBAAiA2IA BF6G/L3rKAICwnqJC+2HOX82+rg/dzgwbLFQwsx7QvXQ4LF4KXlljYK4V/xScGBX W7jXhTj7KMitLnvDX/e60EoL1NVEaP5VyWzY1nj7wmHr8z8l/8yl3xDIjLRZ6wRQ zjAKBggqhkjOPQQDAgNpADBmAjEAgxGPbjRlhz+1Scmr6RU9VbzVJWN8KCsTTpx7 pqfmKpJ29UYReZN+fm/6fc5vkv1rAjEAkTuTf8ARSn1UiKlCTTDQVtCoRcMVLQQp TCxxGzcAlUAAJE6+SJpY7fPRe+n2EvPS -----END CERTIFICATE----- ` // NewTCPServer creates a new TCP server from a handler func NewTCPServer(tlsConfig *tls.Config, port int, handler func(conn net.Conn)) *TCPServer { server := &TCPServer{} l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) if err != nil { panic(err) } server.URL = l.Addr().String() server.listener = l if tlsConfig != nil { cer, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey)) if err != nil { panic(err) } tlsConfig.Certificates = []tls.Certificate{cer} } go func() { for { // Listen for an incoming connection. conn, err := l.Accept() if err != nil { continue } // Handle connections in a new goroutine. if tlsConfig != nil { connTls := tls.Server(conn, tlsConfig) go handler(connTls) } else { go handler(conn) } } }() return server } // Close closes the TCP server func (s *TCPServer) Close() { _ = s.listener.Close() } // NewWebsocketServer creates a new websocket server from a handler func NewWebsocketServer(path string, handler func(conn net.Conn), originValidate func(origin string) bool, port ...int) *httptest.Server { handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if value := r.Header.Get("Origin"); value != "" && !originValidate(value) { w.WriteHeader(http.StatusBadRequest) return } conn, _, _, err := ws.UpgradeHTTP(r, w) if err != nil { return } go func() { defer func() { _ = conn.Close() }() handler(conn) }() }) if path != "" { router := httprouter.New() router.HandlerFunc("*", "/test", handlerFunc) return httptest.NewServer(router) } return httptest.NewServer(handlerFunc) }