起因 一个很简单的漏洞, 分析的文章也都出来了很多, 但是看了那些文章, 我一直搞不懂为啥问号要双重url编码, 我自己看那些文章感觉不编码应该也能成功利用的。 并且好像大家对问号编码的说法各有不同。 还是自己下载了份源码看了一下。
4.8.1下载地址:https://files.phpmyadmin.net/phpMyAdmin/4.8.1/phpMyAdmin-4.8.1-all-languages.zip
分析 在index.php中
1 2 3 4 5 6 7 8 9 10 // If we have a valid target, let's load that script instead 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; }
可以加载一些脚本。 直接看最后一个检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public static function checkPageValidity (&$page, array $whitelist = [] ) { if (empty ($whitelist)) { $whitelist = self ::$goto_whitelist; } if (! isset ($page) || !is_string($page)) { return false ; } if (in_array($page, $whitelist)) { return true ; } $_page = mb_substr( $page, 0 , mb_strpos($page . '?' , '?' ) ); if (in_array($_page, $whitelist)) { return true ; } $_page = urldecode($page); $_page = mb_substr( $_page, 0 , mb_strpos($_page . '?' , '?' ) ); if (in_array($_page, $whitelist)) { return true ; } return false ; }
return true了, 就直接include了。
直接传 db_sql.php?/../robots.txt 就能成功包含到了。 不需要编码, 因为这时候 include ‘db_sql.php?/../robots.txt’ 在include中, ?号后面的字符 并不会当成query, 问号也就只是路径的一部分而已。 所以不需要编码, 利用/../ 也能跳出目录。
Getshell的话, 根据p师傅的思路,执行sql语句后, 直接包含session文件就行。
1 2 3 4 5 6 7 $session_name = 'phpMyAdmin'; @session_name($session_name); // Restore correct sesion ID (it might have been reset by auto started session if (isset($_COOKIE['phpMyAdmin'])) { session_id($_COOKIE['phpMyAdmin']); }
phpmyadmin的session_name 是phpMyAdmin
执行的sql语句存入session的有两个位置。 一个就是sql_history。 /libraries/classes/Relation.php中
1 2 3 4 5 $_SESSION['sql_history' ][] = array ( 'db' => $db, 'table' => $table, 'sqlquery' => $sqlquery, );
这里的$sqlquery 就是所执行的sql语句, 然后存入到了$_SESSION当中。
修复 4.8.2中, 修复了这个漏洞。
1 2 3 4 5 6 7 8 9 if (! empty($_REQUEST['target']) && is_string($_REQUEST['target']) && ! preg_match('/^index/', $_REQUEST['target']) && ! in_array($_REQUEST['target'], $target_blacklist) && Core::checkPageValidity($_REQUEST['target'], [], true) ) { include $_REQUEST['target']; exit; }
在调用checkPageValidity方法的时候, 第三个参数设置为了true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public static function checkPageValidity(&$page, array $whitelist = [], $include = false) { if (empty($whitelist)) { $whitelist = self::$goto_whitelist; } if (! isset($page) || !is_string($page)) { return false; } if (in_array($page, $whitelist)) { return true; } if ($include) { return false; } $_page = mb_substr( $page, 0, mb_strpos($page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; }
当第三个参数为true的时候, 在mb_substr之前, 就return false了。 所以, 现在真的只能包含白名单内的文件了。