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 =>...
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; 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>