概述
序列化
我的理解序列化就是将字符串转换成为一串对象值。他将字符串通过近似字典的模式将字符串的值给显示出来。
反序列化
反序列化与之相反,他将序列化的对象或数组字符串,还原回去。
PHP
序列化
示例一下
上代码
<?php
class ctf{
public $flag='flag{12345}';
public $name='mryh';
public $age='20';
}
$ctf=new Ctf();
$ctf->flag='flag{abcde}';
$ctf->name='BOb';
$ctf->age='18';
echo serialize($ctf);
?>
结果
O | 存储对象,如果serialize()里面的参数为数组的话那么这里显示的就会是A |
3 | 表示类名的长度为三个字节 |
“ctf” | 类名 |
3 | 这个3表示的是类里面有三个属性值 |
s | 这里表示这个值是字符串格式的,如果他是数字那么这里的值就是i |
4 | 表示这个属性值长度为4个字节 |
“flag” | 属性名 |
s | 这个值是字符串格式 |
11 | 属性值长度 |
“flag{abcde}” | 属性名 |
访问控制修饰符
public 申明的全局变量
protected 受保护的变量
private 私有变量
第一个访问控制修饰符没啥讲的,就跟上面示例的一样
第二个访问控制修饰符他序列化出来的对象名是%00*%00属性名
第三个访问控制修饰符他序列化出来的对象名是%00类名%00属性名
特别注意:%00是一个截断的空字符,他也要占一个字节。
还是示例一下吧。
<?php
class ctf{
public $flag='flag{12345}';
protected $name='mryh';
private $age='20';
}
$ctf=new Ctf();
$ctf->flag='flag{abcde}';
// $ctf->name='BOb';
// $ctf->age='18';
echo serialize($ctf);
?>
只是将定义的变量修改,将下面的赋值参数给注释掉。
这里的结果
魔术方法
序列化serialize()方法有一个魔术方法__sleep(),他的作用是哪些属性可以被序列化。如果__sleep()方法中没有值则默认全部都序列化。
<?php
class ctf{
public $flag='flag{12345}';
protected $name='mryh';
private $age='20';
public function __sleep(){
return array('flag','name');
}
}
$ctf=new Ctf();
$ctf->flag='flag{abcde}';
// $ctf->name='BOb';
// $ctf->age='18';
echo serialize($ctf);
?>
因为我创建了一个function魔术方法,所以序列化的只有flag和name属性值。
__sleep()函数的漏洞
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。
示例
反正我输入的参数是一个不存在的值
反序列化
<?php
class ctf{
public $flag='flag{12345}';
protected $name='mryh';
private $age='20';
}
$ctf=new Ctf();
$ctf->flag='flag{abcde}';
// $ctf->name='BOb';
// $ctf->age='18';
$a = serialize($ctf);
echo "serialize:".$a."<br>unserialize:";
var_dump(unserialize($a));
?>
反序列化也没啥需要说的,就是将序列化的结果给反序列化回来。
反序列化的POC方法
反序列化POP链
unserialize()
反序列化函数用于将单一的已序列化的变量转换回 PHP 的值。
当反序列化参数可控时,可能会产生PHP反序列化漏洞。
在反序列化中,我们所能控制的数据就是对象中的各个属性值,所以在PHP的反序列化中有一种漏洞利用方法叫做 “面向属性编程”,面向对象编程从一定程度上来说,就是完成类与类之间的调用。POP链起于一些小的“组件”,这些小“组件”可以调用其他的“组件”
在PHP中,“组件”就是那些魔术方法(如:wakeup()或destruct)
面向属性编程(Property-Oriented Programing)常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链做一些工作了。
魔术方法
__construct() 当对象创建(new)时会自动调用。但在 unserialize() 时是不会自动调用的。
__destruct() 当一个对象销毁(反序列化)时被调用
__toString() 当一个对象被当作一个字符串使用时被调用
__sleep() 在对象在被序列化之前立即运行
__wakeup() 将在序列化之后立即被调用
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__invoke() 当脚本尝试将对象调用为函数时触发
而在反序列化时,如果反序列化对象中存在魔法函数,使用unserialize()函数同时也会触发。这样,一旦我们能够控制unserialize()入口,那么就可能引发对象注入漏洞。
比较重要的几个魔术方法
__wakeup()
将在序列化之后立即被调用
当序列化字符串表示对象属性个数的数字值大于真实类中属性的个数时就会跳过__wakeup的执行。这个大家应该都知道很常见的姿势了。为了直观一点找了些考察反序列化的ctf。
__destruct()
析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
__toString()
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
php session 反序列化
session 的存储机制
session.save_path=“” --设置session的存储路径
session.save_handler=“” --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认使用php
在使用xampp组件安装中,上述的配置项的设置如下:
session.save_path=“D:\xampp\tmp” 表明所有的session文件都是存储在xampp/tmp下
session.save_handler=files 表明session是以文件的方式来进行存储的
session.auto_start=0 表明默认不启动session
session.serialize_handler=php 表明session的默认序列化引擎使用的是php序列话引擎
PHP处理器的三种序列化方式:
| 处理器 | 对应的存储格式 |
| ————————— |:——————————-|
| php_binary | 键名的长度对应的ASCII字符+键名+经过serialize() 函数反序列处理的值 |
| php | 键名+竖线+经过serialize()函数反序列处理的值 |
|php_serialize |serialize()函数反序列处理数组方式|
示例php对象session注入
在线靶场:http://web.jarvisoj.com:32784/index.php
源代码
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}
function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>
先分析一下吧。
上诉代码是让一个页面开启一个session值,然后创建一个类,创建了construct方法和destruct方法。然后判断get参数是否存在,如果存在则实例化上面的类,如果不存在则将index.php中的内容原样输出。
所以当我不给phpinfo参数的时候则原样输出。
如果我赋值给phpinfo的时候,他将会直接实例化OowoO类并调用里面的两个函数方法。输出phpinfo内容
然后找到该页面的地址。
我们先创建一个php文件,写入以下内容。
<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value='|O:5:"OowoO":1:{s:4:"mdzz";s:26:"print_r(scandir(__dir__));";}' />
<input type="file" name="file" />
<input type="submit" />
</form>
浏览器去访问这个页面,然后上传文件,就会惊喜的发现在靶场下面有一个反序列化之后的值。
然后我们在将获取到的
但是感觉这个实例好像一点都不与序列化和反序列化相关,甚至于session都不相关。
PHP Session中的序列化危害
PHP中的Session的实现是没有的问题的,危害主要是由于程序员的Session使用不当而引起的。
如果设置的session序列化选择器与默认的不同的话就可能会产生漏洞(会导致数据无法正确的反序列化 )通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。
反序列化pop链构造
有时遇见魔法方法中没有利用代码,即不存在命令执行文件操作函数,可以通过调用其他类方法和魔法函数来达到目的
反序列化想构造的出的方法
命令执行:exec()、passthru()、popen()、system()
文件操作:file_put_contents()、file_get_contents()、unlink()
phar伪协议触发php反序列化
大多数PHP文件操作允许使用各种URL协议去访问文件路径:如data://
,zlib://
或php://
。这些操作通常用于远程文件,攻击者可以在其中控制文件包含完整的文件路径。
例如常见的
include($_GET['file'])
include('php://filter/convert.base64-encode/resource=index.php');
include('data://text/plain;base64,cGhwaW5mbygpCg==');
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过 phar:// 伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
漏洞利用条件
phar文件要能够上传到服务器端(如GET、POST),并且要有file_exists(),fopen(),file_get_contents(),file(),include()等文件操作的函数
要有可用的魔术方法作为跳板
文件操作函数的参数可控,且:
、/
、phar
等特殊字符没有被过滤。
漏洞复现环境:
upload_file.php
,后端检测文件上传,检测文件类型是否为gif,文件后缀名是否为gif
upload_file.html
,前端文件上传表单
file_un.php
,存在file_exists(),并且存在__destruct()
反序列化字符串逃逸
实例(替换导致字符串加长)
源代码
<?php
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='aaaa';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass;
?>
这段代码就是将类给序列化,多加了一个替换,将bb替换成为ccc。
所以我们先输出吧,先将name改成bb输出看看。
比较两个序列化的字符串发现,虽然他将bb替换成为了ccc,但是他的长度没有发生改变,所以我们你可以构造代码进行逃逸,逃过后面的pass检查。
Poc:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:4:"hack";}
因为一个bb就逃逸了一个字节,所以我们构造的有25个字节,我们需要构造25个bb,也就是输入50个b,以至于将上面内容进行全部逃逸。
他大概的原理是,前一个序列化取值取足够的值之后将后面的给挤出去,也就是将他自己定义的给挤出去了。
所以这样我们就将他自己定义的pass给绕过了。变成自己需要的pass了。
ctf题反序列化逃逸(字符串加长)
在线靶场:piapiapia
打来之后主页面就只是一个登录页面。
因为不知道用户名和密码,所以我们可以利用御剑等后台目录扫描工具进行扫描。扫描出来他有一个www.zip这个文件。
然后下载下来。里面有六个后端源码,我们一个个的打开进行代码审计。
index.php
<?php
require_once('class.php');
if($_SESSION['username']) {
header('Location: profile.php');
exit;
}
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];
if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');
if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
if($user->login($username, $password)) {
$_SESSION['username'] = $username;
header('Location: profile.php');
exit;
}
else {
die('Invalid user name or password');
}
}
else {
?>
这个段代码就是判断登录,如果登录成功就跳转到profile.php这个页面,反之则报错。
在主页面没有什么实质性进展,所以我们需要登录进去,,但是由于不知道用户名和密码,所以我们需要去注册,因为主页面没有注册按钮,但是我们在下载下来的www.zip中发现了一个register.php,所以我们可以去访问这个页面看看能否登录。
能注册成功,我们在登录进去看看主页面。
看看register.php里面有什么。
<?php
require_once('class.php');
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];
if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');
if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
if(!$user->is_exists($username)) {
$user->register($username, $password);
echo 'Register OK!<a href="index.php">Please Login</a>';
}
else {
die('User name Already Exists');
}
}
else {
?>
这个后端代码也很简单,只是注册的普通页面,就判断注册的用户名长度,长度足够就成功注册,不够就报错。
还是看主页面,发现有一个上传文件,我试了一下,写了一个一句话木马进去上传,结果虽然上传成功了,但是是404。
查看源代码
update.php
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');
if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>
还是一段段地解释吧
这段代码表示phone的参数必须是以数字开头的11位数。
下面这段代码表示输入的email必须是以***@.***这种格式
下面这段代码表示输入的nickname这个参数第一个不能以数字,并且输入的参数必须小于10个字节
这里判断上传的文件内容长度必须大于5,并且小于1000000
回归正题,打开那个网页发现404是因为上传的时候使用了md5进行加密了的。
所以文件上传不能用,只能继续代码审计,看能否找到其他漏洞。
class.php
<?php
require('config.php');
class user extends mysql{
private $table = 'users';
public function is_exists($username) {
$username = parent::filter($username);
$where = "username = '$username'";
return parent::select($this->table, $where);
}
public function register($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);
$key_list = Array('username', 'password');
$value_list = Array($username, md5($password));
return parent::insert($this->table, $key_list, $value_list);
}
public function login($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);
$where = "username = '$username'";
$object = parent::select($this->table, $where);
if ($object && $object->password === md5($password)) {
return true;
} else {
return false;
}
}
public function show_profile($username) {
$username = parent::filter($username);
$where = "username = '$username'";
$object = parent::select($this->table, $where);
return $object->profile;
}
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);
$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
public function __tostring() {
return __class__;
}
}
class mysql {
private $link = null;
public function connect($config) {
$this->link = mysql_connect(
$config['hostname'],
$config['username'],
$config['password']
);
mysql_select_db($config['database']);
mysql_query("SET sql_mode='strict_all_tables'");
return $this->link;
}
public function select($table, $where, $ret = '*') {
$sql = "SELECT $ret FROM $table WHERE $where";
$result = mysql_query($sql, $this->link);
return mysql_fetch_object($result);
}
public function insert($table, $key_list, $value_list) {
$key = implode(',', $key_list);
$value = '\'' . implode('\',\'', $value_list) . '\'';
$sql = "INSERT INTO $table ($key) VALUES ($value)";
return mysql_query($sql);
}
public function update($table, $key, $value, $where) {
$sql = "UPDATE $table SET $key = '$value' WHERE $where";
return mysql_query($sql);
}
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
public function __tostring() {
return __class__;
}
}
session_start();
$user = new user();
$user->connect($config);
这个php个人感觉很重要,里面有很多实例化的类,和创建的一些类,还是一部分一部分的看。
filter方法
这个方法里面是过滤方法,他指定了一些关键字的过滤,将一些关键字给过滤成了hacker,还将反斜线给替换成为了下划线。
这几个方法是查找更新等操作的方法。
看到filter过滤方法,在看到上面update那个页面的序列化,这题应该是反序列化字符串逃逸。
通过源码知道,phone不能绕过,只能输入11个数字字符,email也不能,他中间需要用@,所以我们只能使用nickname这个参数进行绕过。
还是使用burp进行抓包,查看里面的参数看看。
发现他的链接是链接到profile.php的,因为获取flag文件要在config.php里面,所以我们要构造序列进行绕过,使他链接在config.php
先自己构造pofile.php的序列化吧。
<?php
$profile['phone'] = "12345678910";
$profile['email'] = "123@qq.com";
$profile['nickname'] = "123abc";
$profile['photo'] = 'upload/' . md5("hahaha");
echo serialize($profile);
?>
修改nickname的值进行构造序列化。
因为我们需要重新构造序列化,所以我们先要计算构造的长度。
Poc:";s:5:“photo”;s:10:“config.php”;}
可以看到这里有33个字符,因为每一个where就逃逸了一个字符,所以我们需要构造33个where让上面的都给替换上去。
nikaname = wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";s:5:"photo";s:10:"config.php";}
然后将序列化的值赋值给nikaname这个参数。
给了之后发现好像不对,又去审计源码,看到这个参数长度小于10个字节
但是我们可以利用数组绕过。
这里显示成功,我们将包进行修改发送。
但是它报错文件名为空,去看看哪里有问题。
发现传递的数组字符串多了很多玩意儿
正常的数组的序列化是将值用大括号括起来的,所以我们在构造的参数前还要将大括号给闭合掉。
payload:$profile['nickname'][] = 'wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}'
成功闭合。继续抓包,修改参数再去试试吧。
他没报错,所以我们成功了,离成功就差一步之遥,
查看源代码,将base64加密的内容进行解密,flag值就出来了。
flag值:flag{76cd2b78-68a5-4379-88e3-bd0127e34280}
实例(替换导致字符串变短)
<?php
function filter($str){
return str_replace('bb', '', $str);
}
class A{
public $name='aaaa';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass."<br>";
echo $res."<br>";
var_dump($c);
?>
这里同上一个一样,只不过替换字符串变成看空字符。
先看他替换后的样子吧。
因为一个bb替换成了空所以说这里少了两个字节,因为他要取值所以会从后面拿值,所以我们可以在pass哪里做文章,将pass的值当成name序列化的值.所以我们先查看pass序列化之后长度是多少。
因为我们需要构造25个字节,也就是13个bb,因为我们只需要25个,所以多了一个字节,我们又要将这个多一个字节传递给pass,然后我们在构造我们需要的序列化值。
Poc:$pas='123456a";s:4:"pass";s:4:"hack'
成功修改pass值。
安洵杯 2019]easy_serialize_php(字符串变短)
[网鼎杯 2020 青龙组]AreUSerialz
打开题库他就是将源代码输出到html页面上。
这题的思路就是将op要变成2,filename要是flag.php才会显示flag。
所以试了好久,需要将protected变成public。
详情请戳https://blog.csdn.net/kuller_Yan/article/details/108566123
LKWA靶场实例
靶场:https://github.com/weev3/LKWA
docker-compose安装:docker pull kminthein/lkwa:latest
docker启动:docker run -d -p 3000:80 kminthein/lkwa:latest
PHP对象注入
主页
然后经过一个个的尝试,发现text哪里存在xss漏洞。
这里不仅要修改文本,还要修改字符长度。
但是后面就抓耳挠腮,不会了。还是看源码吧。
查看源码后还是蒙的,就看到包含了几个文件,而且还实例化了两个对象。先去查看Foo这个类吧,利用管道命令查看包含的文件中有没有Foo这个实例化对象。
发现obj这个PHP文件中有Foo这个类。查看这个php文件。
发现这里是写入文件,所以我们可以实例化这个类,让他输出序列化。利用这个序列化的值传递给主页面的object。
我们将序列化的foo对象赋值下来,传递给object参数。但是文件虽然创建了,但是没有写入数据。
然后经过胡大佬的一系列操作竟然能写进去了。他将foo的属性数加了一个1,然后使用加号(+)代替空格就成功写进去了。
然后去访问这个页面
我们可以试着写一句话木马进去试试。
使用菜刀进行连接
连接成功。这道题差不多圆满完成。
PHP对象注入(Cookie)
还是先打开主页面看一下有没有什么漏洞,发现就是普通的post表单传输,没看到什么奇怪的东西。
我们利用burp进行抓包,查看包里面有啥。通过抓包只发现了头部Cookie中新加了一个username,而且是用序列化之后的值,所以这里的漏洞点应该在username这里。
所以我们将包发送到Repeat模块进行测试。
然后利用上一个的方法只能先去查看源代码,查看Foo类的方法。
我们将这个类复制下来,给他实例化一个对象,并将他序列化。
然后将这个序列化的值传递给username这个序列化的值。
发现他还是没有变化,没有显示phpinfo页面,我们使用url编码在试试。
发现还是不行。
如此反复之后我们发现phpinfo少了一个分号。把分号加上。
发现可以。
我们将包发送出去看主页面。
我们知道该怎么搞了我们试着发送一句话木马,使用菜刀进行连接
ps:试了一下,好像不得行,截图就算了。
PHP对象注入(Reference)
还是老方法,先输入一个参数,使用burp进行抓包。
抓到的包长这样子,我们发现这里不仅cookie中添加了一个username还在传输的字典中做了一个序列化。将input传输过来的值进行url解码。
发现是这个玩意,现在又不知道怎么搞了,去看一下源码吧。
发现就这点有用,必须要让guess反序列化的结果和secretCode这个值相等。但是发现这个值是固定的。
还是构建stdClass的类,将他实例化出来。这里不能创建stdClass的类,不知道为啥,就先创建其他的类然后序列化的时候修改回来。记住因为这里创建的是secretCode的指针,所以我们需要实例化的时候要传递他的指针。使他相等。
将类名修改,使用url编码格式进行编码,将url编码的值传递给input,发送出去在这里。成功。
这里我们又要补充一点其他的知识了。后面的R参数是个啥。顺便把之前的知识也回顾一下吧。
a - array | 这里表示序列化的是一个数组 |
b - boolean | 这里表示标量类型,也就是后面的内容或者属性名这些是布尔值 |
d - double | 这里也是标量类型,后面的内容或者属性名是双精度浮点型 |
i - integer | 也是标量类型,后面的内容或者属性名是整形。 |
s - string | 也是四种标量类型之一,后面的内容是字符串类型 |
O - common object | 属于常见的复合类型。 |
r - Refernce | 分别表示了对象引用和指针引用 |
R - pointer reference | 通r一样,这两个也是比较有用的 |
普通的php对象注入
来自皮卡丘靶场
下载链接:https://github.com/zhuifengshaonianhanlu/pikachu
搭建起来之后直接打开这个靶场。
靶场主页面
随便输入参数,使用burp进行抓包,查看参数这些。
抓包查看到这些参数根本没有改变,没有被序列化的痕迹。
所以只能查看源代码了。截取了一些部分关键的。
关键代码看出,他创建了一个S类,并在类中有一个construct方法。
但是下面那个判断是判断unser这个值需要等于反序列化之后输出的值。查找了这里面的所有文件并没有unser这个类或者什么的,才发现这个页面就是unser,所以我们需要传递的值是序列化的类值。
所以需要创建代码将S这个类实例化出来。
我们将序列化的值传递给o,也就是post数据。
发现没有用,修改里面的参数试试。发现好像出现了一个xss漏洞,但是这个没有显示。