Path traversal in Monstra CMS
In the administration interface of Monstra CMS, there is an option to download a backup file. This request can be modified to download any file on the server.
Backups in Monstra
Monstra is a CMS written in PHP, that stores its data in XML files instead of a database. It has the functionality to create and download backup files:
When we click the link, it performs a request to:
http://server/admin/index.php?id=backup&download=2018-06-21-11-00-30.zip&token=b3efc3d30fefd83f90dcfc59550b13be025b304a
The download
parameter looks like a filename. The corresponding source code looks pretty straightforward:
File::download($backups_path . DS . Request::get('download'));
Serve the file from the download
parameter for download. It looks as it does no further checking or cleaning of the parameter.
Looking at Monstra’s directory layout, backup files seem to be in the backups
directory. We want to break out of that directory and download a file in another directory, such as README.md in the webroot. Let’s change the filename to ../README.md
:
It doesn’t work.
Bypassing sanitation
It seems our download
parameter is modified after all, just not in the place we expect. After some searching, I found the code in the Security class:
public static function sanitizeURL($url)
{
$url = trim($url);
$url = rawurldecode($url);
$url = str_replace(array('--', '"', '!', '@', '#', '$', '%', '^', '*', '(', ')', '+', '{', '}', '|', ':', '"', '<', '>',
'[', ']', '\\', ';', "'", ',', '*', '+', '~', '`', 'laquo', 'raquo', ']>', '‘', '’', '“', '”', '–', '—'),
array('-', '-', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''),
$url);
$url = str_replace('--', '-', $url);
$url = rtrim($url, "-");
$url = str_replace('..', '', $url);
$url = str_replace('//', '', $url);
$url = preg_replace('/^\//', '', $url);
$url = preg_replace('/^\./', '', $url);
return $url;
}
public static function runSanitizeURL()
{
$_GET = array_map('Security::sanitizeURL', $_GET);
}
So the $_GET
array containing all query parameters is cleaned up before use. Specifically, ..
is removed from it.
However, this can be easily bypassed. What we need is that the parameter does not contain ..
when entering sanitizeURL
, but does contain ..
afterward. We can use one of the last three replacements for that. For example, if we put .//.
in our parameter, the //
will be removed and we will be left with ..
.
In the last preg_replace
, the first dot is removed. Thus we need one more dot to prevent this, again escaping it with the double slash so that it doesn’t get removed. Our filename becomes:
.//.//./README.md
which will become ../README.md
after sanitation. When we pass this in the download parameter:
Conclusion
With this vulnerability, the administrator can download files from the web server. The sanitation that is specifically meant to prevent this can be bypassed.