xorl %eax, %eax

CVE-2009-1285: phpMyAdmin Code Injection

leave a comment »

Well, I usually don’t blog about these bugs but phpMyAdmin is a project that is used almost everywhere and this is a quick and dirty way to get code execution. This issue affects phpMyAdmin 3.x before 3.1.3.2 and it was disclosed on 14 April 2009. The bug is present at setup/lib/ConfigFile.class.php file. Here is an outline of that file from 3.1.3.1 release:

1 <?php
    ...
10 class ConfigFile
11 {
12     /**
13      * Stores default PMA config from config.default.php
14      * @var array
15      */
16     private $cfg;
    ...
259     /**
260      * Creates config file
261      *
262      * @return string
263      */
264     public function getConfigFile()
265     {
266         $crlf = (isset($_SESSION['eol']) && $_SESSION['eol'] == 'win') ? "\r\n" : "\n";
267         $c = $_SESSION['ConfigFile'];
268
269         // header
270         $ret = '<?php' . $crlf
    ...
279         // servers
280         if ($this->getServerCount() > 0) {
281             $ret .= "/* Servers configuration */$crlf\$i = 0;" . $crlf . $crlf;
282             foreach ($c['Servers'] as $id => $server) {
283                 $ret .= '/* Server: ' . $this->getServerName($id) . " [$id] */" . $crlf
284                     . '$i++;' . $crlf;
285                 foreach ($server as $k => $v) {
286                     $ret .= "\$cfg['Servers'][\$i]['$k'] = "
287                         . var_export($v, true) . ';' . $crlf;
288                 }
289                 $ret .= $crlf;
290             }
291             $ret .= '/* End of servers configuration */' . $crlf . $crlf;
292         }
    ...

So… function getConfigFile() retrieves various information. Here it constructs a configuration file and $ret includes the PHP code. At line 281 it starts the file with comment:

/* Servers configuration */

Then, as you can clearly see at line 283 the configuration will have a new comment which is:

/* Server: <getServerName()> "id" */

However, $id is completely user controlled since it’s derived from the session variable ConfigFile at line 267. For example, if a user specifies an $id of:

bleh */ <?php echo date(); ?> /*

He will end up with a configuration file that includes this:

/* Server: <getServerName()> bleh */ <?php echo date(); ?> /* */

This simple code injection was patched by limiting the user input using preg_replace() function like this:

             foreach ($c['Servers'] as $id => $server) {
+                $k = preg_replace('/[^A-Za-z0-9_]/', '_', $k);
                 $ret .= '/* Server: ' . $this->getServerName($id) . " [$id] */" . $crlf

Which replaces any matches of /[^A-Za-z0-9_]/ with _ and moves on with the next element. The same bug was also in the following code of the same function:

296         // other settings
297         $persistKeys = $this->persistKeys;
298         foreach ($c as $k => $v) {
299             $ret .= "\$cfg['$k'] = " . var_export($v, true) . ';' . $crlf;
300             if (isset($persistKeys[$k])) {
301                 unset($persistKeys[$k]);
302             }
303         }

Where the exact same logic applies and also the same patch :-P

         foreach ($c as $k => $v) {
+            $k = preg_replace('/[^A-Za-z0-9_]/', '_', $k);
             $ret .= "\$cfg['$k'] = " . var_export($v, true) . ';' . $crlf;

There was another instance of that bug at the last loop of that function which was this:

305         // keep 1d array keys which are present in $persist_keys (config_info.inc.php)
306         foreach (array_keys($persistKeys) as $k) {
307             if (strpos($k, '/') === false) {
308                 $ret .= "\$cfg['$k'] = " . var_export($this->getDefault($k), true) . ';' . $crlf;
309             }
310         }
311         $ret .= '?>';
312
313         return $ret;
314     }
315 }
316 ?>

Again, the concept is the same in the foreach() loop at line 306 and the patch was of course:

             if (strpos($k, '/') === false) {
+                $k = preg_replace('/[^A-Za-z0-9_]/', '_', $k);
                 $ret .= "\$cfg['$k'] = " . var_export($this->getDefault($k), true) . ';' . $crlf;

Finally, I totally agree with Secunia’s advice:

Successful exploitation requires that installation best-practices have not been followed and the setup scripts have not been deleted after a successful installation.

The evil auditors among us would have caught that the bug is still there ;-)

Don’t tell anyone… (I usually don’t write publicly about such things but what the hell… It’s just phpMyAdmin)!

Written by xorl

April 23, 2009 at 17:19

Posted in bugs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s