文件包含漏洞学习笔记
文件包含
基本概念
程序的开发人员为了方便代码的引用,减少代码的重写率,因此开发出文件包含的函数,能够直接通过文件包含函数解析另一个文件的代码,进而增加了开发的效率。
漏洞形成的原因
在包含文件时,由于没有正确的判断和验证来源文件的可靠性,从而导致了用户可以随意的进行文件包含,进而可能导致文件泄露或者恶意代码的注入。
相关函数
1 | 1.include() //包含时遇到报错仅仅发出警告然后继续执行 |
对include和require,k0rz3n师傅做了个很形象的比喻
在php这个工厂里,include() 是一个比较松散的员工,平时没有活干的时候他就闲着,
从来不想着自己看有什么活主动一点,于是只有当程序运行到 include() 的时候他才会执行,
并且呢也因为他松散的天性,在出错的时候他也只是报一个警告,并不会让程序中断
但是,有另一名员工 require() 他能力和include() 差不多,但是他这个人就非常的有上进心,
工作认真负责,他一看到程序运行就立刻包含,不会像include() 一样等别人催,
并且 require() 还会在出错的时候非常认真地报错,并小心谨慎地阻止程序继续运行
文件包含的类型
1.本地文件包含(LFI)Local file include
本地文件包含漏洞,顾名思义,指的是能打开并包含本地文件的漏洞。
大部分情况下遇到的文件包含漏洞都是LFI
2.远程文件包含(RFI)Remote file include
是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的,
因此漏洞一旦存在危害性会很大。
但RFI的利用条件较为苛刻,需要php.ini中进行配置
造成的参数
1 | 1.allow_file_fopen() |
本地测试(借用k0rz3n师傅的测试)
include()
1 | $file = $_GET['file']; |
1.allow_url_fopen = Off | allow_url_include = Off
(1)普通方式包含本地文件(正常)
(2)普通方式包含远程文件(不正常)
(3)伪协议方式包含文件(不正常)
2.allow_url_fopen = Off | allow_url_include = On
(1)普通方式包含本地文件(正常)
(2)普通方式包含远程文件(不正常)
(3)伪协议方式包含文件(正常)
3.allow_url_fopen = On | allow_url_include = Off
(1)普通方式包含本地文件(正常)
(2)普通方式包含远程文件(不正常)
(3)伪协议方式包含文件(不正常)
4.allow_url_fopen = On | allow_url_include = On
(1)普通方式包含本地文件(正常)
(2)普通方式包含远程文件(正常)
(3)伪协议方式包含文件(正常)
结论
<b>对于include()</b>
allow_url_include的开启对伪协议php://input 和 data://
起着决定性的作用,对其他的伪协议似乎没什么影响
allow_url_fopen单独开启没什么实质作用,
但是和allow_url_include结合就能实现远程包含
require()
1 | $file = $_GET['filename']; |
结论
与include()一致
readfile()
1 | $file = $_GET['filename']; |
1.allow_url_fopen = Off | allow_url_include = Off
(1)普通方式读取本地文件(正常–>源码中出现)
(2)普通方式读取远程文件(不正常–>报错)
(3)伪协议方式读取文件(不正常 –>源码不出现)
2.allow_url_fopen = Off | allow_url_include = On
(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(不正常–>报错)
(3)伪协议方式读取文件(正常–>源码出现)
3.allow_url_fopen = On | allow_url_include = Off
(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(正常–>源码出现)
(3)伪协议方式读取文件(正常–>源码出现)
4.allow_url_fopen = On | allow_url_include = On
(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(正常–>源码出现)
(3)伪协议方式读取文件(正常–>源码出现)
结论
对于readfile()
allow_url_include 对其没有任何影响
allow_url_fopen 能让其读取远程文件
file_get_contents()
1 | $file = $_GET['filename']; |
1.allow_url_fopen = Off | allow_url_include = Off
(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(不正常–>报错)
(3)伪协议方式读取文件(正常–>源码出现)
2.allow_url_fopen = Off | allow_url_include = On
(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(不正常–>报错)
(3)伪协议方式读取文件(正常–>源码出现)
3.allow_url_fopen = On | allow_url_include = Off
(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(正常–>源码出现)
(3)伪协议方式读取文件(正常–>源码出现)
4.allow_url_fopen = On | allow_url_include = On
(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(正常–>源码出现)
(3)伪协议方式读取文件(正常–>源码出现)
结论
对于file_get_contents()
allow_url_include 对其没有任何影响
allow_url_fopen 能让其读取远程文件
伪协议
php://filter
php://filter
这是一个过滤器,包含了许多的过滤方法,如果不想执行被包含文件的代码,
就可以使用这个,通常运用base64或者rot13读取源码
条件://不需要两个allow_url的开启即可进行使用
例子:
?file=php://filter/read=convert.base64-encode/resource=flag.php
?file=php://filter/read=string.rot13/resource=flag.php
?file=php://filter/write=string.rot13|<?cuc riny($_cbfg[1])?>/resource=flag.php
php://input
php://input
是个可以访问请求的原始数据的只读流,可以读取到POST没有解析的原始数据,
将POST中的数据作为PHP代码执行
条件://allow_url_include = On
例子:
GET:?file=php://input
POST:cmd= <?php eval($_POST[1])?>//执行一句话木马,连接蚁剑
data://text/plain
data://text/plain
条件://allow_url_include=On | allow_url_fopen=On
例子:
?file=data://text/plain,<?php phpinfo()?>
?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
本地文件包含(LFI)
一、本地文件读取
1 | $file=$_GET['file']; |
二、日志包含
一般情况下,系统会对对外开放的服务进行日志记录,因此我们能够通过写入恶意代码到日志文件中,然后对日志文件经行包含从而执行恶意代码,达到我们的目的。
条件:
明确日志文件的位置路径
日志文件的位置的确定就要前期的信息收集,一方面确定默认的日志的位置,另一方面可以利用这个包含漏洞包含一些配置文件寻找日志的位置
在实战中可以利用字典进行fuzz,也可以找一些探针文件来发现泄露的路径
1.首先我们先写入一句正确的php代码到access.log中
2.利用文件包含日志文件,然后进行解析
通过bp抓包,然后写入一句话,响应为200即算写进日志里
通常是在User-Agent中写入一句话马,但是我这次是在URL里写的
然后再在本地找到日志文件
最后对找到的路径进行包含即可
本地还可以利用报错文件error.log,原理相同,就不再展示了
三、Session文件包含
1.PHP 会将会话中的数据设置到 $_SESSION变量中。
2.当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行序列化,然后发送给会话保存管理器来进行保存。
3.对于文件会话保存管理器,会将会话数据保存到配置项 session.save_path 所指定的位置。
常见的php-session存放位置:
/var/lib/php/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
原理跟日志包含差不多
可以看看我以前写的这一篇
https://cqjkl55.github.io/2022/08/19/Session%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
四、利用session.upload_progress进行文件包含
session.upload_progress.enabled = on
session.upload_progress.cleanup = on
session.upload_progress.prefix = “upload_progress_”
session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”
session.use_strict_mode=off
session.upload_progress.enable=on表示upload_progress功能开始
也意味着当浏览器向服务器上传文件的时候,php会把这个文件的详细信息
(上传时间,进度等,最重要的是有我们写的PHP的一句话马)存储在session中session.upload_progress.clearup=on可以理解为文件上传结束后,php会立刻马上清空session文件中的内容
session.upload_progress.name,这个值是可控的
session.upload_progress.prefix+session.upload_progress.name将表示为session中的键名
session.use_strict_mode=off,默认值为off,我们可以对sessionid可控
问题1:如果代码中没有session_start(),那么该如何创建session文件了?
session.auto_start=On,PHP接受请求会自动初始化Session,不需要session_start(),但是默认情况下为off
但是session.use_strict_mode默认值为0,利用这个就可以自定义Session ID
比如在Cookie中设置PHPSESSION=AAA, PHP就会自动在服务器上创建一个文件(/tmp/sess_AAA)
并产生一个键值,这个键值有ini.get("session.upload_progress.prefix")+
由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。
问题2:默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容立即清空,如何进行rce呢?
这时候就要用到条件竞争了,可以写py的脚本多线程,也可以自己写个文件上传的HTML,
通过不停的BP发包,不停的访问/tmp/sess_AAA.
这里献上TGAO师傅的py脚本
1 | import io |
还有一个Lxxx师傅的脚本
1 | import requests |
如果按着Lxxx师傅写的脚本,写入/tmp/sess_Lxxx中的内容如下
upload_progress_<?php eval($_POST[1]);?>|a:5:{s:10:"start_time";i:1631343214;s:14:"content_length";i:276;s:15:"bytes_processed";i:276;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"Lxxx.jpg";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1631343214;s:15:"bytes_processed";i:276;}}}
本地写的HTML文档例如下:
1 | <!DOCTYPE html> |
参考
https://www.yuque.com/tianxiadamutou/so86eg/ya5sm8