[HFCTF2020]BabyUpload

平台:https://buuoj.cn

打开靶机给出源码


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;
}
?>
分析一下

前面设置了session存储路径,启动了session并根目录下包含flag

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

如果session的username是admin,判断/var/babyctf下是否有success.txt,如果存在,删除文件并输出$flag
否则设置username为guest

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

设置两个post参数direction、attr,$dir_path拼接路径,若$attr为private,在$dir_path的基础上再拼接一个username

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

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

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设置为download,读取上传上来的文件名,拼接为$file_path,限制目录穿越,判断是否存在,存在则返回文件内容。

elseif ($direction === "download") {//如果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;
}
解题

可知要获取flag需满足:

$_SESSION[‘username’] ===‘admin’
$filename=’/var/babyctf/success.txt’

也就是说我们要伪造自己的username是admin,并创建一个success.txt文件。

伪造session

php的session默认存储文件名是sess_+PHPSESSID的值,我们先看一下session文件内容。
查看cookie中PHPSESSID
[HFCTF2020]BabyUpload_第1张图片
构造direction=download&attr=&filename=sess_5373dbeb80f6189c95463d7a6c3881a9post传入,在返回内容中读到内容
[HFCTF2020]BabyUpload_第2张图片
可以看到还有一个不可见字符,参考这位师傅的文章得知,
不同的引擎所对应的session的存储方式有

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

因此我们可以判断这里session处理器为php_binary,那么我们可以在本地利用php_binary生成我们要伪造的session文件。


ini_set('session.serialize_handler', 'php_binary');
session_save_path("D:\\phpstudy_pro\\WWW\\testphp\\");
session_start();

$_SESSION['username'] = 'admin';

运行生成session文件
[HFCTF2020]BabyUpload_第3张图片
将文件名改为sess并计算sha256
在这里插入图片描述
这样,如果我们将sess文件上传,服务器储存该文件的文件名就应该是
sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
用postman将文件传上去
[HFCTF2020]BabyUpload_第4张图片
构造direction=download&attr=&filename=sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4看是否上传成功
[HFCTF2020]BabyUpload_第5张图片
这样就实现了伪造

创建success.txt

现在还需要创建一个success.txt来满足判断,回到代码

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

filename是通过file_exists来判断的,而file_exists函数在php中
[HFCTF2020]BabyUpload_第6张图片
文件名设置不了,直接创建目录也符合条件,将attr设置为success.txt创建目录,再将sess上传到该目录下即可绕过判断
[HFCTF2020]BabyUpload_第7张图片
可以看到已经上传成功
[HFCTF2020]BabyUpload_第8张图片
那么现在我们把PHPSESSID改为sess的文件sha256值让session的username为admin
[HFCTF2020]BabyUpload_第9张图片
再刷新一下即可得到flag
[HFCTF2020]BabyUpload_第10张图片

你可能感兴趣的:(代码审计,ctf)