
Predictable delete_code via rand() in PictShare < 3.7.1 (CWE-338). PoC + advisory writeup.
delete_code via rand() in PictShareAuthorized security research. PoC for defensive and educational use only.
PictShare generates the delete_code
authorization token with getRandomString(), which draws every character from PHP's
non-cryptographic rand(). Because rand() is not a CSPRNG — and the same generator
produces the public file hash shown in every shared URL — the delete_code is predictable
rather than secret, allowing unauthorized deletion of hosted files without ever reading the
code from the info endpoint.
| CVE | CVE-2026-104356 |
| Product | PictShare (self-hosted image/media host) |
| Affected | >= 2.0.0, < 3.7.1 |
| Fixed in | v3.7.1 |
| Vulnerability | Use of Cryptographically Weak PRNG (CWE-338) → predictable auth token |
| Privileges | None (unauthenticated) |
| CVSS 3.1 | 5.9 MEDIUM — AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H |
| CVSS 4.0 | 8.2 HIGH — AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H |
| Discovered by | Alisher Qarshibayev |
| Advisory | VulnCheck |
// src/inc/core.php (< 3.7.1)
function getRandomString($length=32, $keyspace='0123456789abcdefghijklmnopqrstuvwxyz')
{
$str = '';
$max = (function_exists('mb_strlen') ? mb_strlen($keyspace,'8bit') : strlen($keyspace)) - 1;
for ($i = 0; $i < $length; ++$i) {
$str .= $keyspace[rand(0, $max)]; // <-- rand() is NOT cryptographically secure
}
return $str;
}
This one function produces both:
delete_code that authorizes deletion.Since both tokens come from the same non-CSPRNG stream, an attacker who observes the public
hash gains information about the generator's state — the delete_code no longer has the
entropy its 32-character length suggests. rand() carries no cryptographic unpredictability
guarantee, so using it for a security token is the defect (CWE-338), independent of any
specific state-recovery technique.
random_int().rand() (aliased
to the Mersenne-Twister path) per process, so a turnkey remote predictor depends on the
deployment (process reuse, number of observable outputs, PHP build). This PoC therefore
demonstrates the predictability principle deterministically in a controlled seed, rather
than claiming a universal one-shot remote break. That is the honest scope of the finding —
and it is exactly why the vendor still treated it as a vulnerability and fixed it.demo.php reproduces the vendor's exact getRandomString() and shows that, once the PRNG
state is known/recovered, the first output (the public hash) fully determines the second
output (the delete_code) — i.e. observing the hash predicts the code:
php demo.php
Expected output:
[*] Reproducing PictShare getRandomString() with rand()
[*] Seed used by victim process : 1337 (unknown to attacker a priori)
public file hash (output #1): k3f9... (visible in the shared URL)
secret delete_code(output #2): 8a1c...
[*] Attacker recovers seed by matching the OBSERVED public hash:
[+] Seed recovered: 1337
[+] Predicted delete_code: 8a1c...
[+] MATCH — delete_code predicted from the public hash alone (no info API needed)
See demo.php. It is self-contained (no server, no network) and proves the
hash→code determinism that makes rand() unsuitable here.
Upgrade to PictShare 3.7.1 (fix commit ce5fc47),
which replaces rand() with random_int() (CSPRNG). General guidance: generate every
security token (delete codes, reset tokens, API keys, salts) with a cryptographically secure
RNG — random_int() / random_bytes() in PHP — never rand(), mt_rand() or uniqid().
Note: delete codes issued before the fix are not rotated, so pre-fix uploads remain predictable until re-uploaded.
| Date | Event |
|---|---|
| 2026-10-01 | Public disclosure, CVE reserved & published (VulnCheck), fixed in v3.7.1 |
Responsibly disclosed to the vendor and coordinated through VulnCheck. Fixed before this PoC was released. Published for defensive and educational purposes.