CVE-2009-1285: phpMyAdmin Code Injection

2009-04-25T00:00:00
ID SSV:5112
Type seebug
Reporter Root
Modified 2009-04-25T00:00:00

Description

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 =&gt; $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 =&gt; $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;

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

phpMyAdmin 3.1.3.2 暂无 <a href=http://www.phpmyadmin.net/home_page/security/PMASA-2009-4.php target=_blank rel=external nofollow>http://www.phpmyadmin.net/home_page/security/PMASA-2009-4.php</a> <a href=http://www.phpmyadmin.net/home_page/index.php target=_blank rel=external nofollow>http://www.phpmyadmin.net/home_page/index.php</a>