打开题目一张图片,直接查看源码,提示source.php:

访问/source.php得到源码:
1 |
|
题目接受一个get参数file:

这个参数需要满足2个条件:
- 这个参数得是string类型的;
- 这个参数传入
emmm::checkFile()函数后需要返回true。
再来看一下这个checkFile函数,这个函数里有两个陌生的函数:mb_substr和mb_strpos。
mb_substr
Syntax:
mb_substr ( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]] ) : string
$str:必需,会从该string中提取子字符串;
$start:必需,规定从字符串的何处开始;
$length:可选,规定要返回的字符串长度,默认就是到字符串的末尾;
encoding:可选,字符编码,默认是内部编码。Return: 返回字符串的提取部分,如果失败则返回 FALSE,或者返回一个空字符串。
mb_strpos 同 strpos函数
Syntax:
strpos(string,find,start)
string:必需,被搜索的字符串;
find:必需,要查找的字符串;
start:可选,开始搜索的位置。Return: 返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。
然后这里还用到了in_array($page, $whitelist)来判断传入的参数$page(就是原始的$_REQUEST['file']参数)是不是在$whitelist中。
$whitelist是一个有序映射(key=>value):$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
而这里需要搞清楚的是in_array()它是匹配value而不是key,如下图这个例子,被匹配到的是aaa,即value,而不是key。

所以$_REQUEST参数值必需包括source.php或是hint.php。
所以直接令?file=hint.php,程序会执行include $_REQUEST['file'];也就是include "hint.php":

提示flag文件在ffffllllaaaagggg,所以我们需要构造使得最后include $_REQUEST['file']里能够包含这个文件。

在第17行可以看到,程序会从$page,也就是传入的参数中提取指定长度的字符串,这个长度由mb_strpos($page.'?', '?')决定。
mb_strpos会从$page."?"字符串中寻找第一次出现?符号的位置并返回,所以mb_substr($page, 0, mb_strpos($page. '?', '?'));会返回第一次出现?符号前面的字符串(不包括?符号)。
所以我们只要构造这样的payload:
1 | ?file=hint.php?../../../../../../../../../../ffffllllaaaagggg |
就可以在checkFile函数成功返回true,然后就会执行include $_REQUEST['file'],将flag文件成功包含。

CVE-2018-12613 phpmyadmin文件包含漏洞
这道题其实来自CVE-2018-12613,比起上面这道题目要稍微复杂一点。
漏洞描述
该漏洞可以让攻击者在服务器上查看或是执行文件。
漏洞利用前提是攻击者必须先经过身份验证,但以下两种情况例外:
$cfg['AllowArbitraryServer']=true:攻击者可以指定他已经控制的主机,在phpMyAdmin上执行任意代码;$cfg['ServerDefault'] = 0:能绕过登录,允许在没有经过任何身份验证的情况下运行容易受到攻击的代码。
这个漏洞还是挺严重的,官方的评级是Severe。
影响版本
phpMyAdmin 4.8.0和4.8.1
复现环境
- Ubuntu 18.04
- php 5.6.40 + phpMyAdmin 4.8.1 + Mysql 5.7
创建PHP My Admin用户:
1 | mysql> CREATE USER 'pma481'@'%' IDENTIFIED BY '123456'; |
授予超级用户权限:
1 | mysql> GRANT ALL PRIVILEGES ON *.* TO 'pma481'@'%' WITH GRANT OPTION; |
漏洞成因
index.php:

第55行的if条件需要满足5个条件:
!empty($_REQUEST['target']):target参数需要存在;is_string($_REQUEST['target']):target参数需要是string类型的;!preg_match('/^index/', $_REQUEST['target']):target参数不能以index开头;!in_array($_REQUEST['target', $target_blacklist):target参数值不能在$target_blacklist中;Core::checkPageValidity($_REQUEST['target']:target值需要通过该check函数。
$target_blacklist也定义在index.php中:

只要target的值不在$target_blacklist中就可以绕过了,所以前4个条件很容易满足。
跟进checkPageValidity函数(位于libraries/classes/Core.php文件):
1 | public static function checkPageValidity(&$page, array $whitelist = []) |
因为没有传进$whitelist参数,所以开始$whitelist被初始化为self::$goto_whitelist:
1 | public static $goto_whitelist = array( |
从checkPageValidity函数中可以看到,传入的$page参数中要通过该函数验证满足以下一个条件就可以:
$page参数包含在$whitelist之中;- 经过
mb_substr的$page参数(也就是$_page参数),需要包含在$whitelist中; - 对
$_page参数进行urldecode之后,再进行一个次mb_substr的结果在$whitelist中。

漏洞利用
poc:
1 | localhost/phpMyAdmin-4.8.1/index.php?target=db_sql.php%3f/../../../../../../../../etc/passwd |

getshell:
选择SQL,执行select语句:

php会序列化参数传递的值,然后保存在本地的session文件中,对于Linux的session保存目录就是/var/lib/php/sessions,对应的session文件就是sess_[your session id]。
所以F12查看一下当前查询的session:

所以对应的session文件就是/var/lib/php/sessions/sess_92o01utbpqift1ogm86flv4beeapadek。
payload:
1 | localhost/phpMyAdmin-4.8.1/index.php?target=db_sql.php%253f/../../../../../../../../var/lib/php/sessions/sess_92o01utbpqift1ogm86flv4beeapadek |

官方给出的缓解手段是设置正确的open_basedir,比如在php.ini中将open_basedir设置为/var/www/html,然后重启apache服务器,再执行上述getshell操作,发现被拦截了。

官方修复
index.php:

libraries/classes/Core.php:

从官方补丁来看,修复之后就是直接根据第一次in_array($page, $whitelist)的结果来判断$page参数的合法性。