PHP反序列化入门

发布于 2020-11-22  128 次阅读


人生中第一次自己做出来CTF题,写这篇博客算是纪念一下吧。

解题过程与思路:

打开网站后只看到如图所示的网页,并没有什么异样.

尝试在根目录下输入

url/www.zip

没想到还真有网页备份(正常情况下大概应该使用工具对网站目录进行扫描).

下载wwwzip之后查看网站源代码,

在主页的php文件中看到这样的代码,说明可能有反序列化漏洞可以使用.

进入class.php文件后看到如上代码,可以知道是需要构造反序列化GET请求传入username和password两个变量得到flag.

因此我们构建如上的GET请求,得到flag.

反序列化漏洞利用入门

什么是序列化(serialize)和反序列化(serialize)?

在 PHP 中将对象、数组、变量等转化为字符串,这样便于将数据保存到数据库或者文件中,这个过程称之为序列化。当需要使用这些数据时,就需要用反序列化就是将字符串还原回原来的样子,也就是序列化的逆过程。PHP 提供了 serializeunserialize 函数来支持这 2 种操作,当 unserialize 函数的参数被用户控制时就会形成反序列化漏洞

序列化的例子:

a:3:{i:0;s:6:"张三";i:1;s:6:"李四";i:2;s:6:"王五";} 
将上面内容反序列化后输出结果为:
Array
(
    [0] => 张三
    [1] => 李四
    [2] => 王五
)

magic method

PHP 的面向对象中包含一些魔术方法,这些方法在某种情况下会被自动调用。要利用PHP反序列化和序列化漏洞就要用上这些函数。

magic 方法功能
__construct()类构造器
__destruct()类的析构器
__sleep()执行 serialize() 时,先会调用这个函数
__wakeup()执行 unserialize() 时,先会调用这个函数
__toString()类被当成字符串时的回应方法

序列化的语法:

PHP 序列化后的内容是简单的文本格式,但是对字母大小写和空白(空格、回车、换行等)敏感.序列化的格式如下:

O:4:"test":1:{s:4:"test";s:4:"MSKJ";}

PHP 对不同类型的数据用不同的字母进行标示,Yahoo 开发网站提供的 Using Serialized PHP with Yahoo! Web Services 一文中给出所有的字母标示及其含义:

  • a - array
  • b - boolean
  • d - double
  • i - integer
  • o - common object
  • r - reference
  • s - string
  • C - custom object
  • O - class
  • N - null
  • R - pointer reference
  • U - unicode string

其中a,b,d,i,O,N等都比较容易明白. r,R分别表示对象引用和指针引用,U表示Unicode编码的字符串.

NULL 的序列化

在 PHP 中,NULL 被序列化为:

N;

boolean 型数据的序列化

boolean 型数据被序列化为:

 b:<digit>;

其中 <digit> 为 0 或 1,当 boolean 型数据为 false 时,<digit> 为 0,否则为 1。

integer 型数据的序列化

integer 型数据被序列化为:

i:<number>;

其中 <number> 为一个整型数,范围为:-2147483648 到 2147483647。数字前可以有正负号,如果被序列化的数字超过这个范围,则会被序列化为浮点数类型而不是整型。如果序列化后的数字超过这个范围 (PHP 本身序列化时不会发生这个问题),则反序列化时,将不会返回期望的数值。

double 型数据的序列化

double 型数据被序列化为:

d:<number>;

其中 <number> 为一个浮点数,其范围与 PHP 中浮点数的范围一样。可以表示成整数形式、浮点数形式和科学技术法形式。如果序列化无穷大数,则 <number> 为 INF,如果序列化负无穷大,则 <number> 为 -INF。序列化后的数字范围超过 PHP 能表示的最大值,则反序列化时返回无穷大(INF),如果序列化后的数字范围超过 PHP 所能表示的最小精度,则反序列化时返回 0。当浮点数为非数时,被序列化为 NAN,NAN 反序列化时返回 0。但其它语言可以将 NAN 反序列化为相应语言所支持的 NaN 表示。

3.5.string 型数据的序列化

string 型数据被序列化为:

s:<length>:"<value>";

其中 <length> 是 <value> 的长度,<length> 是非负整数,数字前可以带有正号(+)。<value> 为字符串值,这里的每个字符都是单字节字符,其范围与 ASCII 码的 0 - 255 的字符相对应。每个字符都表示原字符含义,没有转义字符,<value> 两边的引号("")是必须的,但不计算在 <length> 当中。这里的 <value> 相当于一个字节流,而 <length> 是这个字节流的字节个数。

补充

在 PHP5 最新的 CVS 中(也就是将来要发布的 PHP6),上面对于 string 型数据的序列化方式已经被下面这种所取代,但是 PHP6 仍然支持上面那种序列化方式的反序列化。

新的序列化方式叫做 escaped binary string 方式,这是相对与上面那种 non-escaped binary string 方式来说的:

string 型数据(字符串)新的序列化格式为:

S:<length>:"<value>";

其中 <length> 是源字符串的长度,而非 <value> 的长度。<length> 是非负整数,数字前可以带有正号(+)。<value> 为经过转义之后的字符串。

它的转义编码很简单,对于 ASCII 码小于 128 的字符(但不包括 \),按照单个字节写入(与 s 标识的相同),对于 128~255 的字符和 \ 字符,则将其 ASCII 码值转化为 16 进制编码的字符串,以 \ 作为开头,后面两个字节分别是这个字符的 16 进制编码,顺序按照由高位到低位排列,也就是第 8-5 位所对应的16进制数字字符(abcdef 这几个字母是小写)作为第一个字节,第 4-1 位作为第二个字节。依次编码下来,得到的就是 <value> 的内容了。

数组的序列化

数组(array)通常被序列化为:

a:<n>:{<key 1><value 1><key 2><value 2>...<key n><value n>}

其中 <n> 表示数组元素的个数,<key 1>、<key 2>……<key n> 表示数组下标,<value 1>、<value 2>……<value n> 表示与下标相对应的数组元素的值。

下标的类型只能是整型或者字符串型(包括后面那种 Unicode 字符串型),序列化后的格式跟整型和字符串型数据序列化后的格式相同。

数组元素值可以是任意类型,其序列化后的格式与其所对应的类型序列化后的格式相同。

对象的序列化:

对象(object)通常被序列化为:

O:<length>:"<class name>":<n>:{<field name 1><field value 1><field name 2><field value 2>...<field name n><field value n>}

其中 <length> 表示对象的类名 <class name> 的字符串长度。<n> 表示对象中的字段1个数。这些字段包括在对象所在类及其祖先类中用 var、public、protected 和 private 声明的字段,但是不包括 static 和 const 声明的静态字段。也就是说只有实例(instance)字段。

<filed name 1>、<filed name 2>……<filed name n>表示每个字段的字段名,而 <filed value 1>、<filed value 2>……<filed value n> 则表示与字段名所对应的字段值。

字段名是字符串型,序列化后格式与字符串型数据序列化后的格式相同。

字段值可以是任意类型,其序列化后的格式与其所对应的类型序列化后的格式相同。

但字段名的序列化与它们声明的可见性是有关的,下面重点讨论一下关于字段名的序列化。


你好哇!欢迎来到雷公马碎碎念的地方:)