Post

Exploiting LimeSurvey

LimeSurvey is a widely used open source application that allows it to create surveys with various features. For this post I will use LimeSurvey Version 2.72.3+171020 which contains a known vulnerability and use it together with 2 yet unknown vulnerabilities to achieve code execution. The first step of the exploit is based on this security adivsory by the LimeSurvey team and the issue is fixed in all versions beginning with 2.6.7 LTS, 2.73.1 and 3.4.2.

Good practice is to look at the patches that fix the vulnerability in order to understand and exploit it. Since the advisory mentions that it is inside InstallerController.php, the git commit history of the file can be viewed in order to identify past changes that might look interesting.

There is a security tagged commit and several additional commits in a short time frame of 2 days, which suggests that the bug was fixed by some of these commits. An especially interesting change was made with ‘Dev: Only write config file when installation is completely done’.

This change shows that it is possible to bypass the _checkInstallation step of the installer by either having not installed LimeSurvey yet and therefore not having a valid config/config.php or by supplying a special post parameter InstallerConfigForm. To understand the context of this check an overview of how the installer actually works has to be established.

The installer contains a function run which has a switch that checks which step of the installation has to be called. Valid steps are welcome, license, viewLicense, precheck, database, createdb, populatedb, optional and index. These steps can be reached by calling /index.php?r=installer/stepname. Before directing control to the appropriate steps function a call to _checkInstallation is made. Since this can be bypassed by providing the post parameter InstallerConfigForm it is now possible to call steps of the installation process on an already completed LimeSurvey installation.

One way of exploiting this is to call the stepDatabaseConfiguration method by sending a post request to /index.php?r=installer/database. This step sets up a database connection and expects valid values for database type, location, name, prefix, user and password. This can be used to connect a remote LimeSurvey installation to an attacker controlled database. An example of such a request can be seen in the following listing:

1
2
3
4
5
6
7
8
url = 'http://127.0.0.1/LimeSurvey/index.php/installer/database'
    data = {"YII_CSRF_TOKEN": csrf_token+"==", 
    'InstallerConfigForm[dbtype]' : "mysql",
    'InstallerConfigForm[dbname]' : "lime",
    'InstallerConfigForm[dbuser]' : "user",
    'InstallerConfigForm[dblocation]' : "127.0.0.1:3306",
    'InstallerConfigForm[dbpwd]' : "pass"}
    r = requests.post(url, headers=headers, data=data, proxies=proxyDict, verify=False)

After the database is switched to an attacker controlled one, it is possible to log into the application (because the credentials are stored inside the database). The application itself still runs on the target server which leads to file upload bugs resulting in remote code execution even though the database is not necessarily remote.

Looking through the application reveals several methods of uploading data to the application, for example images or templates. An especially interesting feature allows uploading resources for surveys as zip files. 2 bugs could be identified in this component that both lead to code execution.

The upload process works as follows: The archives get uploaded into a temporary directory inside the webroot and extracted into a folder with a random name. Then a filter is applied that compares all extensions inside the archive with a whitelist and moves all files with allowed extensions to their final location – in this case the particulars surveys resources folder. When this process is completed the temporary directory and the zip file are deleted.

The first bug results from the way the name for the temporary folder is chosen. The following code generates these folders:

1
2
3
do {
      $path = $dir.$prefix.mt_rand(0, 9999999);
   } while (!mkdir($path, $mode)); 

A random value between 0 and 9.999.999 is used as the name of the folder, probably to make it hard to guess. After unpacking, the temporary archive and the unpacked folder are immediately deleted. This results in a short time frame that could be used to guess the name of the folder and call a malicious php script which is inside the archive (because all files get unpacked before any filters are applied). There are different ways to make it easier to guess the correct folder name and therefore execute a webshell. By uploading a ZIP-Bomb it is possible to cause high load on the system which extends the time available for guessing by up to 10 minutes, depending on the hardware.

Another way is to upload a zip file with a corrupt header which leads to the part of the code that would delete the file and folder not being triggered. An attacker can upload a large amount of corrupt zip files and therefore reduce the space of available numbers a lot. By sending GET-Requests to random numbers and looking at the results it is possible to determine which numbers are left for new uploads. Now the attacker can upload malicious zip files and guess their names with higher probability for success. I reported the issues on 14.08.2018 and they were fixed very quickly on the following day in 91d1432 and 20fc85e.

Another even more severe bug resides in the used version of the pclzip library. By using relative paths inside the zip file one can escape the temp directory and write to any location inside web root. The following command can be used to create such an archive: zip -r payload.zip ../shell.php. Since only the zip file and the created folder inside the temp directory are deleted on success or failure the shell persists. This was reported on 14.08.2018 and fixed in 72a02eb on 15.08.2018. The issues have been assigned CVE-2018-1000658 and CVE-2018-1000659.

This post is licensed under CC BY 4.0 by the author.