点开就是源代码,直接进行代码审计
<?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;
}
?>
开始解答:
根据上半部分代码,我们可以分析出,如果需要输出flag,则需要满足以下两个条件:第一:$_SESSION['username'] ==='admin' 第二:存在/var/babyctf/success.txt
error_reporting(0);//报错不提示
session_save_path("/var/babyctf/");//session存储的位置
session_start();//session开始
require_once "/flag";//包含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';
}
// 此模块,如果$_SESSION['username'] ==='admin' 并且存在/var/babyctf/success.txt 输出flag,程序结束,
// 否则,$_SESSION['username'] ='guest'
根据下半部分代码,我们可以分析出,主要是介绍了两个函数:一个是upload,一个是download。
$dir_path = "/var/babyctf/".$attr; //设置路径为/var/babyctf/$attr
if($attr==="private"){
$dir_path .= "/".$_SESSION['username'];//路径为/var/babyctf/$attr/$_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'];//路径为/var/babyctf/$attr/$_SESSION['username']/.$_FILES['up_file']['name']
$file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);//路径为/var/babyctf/$attr/$_SESSION['username']/.$_FILES['up_file']['name']_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'));//basename返回路径下的文件名,即返回filename
$file_path = $dir_path."/".$filename;//路径为/var/babyctf/$attr/$_SESSION['username']/$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文件内容里面是什么格式,那么我们需要分析下载功能所需参数,首先$direction === "download",还需要传递一个filename,那么session的文件名格式,我们可以自己查看自身php中的tmp文件夹发现,php的session默认存储文件名都是sess_+PHPSESSID这种格式,通过F12找到自身phpsessid
到此为止,我们找到了filrname的值,即需要下载的2个参数已经找到,direction =download,filename=sess_f063a1296a5c959d3bee459c33f242df,在通过hackbar进行传递参数,注意是POST传递:
传递后,页面左下角刷新出:usernames:5:"guest";
右键源代码:我们查询资料可知:
php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
第一种,php类型生成的session文件为: ykingh|s:3:"123";
第二种,php_binary类型生成的session文件为:ykinghs:3:"123";
第三种,php_serialize类型生成的session文件为:a:1:{s:6:"ykingh";s:3:"123";}
由此可见,返回的usernames:5:"guest";没有竖线,又无{},因此我们可以判断这里session处理器为php_binary,那么我们可以在本地利用php_binary生成我们要伪造的session文件。首先进行php.ini修改,让本地的php环境支持php_binary
在本地编写sess的生成文件,代码如下:
<?php
session_start();//session开始
if(isset($_GET['name']))
$_SESSION['username']=$_GET['name'];
?>
运行以上代码,生成如下图:
由文件上传代码分析可得,我们需要修改这个文件名为sess,然后计算其sha256值。
由此可得该文件内容哈希值为:432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
根据题目源码,服务器在储存文件时在文件名后加上了_+sha256,因此我们上次的sess文件在服务器端名为sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4。然后我们查看上传文件需要的参数,direction=upload,attr可为空,
现在根据源代码,编写上传代码,因为我们需要上传我们的admin的sess文件:
<form action="http://0beb395b-65d0-4163-90d0-7ddeb1ecb427.node4.buuoj.cn:81/" method="post" enctype="multipart/form-data">
file: <input type="file" name="up_file"/><br>
<input type="submit" value="Send"/>
<input type="text" name="direction" value="upload"/><br>
<input type="text" name="attr" value=""/><br>
</form>
构造如下payload,查看是否上传成功,如下图,admin信息已经上传成功
现在我们还需要满足 $filename='/var/babyctf/success.txt';此时上传文件为如下图:
上传成功后,路径问题已经解决,现在需要满足$_SESSION['username'] ==='admin',admin文件我们也已经上传,此时只需要在hackbar中或者bp中改变我们的cookie值,就会让网站认为我们是admin,如下图:
则输出
flag{b47727e4-e979-470b-a57f-f0d91340703f}
最后附上代码当中的一些注释便于理解:
<?php
error_reporting(0);//报错不提示
session_save_path("/var/babyctf/");//session存储的位置
session_start();//session开始
require_once "/flag";//包含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';
}
// 此模块,如果$_SESSION['username'] ==='admin' 并且存在/var/babyctf/success.txt 输出flag,程序结束,
// 否则,$_SESSION['username'] ='guest'
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
// 通过filter_input方法以POST的传参方式传输两个属性,分别是direction和attr
$dir_path = "/var/babyctf/".$attr; //设置路径为/var/babyctf/$attr
if($attr==="private"){
$dir_path .= "/".$_SESSION['username'];//路径为/var/babyctf/$attr/$_SESSION['username'],如果$attr==="private"
}
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'];//路径为/var/babyctf/$attr/$_SESSION['username']/.$_FILES['up_file']['name']
$file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);//路径为/var/babyctf/$attr/$_SESSION['username']/.$_FILES['up_file']['name']_hash_file("sha256",$_FILES['up_file']['tmp_name'])
//hash_file这个函数为:给指定文件的 内容 生成哈希值,注意是内容
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'));//basename返回路径下的文件名,即返回filename
$file_path = $dir_path."/".$filename;//路径为/var/babyctf/$attr/$_SESSION['username']/$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;
}
?>