文件包含漏洞总结

文章发布时间:

最后更新时间:

文章总字数:
2k

预计阅读时间:
7 分钟

文件包含漏洞总结

之前对文件包含漏洞的理解一直浅薄地停留在include+php伪协议,正好今天复现CISCN2022 backdoor的时候又遇到了session包含,于是就来系统学习总结一下。

基础概念与漏洞成因

以PHP为例,常用的文件包含函数有以下四种
include(),require(),include_once(),require_once()

require():找不到被包含的文件会产生致命错误,并停止脚本运行
include():找不到被包含的文件只会产生警告,脚本继续执行
require_once()与require()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
include_once()与include()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含

而四种中最常用的是**include()**,文件包含漏洞产生的最根本原因就是include()函数并不在意被包含的文件是什么类型,只要文件的数据里面有php代码就会自动解析。也就是说只要有地方能让我们写php语句进去,然后包含该文件就能执行代码。

文件包含漏洞利用

在ctf比赛中最常出现的是本地的文件包含,即LFI。

任意敏感文件读取

windows:

1
2
3
4
5
6
7
8
C:\boot.ini //查看系统版本
C:\Windows\System32\inetsrv\MetaBase.xml //IIS配置文件
C:\Windows\repair\sam //存储系统初次安装的密码
C:\Program Files\mysql\my.ini //Mysql配置
C:\Program Files\mysql\data\mysql\user.MYD //Mysql root
C:\Windows\php.ini //php配置信息
C:\Windows\my.ini //Mysql配置信息
C:\Windows\win.ini //Windows系统的一个基本系统配置文件

linux:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_ras.keystore
/root/.ssh/known_hosts //记录每个访问计算机用户的公钥
/etc/passwd
/etc/shadow
/etc/my.cnf //mysql配置文件
/etc/httpd/conf/httpd.conf //apache配置文件
/root/.bash_history //用户历史命令记录文件
/root/.mysql_history //mysql历史命令记录文件
/proc/mounts //记录系统挂载设备
/porc/config.gz //内核配置文件
/var/lib/mlocate/mlocate.db //全文件路径
/porc/self/cmdline //当前进程的cmdline参数

可以使用绝对路径读取,也可以使用../来跳出目录限制读取,至于直接读到flag还是别想了

本地文件包含漏洞利用

  • 配合文件上传,直接上传一个webshell.jpg(白名单检测下),再通过include将这个文件包含进来就可以获得webshell。值得注意的是一般这样的webshell写法是:

    1
    2
    3
    <?php
    fwrite(fopen("shell.php","w"),'<?php eval($_POST[123]);?>);
    ?>

    如果目标文件夹可以支持写操作最好这样,否则webshell可能功能有点问题,但是一般都不支持直接往上面写文件(笑),于是也只能写正常的一句话马。

  • 包含Apache日志文件

    条件是:日志文件知道存储目录或者可读,但是一般情况下日志文件并不会放在默认路径,需要读取服务器配置文件.conf或者通过phpinfo的值

    当用户发起请求的时候,不管成功与否,服务器都会把该请求写入access.log,但是写的时候经过了url编码,所以我们要先传参,然后bp截包改回原来的,否则就算包含进来php语句不会被解析。

  • 包含SESSION文件

    在phpinfo里找到session文件存放的位置,或者猜一猜常见的位置:

    • /var/lib/php/sess_PHPSESSID

    • /var/lib/php/sess_PHPSESSID

    • /tmp/sess_PHPSESSID(最常见)

    • /tmp/sessions/sess_PHPSESSID

    session文件格式:sess_[phpsessid],而phpsessid在发送的请求的cookie字段中可以看到。通过控制cookie我们可以控制session_start时访问的session文件,从而对特定的文件内容进行序列化和反序列化。

  • 包含临时文件

    通过条件竞争。当我们找不到用于触发RCE的有效文件时,如果可以访问phpinfo(它可以告诉我们临时文件的随机生成的文件名及其位置)(且如果服务器后端的处理逻辑是先上传再检测,而不是先检测再上传),我们可能可以包含一个临时文件来利用它升级为RCE。

    利用方式:

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

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

    然而,我们需要先发送数据包给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

    简单来说,就是抓时间差,简单利用的话可以一边使用bp重复上传文件,一边用python脚本不断访问上传了的文件,访问成功就成功了。

利用php伪协议来攻击

  • file://协议:用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响,XXE注入漏洞中就有使用过file://协议

  • php://协议:最常用的是filter和input,filter可以读源码,记得base64加密,而input可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行

  • ZIP://协议:没怎么用到过,只记得用zlib和zlib2可以绕过phar开头的检测(都是压缩包)

    • zip://中只能传入绝对路径。
    • 要用#分割压缩包和压缩包里的内容,并且#要用url编码成%23(即下述POC中#要用%23替换)
    • 只需要是zip的压缩包即可,后缀名可以任意更改。
    • 相同的类型还有zlib://和bzip2://
  • data://协议,可以写入,和input类似

    1
    2
    3
    data://text/plain,<?php phpinfo();?>
    //如果此处对特殊字符进行了过滤,我们还可以通过base64编码后再输入:
    data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

参考链接

文件包含漏洞全面详解_caker丶的博客-CSDN博客

文件包含利用方式(总结) - PANDA墨森 - 博客园 (cnblogs.com)