CISA added CVE-2026-45247 to the Known Exploited Vulnerabilities catalog on June 3, 2026 with a federal-agency patch deadline of June 6. Today is June 11. The two PoCs published against the CVE number are HORKimhab's CVE-2026-45247, whose README's "Simple Usage" section contains the literal strings <repository-url>, <install-command>, and <run-command>, and fevar54's PoC-Funcional---CVE-2026-45247-Mirasvit-Full-Page-Cache-Warmer-RCE-, whose 280-line Python builds a serialized PHP payload PHP cannot parse. Both repositories are public record. CISA's exploitation telemetry and GitHub's exploit corpus are not the same source.
CVE-2026-45247: KEV Says Exploited, The Public PoC Cannot Unserialize
pattern
cve
proof of concept
- HORKimhab/CVE-2026-45247%20(%E2%98%850)
- infosec.exchange/@Matchbook3469/116693209183281365
- infosec.exchange/@cybersecurefox/116719661908751115
- infosec.exchange/@securitycyber/116721620405895828
- infosec.exchange/@securitycyber/116721614685703598
- fevar54/PoC-Funcional---CVE-2026-45247-Mirasvit-Full-Page-Cache-Warmer-RCE-%20(%E2%98%850)
The HORKimhab repository is template scaffolding
The repository ships four files. The interesting one is cve-id-template.txt:
# lowercase
e.g: cve-2025-46822.sh, cve-2025-46822-lab.sh, ...The "Simple Usage" section of README.md:
git clone <repository-url>
cd <repository-name># Install dependencies
<install-command>
# Run the project
<run-command>Followed by the line Replace the commands above with your actual project setup steps. The "Credit or Reference" section names url1. The CVE number does not appear in the README body. It appears only in the repository name.
We have this template on file. HORKimhab's CVE-2026-20223 was the same scaffolding with a different CVE stamped on the URL. The author's relationship to the bug is the directory name on GitHub.
The fevar54 PoC is rejected at the first protected property
The fevar54 repository ships a Python file titled PoC Funcional - CVE-2026-45247 (Mirasvit Full Page Cache Warmer RCE). The README states the vulnerable code path:
$cookieValue = $_COOKIE['CacheWarmer'];
$data = unserialize(base64_decode($cookieValue));The script's build_malicious_cookie constructs the serialized payload:
payload = (
f'O:37:"Monolog\\Handler\\FingersCrossedHandler":3:{{'
f's:11:"*passthru";'
f'O:23:"Monolog\\Handler\\StreamHandler":3:{{'
f's:9:"*process";'
f'O:28:"Monolog\\Processor\\IntrospectionProcessor":1:{{'
f's:6:"*skips";a:0:{{}}'
f'}}'
f's:6:"*url";s:{27 + len(cmd)}:"php://filter/write=exec|{cmd}";'
f's:9:"*bubble";b:1;'
f'}}'
...
)PHP serializes a class's protected property by prefixing the property name with three bytes: a NUL, a literal *, and a NUL. The serialized form of FingersCrossedHandler's protected passthru is the byte sequence \x00*\x00passthru, eleven bytes total. The Python string "*passthru" is nine bytes. The script declares the length as 11. unserialize reads the declared length, consumes eleven bytes from the buffer, and finds eleven bytes that are *passthru";O because the count spills past the closing quote into the next characters of the script's own emit. It looks for the closing "; at offset eleven, finds :, and returns false. The chain ends before any Monolog code runs.
The same error appears in every protected-property declaration in the payload. s:9:"*socket" declares nine bytes for seven. s:10:"*handler" declares ten for eight. s:9:"*process" declares nine for eight. Each length is the size the field would be with the NUL prefix the script forgot to write.
The script also defines three payload-generation methods on the PHPObjectPayload class: generate_syslog_udp_handler_payload, generate_buffer_handler_payload, generate_fingers_crossed_payload. Only one of the three is ever called. All three contain the same byte-level errors in the protected-property declarations. The author wrote three variants of the same broken payload, then picked one.
php://filter/write=exec|<cmd> is not a PHP stream filter
The intended sink, supplied as the StreamHandler's *url:
php://filter/write=exec|<command>php://filter is a real PHP stream wrapper. The filter names it accepts are a fixed list compiled into the runtime: convert.base64-encode, convert.base64-decode, convert.iconv.*, string.toupper, string.tolower, string.rot13, string.strip_tags, convert.quoted-printable-encode, zlib.deflate, bzip2.compress, plus a few more from optional extensions. There is no exec filter in PHP. There has never been an exec filter in PHP.
The script's exfiltration check reads the HTTP response for uid= strings:
if 'PWNED' in resp.text or 'uid=' in resp.text:
print("[+] ¡Comando ejecutado exitosamente!")
match = re.search(r'(uid=[^\s]+|PWNED[^\s]+)', resp.text)The check is reasonable for an exploit that actually reached system(). This exploit does not. There is nothing in the response to extract.
The cookie name and the payload prefix are the same string
The script builds the cookie value:
cookie_value = f"CacheWarmer:{payload_b64}"
return cookie_valueThen sets it as a cookie named CacheWarmer:
self.session.get(
self.target_url,
cookies={'CacheWarmer': malicious_cookie},
timeout=30
)The HTTP request carries Cookie: CacheWarmer=CacheWarmer:<base64>. On the server, $_COOKIE['CacheWarmer'] resolves to the string CacheWarmer:<base64>. PHP's base64_decode in default mode silently strips characters that are not in the base64 alphabet. The colon is stripped. The eleven letters of CacheWarmer are themselves valid base64 characters and decode to eight bytes of garbage, which are concatenated with the actual payload's bytes shifted by one byte of alignment. unserialize of that buffer fails on the first byte because PHP serialized values start with O:, s:, a:, i:, or b:, not whatever the eleven letters of CacheWarmer decode to. The cookie name and the payload prefix are the same string.
The verification check is also theater
Before the script fires the payload, it tries to confirm the target is Magento running Mirasvit Cache Warmer. The method checks three paths:
test_paths = [
'/magento_version',
'/pub/static/version.php',
'/static/version.php'
]If none return 200, it tries /pub/media/mirasvit/cache_warmer/CHANGELOG.md, a path Mirasvit does not actually expose under pub/media (Mirasvit modules live under app/code/Mirasvit/CacheWarmer/). If that also fails:
print("[!] No se pudo determinar si el objetivo es vulnerable")
return True # Asumimos vulnerable para continuarThe Spanish comment translates to "We assume vulnerable to continue." The method returns true regardless of what the target actually serves. Every URL the operator supplies is judged vulnerable. The verification is decoration; the broken exploit fires against whatever the operator points it at.
What KEV listings rest on
CISA's KEV catalog records vulnerabilities for which CISA has reliable evidence of in-the-wild exploitation. The evidence is telemetry: victim reports from federal agencies, IOC feeds from private partners, EDR vendor notifications. A CVE that lands on KEV without a working public PoC is the catalog working as designed. KEV exists to surface threats CISA sees in the wild before the public exploit ecosystem catches up.
The gap on CVE-2026-45247 is distinctive. KEV listings without working public PoCs are common. KEV listings whose only public PoCs are a template stub and a 280-line approximation that cannot unserialize are less so. Either CISA's underlying chain has not been published, or the underlying chain reaches the bug through a route different from what PoC authors have tried to reconstruct from the CVE description.
Mirasvit's 1.11.12 release is the only other reference point in the public record. The patched commit is not public, but Magento PHP object injection bugs in third-party modules have a stable mitigation shape: replace cookie-value unserialize with JSON parsing, or pass ['allowed_classes' => false]. Whoever wrote the patch knows where the change lands. Whoever is exploiting in the wild knows what the patch closes. Neither knowledge is on GitHub.
The pattern
The catalog already names this shape: the placeholder-poc pattern is a script that attaches authorship to a CVE-numbered repository, structured like an exploit, missing the operational primitive. The mature exhibits include HORKimhab's prior CVE-2026-20223 stub, the byte-identical Fragnesia REPLs that stopped at the syscall before the primitive, and the kaleth4 CredSSP repository whose CLI flags the code never parsed.
CVE-2026-45247 sharpens the pattern in two directions at once. HORKimhab's repository repeats the same template the author used against CVE-2026-20223; the placeholder lives in the README. fevar54's repository moves the placeholder into the bytes. The script is structurally complete. The imports resolve. The Monolog classes are real. The gadget chain target family (FingersCrossedHandler, BufferHandler, StreamHandler) is what a working PHP Object Injection exploit against Magento would target. The cookie name matches the vendor's CacheWarmer cookie. Every protected-property length declaration is wrong by the three bytes of the NUL prefix the script forgot to encode. The shape is what a language model produces when asked to write a serialized PHP payload from a class diagram without running PHP.
KEV's patch deadline was June 6. Today is June 11. The vendor's patch exists. The exploitation telemetry exists. The two PoCs published under the CVE number cannot reach the bug.
PoCs: HORKimhab/CVE-2026-45247, fevar54/PoC-Funcional---CVE-2026-45247-Mirasvit-Full-Page-Cache-Warmer-RCE-
The CVE number in the repository name is the only part of either artifact that is accurate.