Qlik patched the smuggling bug, then Praetorian beat it with one extra letter
On August 29, 2023, Qlik shipped a literal-string filter for chunked transfer encoding. Three weeks later Praetorian sent tchunked, the desync came back, and Cactus ransomware spent the next two months harvesting the administrators who thought they were done patching.
Somewhere in late August 2023, an administrator running Qlik Sense Enterprise for Windows applied the patch Qlik had shipped on August 29, closed the change ticket, and went back to the backlog. The advisory called it a critical fix for an unauthenticated RCE chain. The notes were short. The deploy was uneventful. By any reasonable measure, that operator did the right thing on the right day. Three weeks later, Praetorian published a writeup demonstrating that the patch could be defeated by adding a single letter to one HTTP header, and from late November onward Cactus ransomware operators worked methodically through the population of servers whose owners had done exactly what that operator did.
The bypass mechanic is short. The August 29 fix walked the Transfer-Encoding header and rejected any value that exactly matched the literal string chunked. Praetorian sent Transfer-Encoding: tchunked instead. The Proxy’s validator decided that was not a smuggling attempt, forwarded the request, and the Repository’s looser HTTP parser still treated the body as chunked-encoded. The desync came back. The full unauthenticated RCE chain worked again, identical to before, with one extra letter in one header. That bypass got its own CVE, CVE-2023-48365, and the patch for it shipped in November 2023. The two-month gap is the actual story. The literal-string filter is what created it.
The original chain in four steps
Qlik Sense Enterprise for Windows ships three Windows services that matter: Proxy.exe (front-end auth and routing), Repository.exe (the management API at /qrs/*), and Scheduler.exe (task execution). Praetorian’s August 2023 disclosure used all three.
CVE-2023-41266 is the unauth foothold. The Proxy whitelists static font assets matching paths that begin with /resources/qmc/fonts/ and end in .ttf. The whitelist check runs before path normalization, so a URL like /resources/qmc/fonts/../../../qrs/ReloadTask?xrfkey=1333333333333337&filter=.ttf satisfies both the prefix and the suffix anchors while traversing into /qrs/*. The Proxy synthesizes an anonymous session and forwards the request. CVSS 8.2 from Qlik, 6.5 from NVD; both flag CWE-22.
CVE-2023-41265 is the privilege escalation. Praetorian found a textbook CL.TE desync in the Proxy’s ForwardPostData routine. When a request carries both Content-Length and Transfer-Encoding: chunked, RFC 7230 says Transfer-Encoding wins. The Proxy keyed off Content-Length. The Repository keyed off Transfer-Encoding. The Proxy forwarded both headers untouched, so two parsers drew two different request boundaries on the same byte stream. Worse, the Proxy stamps X-Qlik-User onto outgoing requests as part of identity injection, and the Repository trusts that header. A smuggled inner request inherits whatever identity the Proxy attached to the outer envelope. Praetorian impersonated INTERNAL\sa_repository, the Repository service account, which has admin rights inside QRS.
End-to-end, the PoC shape is short:
POST /resources/qmc/fonts/../../../qrs/ReloadTask?xrfkey=1333333333333337&filter=.ttf HTTP/1.1
Content-Length: <N>
Transfer-Encoding: chunked
<chunked body terminating early>
<smuggled inner request impersonating sa_repository>
The smuggled request hits /qrs/internal/management/apilogcontext/disable to silence audit logging, then POSTs to /qrs/externalprogramtask to register a new External Program Task running an arbitrary command. Scheduler.exe runs the command as the service account on the Windows host. The alternative final step is creating a new admin user via /qrs/user. Either way, the attacker owns the box pre-auth.
Qlik scored CVE-2023-41265 at 9.6 Critical. NVD raised it to 9.9. CWE-444. The bug class is “interpretation conflict between two HTTP parsers in the same request path,” which is decades old and well-documented in the smuggling literature.
The fix that didn’t fix it
Praetorian’s binary diff of the August 2023 patch is the most useful piece of evidence in this whole story. Qlik introduced two new classes, HttpHeaderValidation and HttpHeaderExtensions, that performed the textbook CL.TE mitigation in two steps. First, if both Content-Length and Transfer-Encoding are present, strip Content-Length. Second, walk each value of the Transfer-Encoding header and, in Praetorian’s words, “compare each of these values to the value chunked and return a false result if any of the values exactly match.”
Literal string equality. Against the single token chunked. With no normalization, no token-list awareness, no canonicalization step.
Three weeks later Praetorian sent Transfer-Encoding: tchunked. The Proxy’s validator returned "tchunked" != "chunked", so the request was not flagged as a smuggling attempt and was passed through with both headers intact. The Repository’s HTTP parser was permissive enough to honor chunked semantics on tokens it didn’t strictly recognize, so it parsed the body as the attacker intended. The desync was back. The original chain worked, byte-for-byte, with tchunked substituted for chunked in one header.
Qlik’s re-issued advisory describes the regression in seven words: “This resolves an incomplete fix for CVE-2023-41265.” The November 2023 patch replaced the literal-match check with what Qlik called “a more robust filtering mechanism that is less prone to CL.TE and TE.CL request tunneling attacks.” Praetorian validated the second fix.
This is the failure mode that matters. The August patch was not wrong about the bug class; it correctly identified CL.TE smuggling and correctly identified that the mitigation belonged in the Proxy’s request-validation pipeline. The patch was wrong about how RFC 7230 token matching works. RFC 7230 defines Transfer-Encoding as a comma-separated list of tokens, and HTTP parsers in the wild are notoriously generous about what they accept around the canonical name. Anything that compares a single header value to the literal seven-character string chunked is going to lose to chunked, and Chunked and chunked and, as Praetorian demonstrated, tchunked. The bypass surface for an exact-match check against a permissive downstream parser is enormous. Praetorian found it in 22 days because they were already looking at the patched code.
What Cactus did with the gap
The window between the August 29 patch and the September 20 disclosure created a population of administrators who patched in good faith and stopped looking. Many of those servers stayed on the August Initial Release for months. That population is what Cactus harvested.
Arctic Wolf published the first public attribution on November 28, 2023. Multiple incident-response engagements observed the Cactus group using the Qlik Sense Scheduler Service as initial access, then living off the land: PowerShell and BITS to stage tooling, cmd.exe and net.exe for credential and account manipulation, msiexec.exe to silently uninstall Sophos, WMI and quser for discovery. Reconnaissance output was redirected into .ttf files inside the Qlik content directory. The same suffix the path-traversal validator was fooled by, used here as a convenient hiding place for command output that an admin browsing the install tree would never look twice at.
Post-exploitation tooling was the standard Cactus kit: renamed ManageEngine UEMS binaries posing as Qlik files, AnyDesk for interactive remote access, Plink for RDP-over-SSH tunneling, WizTree for disk reconnaissance, and Rclone renamed to svchost.exe for exfiltration. The published IOCs include zohoservice.net and three IP addresses (45.61.147.176, 216.107.136.46, 144.172.122.30).
Fox-IT and NCC Group followed on April 25, 2024 with “Sifting through the spines,” documenting two confirmed engagements with exploitation dates of December 3 and December 4, 2023, and a broader sweep that estimated roughly 122 likely-compromised Qlik Sense instances — 49 in the United States, 13 in Spain, 11 in Italy. The Fox-IT post is currently 301-redirecting; the figures come from secondary citations and Shadowserver’s coordinated disclosure, so treat them as approximate. Northwave published a parallel “PrickSense” set of engagements with consistent TTPs.
Shadowserver’s special report scanned the internet through late November and early December 2023. Roughly 5,200 internet-exposed Qlik Sense servers, roughly 3,100 still vulnerable, and approximately 91 already compromised at peak based on attacker-staged font files inside the install tree. Fox-IT’s later sweep, drawing on a wider observation window into Q1 2024, lifted the total to roughly 122 likely-compromised instances. The two numbers are different measurements at different times, not a range. Shadowserver’s guidance to alert recipients was unusually direct: treat the host and the adjacent network as compromised, not merely vulnerable.
The exploitation timeline maps cleanly onto the patch lifecycle. August 29, original patch. September 20, DoubleQlik disclosed. Late November, Cactus weaponizes against the long tail of half-patched servers. December 3 and 4, the first publicly documented compromises. Through Q1 2024, the vulnerable population stays in the low thousands despite vendor and government notifications. Qlik Sense is also a high-leverage target for ransomware specifically because it sits inside finance, HR, and operations BI dashboards with broad data-source connectivity, which is why Cactus prioritized exfiltration tooling alongside the encryptor.
CISA’s KEV ledger missed the bypass for 14 months
CISA added CVE-2023-41265 and CVE-2023-41266 to KEV on December 7, 2023, after Cactus was already in the press. CVE-2023-48365, the bypass that did most of the actual damage, was not added until January 13, 2025. Fourteen months after Praetorian’s public disclosure. Roughly thirteen months after the first documented in-the-wild exploitations.
CISA does not publish per-CVE rationale for KEV inclusion timing, so the 14-month gap is interpretable but not explained. The most likely reading is procedural. KEV requires confirmed, documented in-the-wild exploitation against the specific CVE ID, and most public IR reports through 2023 and 2024 cited the cluster as 41265 / 41266 / 48365 without isolating which ID corresponded to the bypass artifact in each engagement. Telemetry for “is this server vulnerable to 41265 only or also 48365” requires version-build resolution that not every IR firm captures cleanly. Whatever the cause, the operational shape is the wrong one. Federal agencies had a 21-day BOD 22-01 deadline on the August CVEs in December 2023 and no KEV-driven obligation to chase the bypass until the standard 21-day window after the January 13, 2025 listing. The bypass was the bug actually being used in the wild during that fourteen-month gap.
What to do Monday
Stop at “August 2023” and you are not patched. Read that twice.
The fixed versions are not where most ops teams’ tickets ended.
| CVE | Type | CVSS | Fixed in |
|---|---|---|---|
| CVE-2023-41266 | Path traversal | 8.2 | August 2023 IR / May 2023 P4 / Feb 2023 P8 / Nov 2022 P11 / Aug 2022 P13 |
| CVE-2023-41265 | HTTP request smuggling | 9.6 | August 2023 set, same as 41266 |
| CVE-2023-48365 | Bypass of the 41265 fix | 9.9 | November 2023 IR / August 2023 Patch 2 / May 2023 P6 / Feb 2023 P10 / Nov 2022 P12 |
Anything at or below August 2023 Patch 1 is RCE-exposed pre-auth. The trains older than that go back to November 2021 P16. Qlik Sense Cloud and SaaS tenants were patched by Qlik as part of platform operations and are not in scope.
The operator playbook:
- Inventory. Every Windows host running
Proxy.exe,Repository.exe, orScheduler.exefromC:\Program Files\Qlik\Sense\. Pull build numbers from Add/Remove Programs. Anything older than November 2023 IR or the equivalent train-specific patch needs to be rolled forward. - Get the Proxy off the public internet. If the business absolutely needs internet-facing access, front it with a reverse proxy that strictly enforces RFC 7230 and drops or normalizes requests carrying both
Content-LengthandTransfer-Encoding. NGINX 1.21.1 or later, HAProxy 2.4 or later withoption http-restrict-req-hdr-names, and AWS ALB all reject this by default. IIS ARR does not. An RFC-compliant fronting proxy disrupts the smuggling step but does not stop the path traversal, so patching is still required. - WAF rule short of patching. Block requests where the URL path contains
/resources/and ends in.ttf,.woff,.otf, or.eotand contains..%2f,../, or%2e%2e. Block any external request to/qrs/externalprogramtask. - Network segmentation. Repository listens on TCP 4242 and Scheduler on 4243. Neither should be reachable from anything except the Qlik nodes themselves.
- Assume compromise on any unpatched, internet-facing instance. Shadowserver’s guidance is the right posture.
For hunting, the proxy audit log lives at C:\ProgramData\Qlik\Sense\Log\Proxy\Audit\. Three patterns are worth pulling at least 90 days back:
- URL paths matching
/resources/.*\.\./\.\./qrs/. The Praetorian PoC shape. The literalxrfkey=1333333333333337shows up in opportunistic scan traffic. User authenticatedentries pairing usernameNONEanonymouswith any/qrs/endpoint other than/qrs/about. Anonymous sessions should never reach repository APIs.- POSTs to
/qrs/externalprogramtaskor/qrs/externalprogramtask/createfrom non-admin sessions.
On the filesystem, unexpected .ttf, .woff, .otf, or .eot files written under the Qlik install tree are a Cactus reconnaissance fingerprint. On the process tree, child processes of Scheduler.exe that aren’t the Qlik engine binaries — cmd.exe, powershell.exe, rundll32.exe, certutil.exe, bitsadmin.exe — are the obvious live-off-the-land tells.
There is no widely published Suricata or Snort signature set for the wire shape, but it is straightforward to write a custom rule: HTTP request with both Content-Length and Transfer-Encoding headers reaching the Qlik Proxy port (typically 443 or 4244), URI containing /resources/ plus .. plus a font extension. If you have an internet-facing Qlik Proxy and you don’t have that rule, you are not getting the wire-level signal that would tell you whether scan traffic is finding you.
The point of the August fix wasn’t wrong. The token-matching logic was. Twenty-two days is the half-life of an exact-string check against a smuggling primitive when the researchers who reported the original bug are still reading your code, and Cactus had the better part of two months between Praetorian’s bypass disclosure and active exploitation — long enough to weaponize at leisure. The cost of patching CVE-2023-41265 in good faith and going back to the backlog was, for the population that did exactly that, identical to the cost of not patching at all. Worse, actually, because the second patch was a harder sell internally. “We already fixed Qlik” is a sentence that shows up on a lot of November 2023 change-board minutes. Patches that ship with a published bypass three weeks later are a category of vendor failure that the KEV process is not currently shaped to surface, and the 14-month gap on CVE-2023-48365 is the receipt.
PatchDay Alert flags KEV additions the day they land and reads the bypasses the same way as the original advisories, because the second CVE is usually the one being used in the wild.
Sources
- Praetorian: ZeroQlik technical writeup
- Praetorian: DoubleQlik, bypassing the original fix for CVE-2023-41265
- Qlik August 2023 advisory (CVE-2023-41265, CVE-2023-41266)
- Qlik November 2023 advisory (CVE-2023-48365)
- NVD: CVE-2023-41265
- NVD: CVE-2023-41266
- NVD: CVE-2023-48365
- Arctic Wolf: Qlik Sense exploited in Cactus ransomware campaign
- Fox-IT / NCC: Sifting through the spines, identifying potential Cactus ransomware victims
- Northwave: PrickSense, how Cactus exploits Qlik Sense
- Shadowserver: Vulnerable / compromised Qlik Sense special report
- Censys advisory on KEV addition for CVE-2023-48365
- Dark Reading: More than 3,000 Qlik Sense servers vulnerable to Cactus
- Help Net Security: Qlik Sense flaws exploited by Cactus ransomware
Share
Related field notes
-
The CVSS 4.3 that APT28 was already using
Microsoft shipped the fix for CVE-2026-32202 without an exploitation flag while Russian state actors had a five-month head start. Vendor-tag triage missed it. The federal deadline is tomorrow.
-
The seven-year gap is the story, not the CVE
Microsoft patched CVE-2018-8639 in December 2018. CISA added it to the KEV catalog in March 2025. The interesting number isn't the bug's age — it's the distance between when a fix shipped and when the exposed fleet was acknowledged.
-
SimpleHelp CVE-2024-57727: a seven-day patch and a sixteen-month leak
SimpleHelp shipped a fix in seven days from full disclosure. Then they posted it to a forum. Ransomware affiliates have been pulling hashed admin credentials out of unpatched servers ever since.
One email, every weekday morning.
You're in. Check your inbox.