Incident investigation
Clop's extortion site became an extortion target
Extortion group Clop had its website defaced and received a ransom demand from another group, exposing a Grav flaw that lingered for months in the older branch; sites still running that code need an update and a check for earlier compromise.

In this article
Clop is an extortion group that uses stolen company information to pressure victims. On September 18, its own leak site acquired a file uploaded by an outsider, followed by a replacement home page. BleepingComputer visited the site and confirmed both the downloadable file and the defacement. The other group, ShinyHunters, then demanded an eight-figure payment from Clop, threatening to release material it claimed to have taken. The group accustomed to posting extortion notices had received one of its own.
Clop used Grav, a PHP content management system for publishing pages and handling forms, with much of its content stored directly in files. Follow-up reporting traced the intrusion to temporary form-file handling. Grav's maintainers then confirmed that the flaw had been fixed in April, but the older 1.7 branch received the repair only on September 24, after the incident became public.
The attackers said Clop was running 1.7.43. That was older than the then-current 1.7.53.3, yet even an update to the latter would have left the relevant code vulnerable. Other sites remaining on 1.7 faced the same gap: the newer branch was repaired while updates they could install on their existing branch still lacked the patch.
1 Find the code that is actually serving requests
The entry point involves Grav core's FormFlash, which preserves temporary form state and uploaded files. The old implementation inserted a form identifier into a directory path. An identifier with path semantics could send file operations outside the intended directory. In core 1.7.43, the constructor accepts the identifiers and getTmpDir() joins the path. The 1.7.53.4 repair restricts identifier characters at construction, after which the upload path rejects an empty identifier. Updating the Form plugin alone can leave the old core implementation in place.
On September 30, Grav recommends stable release 2.2.3. That is the deployment target for sites that can complete compatibility testing. Installations that must remain on 1.7 should first apply the 1.7.53.4 backport and then plan their migration. Release 2.0.0-beta.2 marks the first April fix; a deployment today should use the current stable release. The 1.7.53.4 backport closes the gap discussed here. Other dependencies and historical security fixes still need their own review.
If an immediate update is impractical, restrict external access to the affected forms and their upload and state-storage handlers, then verify enforcement on the server side. Hiding an upload button leaves direct submissions possible. Disabling a form can interrupt enquiries or attachment collection, so provide a working alternative. If the upgrade fails, keep restrictions in place and recover with a package that includes the repair for its branch and has passed compatibility testing. The 1.7.53.4 repair floor does not establish that data and configuration are compatible with a major-version rollback.
2 How a form identifier chooses a file's location
A website usually stages an attachment in a temporary directory before moving it to its final destination. Grav needs to associate that file with a session and a form, so it includes both identifiers in the path. The default layout is effectively tmp/forms/session-ID/form-ID. This is a convenient arrangement as long as each identifier remains a single directory name.
The problem begins with the form ID sent back by the browser. In the reported Form 7.3.0 version, form.php, lines 1191–1215, reads __unique_form_id__ and calls setUniqueId() after finding the matching form. A server-generated value is still under the request sender's control when it comes back. A hidden field changes the page's presentation; it does not keep the sender from changing the submitted value.
Core's FormTrait::getFlash() then passes that value to FormFlash as unique_id. Here, “flash” means short-lived form state retained between requests. Once the constructor has accepted the ID, getTmpDir() gives the filesystem an almost direct concatenation:
// Grav 1.7.43 · FormFlash::getTmpDir()
return $this->folder && $this->uniqueId
? "{$this->folder}/{$this->uniqueId}"
: '';
A normal identifier such as form_42 selects a subdirectory with that name inside the session directory. Directory separators and parent-directory components change where a path leads. When PHP passes the assembled string to the filesystem, those components are interpreted as navigation. A value intended to distinguish forms has acquired the ability to choose a storage location.

