CTF知识集-反序列化
师傅们的教学
1 | https://blog.csdn.net/solitudi/article/details/113588692 |
写在开头可能用到的提醒
- 序列化的字符串,如:O:11,可写成O:+11,类似于S:4也可
- 绕过__wakeup(),属性超过原定义属性个数
- 限定版本:PHP5-5.6.25 | PHP7-7.0.10
- 十六进制绕过,将序列化的字符串小写的s改为大写S,里面的字符串可用十六进制代替
- 例如 s:”name” 可改为 S:”n\97me”
- 字符串序列化还是字符串本身,例如:serialize(test),那么输出还是test
- 当__serialize和__sleep方法同时存在,序列化时忽略__sleep方法而执行__serialize;当__unserialize方法和__wakeup方法同时存在,反序列化时忽略__wakeup方法而执行__unserialize。
魔术方法
- __construct(),在对象被new的时候自动调用
- __destruct(),当对象被销毁的时候会自动调用
- __wakeup(),当执行反序列化的时候执行也就是unserialize()
- __sleep(),当执行序列化的时候执行也就是serialize()
- __invoke(),当对象被当成函数调用时触发,例如$a()
- __call() 当从对象中调用不存在的方法时触发,例如a对象中没有b方法,触发
- __get(),在对象中调用不存在的变量时候触发
- __set(),在对象中写入不可写入的的属性时触发
- __isset(),在不可访问的私有属性调用iset()或empty()时候触发
- __unset,在不可访问的私有属性上使用unset()时触发
- __tostring(),把类当成字符串来调用时候触发,或者把对象当成字符串去输出
- __sleep(),当被serialize时候,如果有__sleep,会优先触发
案例
经典案例
1 | include('flag.php'); |
方法: $user是反序列化后的类,所以$_cookie[‘user’]要传入序列化的类,根据代码,我们可以把ctfshowuser这个类中的$isvip的布尔值改为true,然后将这个class,放入本地,然后echo urlencode(serialize(new ctfshowuser())),把这个输出值放入cookie。
思路: 因为$user是unserialize后的类,所以所以要把利用的类给序列化即可
序列化不会改变方法中的详细代码,只是可以改变调用什么之类的
反序列化逃逸
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29<?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__);
详细原理: https://www.bilibili.com/video/BV1D64y1m78f?p=9&spm_id_from=pageDriver
目标:使public $token=’admin’
方法:使用字符串逃逸,因为题目使用了str_replace来替换
过程:因为传入fuck会被替换为loveU,那么序列化的时候,s:4:”fuck”被替换后会变成s:4:”loveU”,这样就会不等相差了一个字符。然后在$f传入 fuck(若干个)”;s:3:”msg”;s:1:”b”;s:2:”to”;s:1:”c”;s:5:”token”;s:5:”admin”;},这个字符串是被替换后 的序列化字符串截取的,把user换成admin,然后一直添加fuck,然后一直查看序列化后的s:数量:”和这里面的字符串数量一不一样”,如果一样了,可以使用var_dump(unserialize(序列化后的字符串)) 看看是不是ok了
正常的序列化(未被过滤): O:7:”message”:4:{s:4:”from”;s:4:”fuck”;s:3:”msg”;s:1:”b”;s:2:”to”;s:1:”c”;s:5:”token”;s:4:”user”;}
过滤后的: O:7:”message”:4:{s:4:”from”;s:4:”loveU”;s:3:”msg”;s:1:”b”;s:2:”to”;s:1:”c”;s:5:”token”;s:5:”admin”;}
例如字符串: 字符串逃逸,system被替换为了ctfshow,差异一个字符,后面用”;}闭合
O:8:”backdoor”:2:{s:1:”m”;s:168:”ctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshow”;s:1:”a”;s:6:”whoami”;}”;s:1:”a”;s:6:”whoami”;}
指针改变变量
1 | include('flag.php'); |
- 解法: 本地试,复制关键代码,然后把$this->password = $p 修改为$this->password = &$this->token,然后$msg=serialize(new ctfshowAdmin(‘1’,’2’)) echo $msg; 输出即可
- 思路,代码要求类中的login方法,token和password全等于,把password的值赋值为,&$this->password ,意思就是把password的地址给token,这样值就会相等