 hash can read its
`delete_code` and then permanently delete arbitrary hosted files.
| **CVE** | [CVE-2026-104051]( |
| **Product** | PictShare (self-hosted image/media host) |
| **Affected** | `>= 2.0.0`, `url[1] ?? '';
if (!$hash) return ['status' => 'err', 'reason' => 'Missing hash'];
if (!isExistingHash($hash)) return ['status' => 'err', 'reason' => 'Hash not found'];
return getMetadataOfHash($hash); // 'err', 'reason' => 'Invalid delete code'];
File hashes are public (they appear in every shared image URL), so the whole chain —
**leak the code via `info`, then delete via `delete`** — needs nothing but the URL a user
python3 poc.py --url --hash
# add --delete to actually exercise the deletion (destructive) step
delete_code : 7f3a9c1e... / -> {"status":"ok"}
See [`poc.py`](./poc.py). Deletion is **opt-in** (`--delete`) so the default run is
- Any unauthenticated visitor can delete any hosted file using only its public hash
→ loss of availability / integrity of all hosted content.
- Uploader PII disclosure (IP, User-Agent, remote port) → deanonymization and privacy loss.
Upgrade to **PictShare 3.7.1** ([fix commit `ce5fc47`](
`info()` now returns a strict whitelist (`mime`, `size`, `hash`, `sha1`, `uploaded`) and no
longer leaks `delete_code`, `ip`, `useragent` or `remote_port`. General guidance: API
responses must whitelist fields explicitly; never serialize an internal record that mixes
> Related: [CVE-2026-104356]( — even without
> this leak, the `delete_code` was predictable because it was generated with `rand()`.
| 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.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