The next question is which operation uses the path. The plugin's uploadFiles() calls the flash object's upload method. The compatibility class used in 1.7.43, Grav\Common\Form\FormFlash::uploadFile(), obtains the temporary path, creates the directory, and calls move_uploaded_file(). At this point, the assembled string determines where bytes are written to disk.
That explains the limits of checking an upload's extension or size, or giving the file a random name. Those measures constrain content, volume, or the final filename. This flaw affects its parent directory. The plugin does perform type and size checks before the upload call, and a particular installation is further constrained by form configuration, reachable functionality, and the PHP process's write permissions. The source explains how directory traversal becomes possible. Public material does not provide the complete requests, files, and subsequent execution history from Clop's server.
3 The April fix reached the old branch in September
April's d904efc commit restricted characters in path identifiers and shipped in 2.0.0-beta.2. On September 24, a39fe54 brought the corresponding repair to 1.7, released as 1.7.53.4. Such a backport lets users fix the same error without immediately migrating a major version. The maintainers confirmed the earlier omission in the follow-up interview; public records do not explain the months between the two fixes.
In 1.7.53.4, sanitizeId() checks the identifier with the expression below and returns an empty string when the match fails. The function discards the whole invalid identifier. Removing only its troublesome characters could quietly turn different inputs into the same directory name.
// Grav 1.7.53.4 · sanitizeId() acceptance rule
preg_match('/^[A-Za-z0-9,_-]{1,64}$/', $id) ? $id : '';
How does the empty identifier stop the write? getTmpDir() returns an empty path, and the compatibility class's uploadFile() returns failure before creating a directory or moving the file. Form-state saving has separate empty-value checks. Plugins or custom forms that use different file operations still require checks along their own calling paths.
The older branch also retains a deliberate difference. The 2.x implementation restricts three fields: id, session_id, and unique_id. The 1.7 backport restricts the latter two. Its id comes from form configuration and can be a path-like lookup identifier for media forms; copying the restriction would affect that legitimate use. A new regression test therefore requires that configuration value to survive unchanged.
The patch's upstream test cases cover separators, parent-directory components, stream-style paths, encoded forms, excessive length, and control characters, while retaining ordinary identifiers, commas, and valid 64-character values. To check the repair in an isolated copy of a site, first send a normal request through the form path, then submit invalid identifiers and inspect whether they are rejected and writes stay in the intended directory. If an earlier configuration check stops the request, the test has not reached the changed code.
There is another distinction in the vulnerability record. The original advisory discusses __form-flash-id reaching session_id, with directory creation and index.yaml writes. September's incident account describes __unique_form_id__ reaching unique_id. Both encounter the same class of unsafe directory construction, through different request parameters and calling conditions. A detector that recognizes only the parameter in the original advisory can miss the later reported route.
4 What was taken after the home page changed?
The reporter directly observed the uploaded file and replaced home page, and Grav's maintainers validated the vulnerability description sent to them. ShinyHunters also claimed to have obtained logs, source code, and onion-service private keys, invoking alleged payment records in its threats. The reporter did not independently verify that material.
Clop acknowledged that its software was not fully updated and subsequently moved to a new onion address, while denying both the theft of important financial or operational information and any negotiations. Determining what the server held and who read it would require verifiable files, logs, or a server image. Public reporting has not confirmed a ransom payment.
5 Before putting the site back into use
Check the Grav core and FormFlash files actually serving requests, including any old containers or replicas still taking traffic. Complete the repair checks described above, then try the tasks a visitor depends on: submit an enquiry, upload an attachment, and retrieve it. Sites with custom media forms should check those too.
If the vulnerable handlers were exposed, also look back at file changes before the update. Preserve website and proxy logs, file timestamps, and trusted backups. Focus on new files outside staging directories and unexpected changes to pages, plugins, or configuration. Logs without request bodies may never have recorded the submitted form identifier, so an unsuccessful parameter search cannot rule out an intrusion.
After confirmed or strongly suspected unauthorized writes, rebuild from trusted packages and inspect the content and configuration before restoring them, to avoid carrying suspect files into the replacement. Address credentials, private keys, and connected systems according to what the compromised process could access, including revocation and rotation where needed. Keep temporary access restrictions in place until these checks and any necessary recovery are complete.
6Evidence and sources
6.1Sources and material
- BleepingComputer: September 19 observations, updated September 21https://www.bleepingcomputer.com/news/security/shinyhunters-hacks-clop-leak-site-threatens-to-extort-ransomware-gang/
- BleepingComputer: September 25 technical account and maintainer interviewhttps://www.bleepingcomputer.com/news/security/shinyhunters-hacked-clop-leak-site-using-grav-cms-path-traversal-flaw/
- Grav: CVE-2026-42608 / GHSA-hmcx-ch82-3fv2https://github.com/getgrav/grav/security/advisories/GHSA-hmcx-ch82-3fv2
- Grav: April repair d904efchttps://github.com/getgrav/grav/commit/d904efc33e03ebb597afde8d3368b28cf0423632
- Grav: 1.7 backport and regression tests a39fe54https://github.com/getgrav/grav/commit/a39fe54c6ffc6c20189bd8099a754a8f2c74daa6
- Grav: 1.7.53.4 release noteshttps://github.com/getgrav/grav/releases/tag/1.7.53.4
- Grav: current stable 2.2.3 release noteshttps://github.com/getgrav/grav/releases/tag/2.2.3
- Grav: current official download recommendationhttps://getgrav.org/downloads