首先来说下文件包含是什么,粗俗的讲就是本网页包含其他源代码来执行。此处本意正如编程中的函数调用,重复的代码大可不写。合理调用利用重复代码才是王道。

那么文件包含漏洞是什么呢?文件包含不该是正常网站的一个功能吗?用尘哥的话说就是xx程序员多了,就有了漏洞,就有了安全这行业。本来应该包含正确代码到文件中去执行的,但是在包含这个地方没有做好过滤。导致我们能让网站随我们心意“任意”执行我们想要做的“事情”。

在phpMyadmin4.0 4.1中就有这么一处包含的位置过滤不严导致可控。我们通过文件包含去调用我们“上传”到网站上的恶意代码,如:一句话木马。只要将其包含就可以“执行”。
让我们先来认识下PHP中文件包含的函数:

include()

使用此函数,只有代码执行到此函数时才将文件包含进来,发生错误时只警告并继续执行整个php文件。

include_once()

这个函数跟include函数作用几乎相同,只是他在导入函数之前先检测下改文件是否被导入。如果已经执行一遍那么就不重复执行了。

require()

使用此函数,只要程序执行,立即调用此函数包含文件,发生错误时,会输出错误信息并立即终止程序。

require_once()

功能和前者一样,区别在于当重复调用同一文件时,程序只调用一次。

先让我们大致的浏览的一下phpMyadmin网站的结构。既然说是文件包含,那么漏洞点必然在包含的位置那。大致浏览了一波,好像没有找到明显的?xxx=xx.php这种页面。那么直接上手源代码吧,提示漏洞出在index.php中。让我们依次去查找文件包含的关键字:

include()


搜索全文后发现只有这四个地方用到了包含include函数.

include_once()

就只有一处

require

就只有这一处

Require_once

也只有这么一处。
因为我们需要被包含文件位置可变(目前非可控)。所以除去所有相对路径后只剩下include中的两处了。
分别是:

先让我们来分析下include $page的条件(哦吼,php学的烂看不懂。让我百度一波)

$page中的数值会在函数中判断后由Utile类getScriptNameForOption函数返回值赋予。继续溯源到Util::getScriptNameForOption中去。

查看Util.php源代码,定位到3064行——3110行。代码分别如下

总的看了下它共有三个判断语句,如果传入的$location等于server、database、table中的一个。那么就进行swith($target)选择一个值return返回。由此可见,这个包含的地方不是漏洞点。
现在只剩下include $_REQUEST[‘target’];了。

首先对涉及到的函数进行百度(菜是原罪)

1.empty:若变量已存在、非空字符串或者非零,则返回false值,反之返回true。

2.is_string:检测变量是否是字符串,如果是字符串返回TURE,反则返回FALSE。

3.preg_match:用于执行一个正则表达式匹配

4.in_array:搜索数组中是否存在指定的值。

5.将target传去Core类的checkPageValidity进行判断。

如果以上五项都为True,则执行下面的include $_REQUEST[‘target’];

让我们依次来分析各项为True的条件。

empty():要让后面的’target’存在或者是空字符串或者非零。这样empty返回0,和前面的!非一下就成了1.

is_string:target的数值要为字符串。

preg_match:如果target满足正则条件则返回1。target字符串的头不能为index,否则!1就返回0。

in_array:target中的值不能出现在$target_blacklist中。$target_blacklist中有’import.php’和’export.php’。只要’target’中的值不包含’import.php’和’export.php’就可以了

Core::checkPageValidity:将’target’的值传入core类的checkPageValidity函数中去进行判断。如果返回的数值为True,那么就为1。对Core::checkPageValidity进行溯源:

进入Core.php,定位checkPageValidity函数进行判断。

大致查看下有哪些判断条件,共有五个判断语句。如下逐个分析:

在这个if中,因为我们没有给whitelist传送一个值过来,所以导致这里empty($whitelist)为True。转而执行下面的$whitelist = self::$goto_whitelist;在slfe::$goto_whitelist中存放着白名单。这里将白名单赋予给$whitelist变量。

goto_whitelist白名单如下(部分):

在这里变量page也就是传入的target要被定义过或是字符串,否则返回false。

我们传入的变量如果是白名单里的一种,那么就返回true。

5.这里用到了mb_substr函数和mb_strpos函数。mb_substr的作用是返回字符串的一部分。mb_strpos这里是判断后面字符出现的位置。这里总的含义就是将变量page里出现在?号前面的数据截取后传入$_page。如下举例:

在这里我曾有一个zz的地方,知道mb_strpos函数的作用后不免思考:为什么要加上一个?号后再判断它位置来截断呢?毫无意思的操作,自己加然后截断自己的参数。从而导致我觉得.号作用不是拼接而有其他意思。后来如上图试验后才明白函数原来是从左往后去判断截断的。如果在用户输入有?的前提下将文件名和参数分开,没有?时也可以取出文件名。

但是在这里有一个问题,正如我们上面使用的语句:
db_sql.php?/../../../../../../etc/passwd。它虽然绕过了之前所有的过滤,但是在php中问号后面的是会被当作参数来执行的。那还是达不到包含文件的作用的!继续来看下面的参数!

5.在这里有一个很有趣的地方,那就是urldecode()。他的作用是将url编码解码一次,那么在这里就很有意思了!

它将解码后的在拿去判断,这是无用的!因为php最后包含的是解码之前的代码!如下所示:

文件包含的是db_sql.php%3f/../../../../../../etc/passwd,可解码后判断的是db_sql.php?/../../../../../../etc/passwd这是不一样的。这这个if里我们顺利通过了关键字白名单判断且用正确格式包含了自己想要得到的数据。

综合上面所有的判定条件。我们要通过target参数传入一串字符串,字符串前面的文件名要在白名单中,且?要进行二次url编码(浏览器传送时会自动解码一次)。这样就可以绕过所有的过滤达到我们要的效果了。

如下所示:

因本人很菜,无法在默认配置下利用文件包含漏洞点去拿getshell。至此只写下如何查看phpinfo()。

一.通过数据库写入phpinfo()从而来包含。

再来查看当前数据库位置

具体目录根据保存规则推算。在这里tt.frm得允许其他人可读。

二.第二种方法就是网上常见得包含Php日志。

在这里不需要sess_sxxxx有读权限。因为文件本身的所有者就是apache。二第一种方法之所以要其他人读权限。是因为数据库本身的所有者是apache