函数
- serialize() 将一个对象转化字符串
- unserialize() 将字符串还原成一个对象 注: 反序列化其实为创建实例的过程, 系统会根据 序列化字符串中的 对象信息 来判断创造哪一个类的实例, 并会 根据序列化字符串内容 覆盖属性的值(能覆盖private和protect属性) 注: php中类的初始化中属性不能用可变化量赋值(不能用new
类名
赋值), 所以要通过创建实例再将实例赋值给对应属性来改变属性的值
魔术方法
__construct(形参,...)
: 当使用new创建 类变量 时触发
- 在创建对象时 new
类名
(实参
,...) 来传入参数
- 注: 当用反序列化来创建类时不会触发__construct
__destruct()
: 用户主动销毁对象(unset)或程序结束时由系统自动销毁时触发
__toString()
: 当使用echo print以及字符串函数等将 类 当作字符串输出时触发
- 若无该方法时满足触发条件会报错
- 不能有形参,否则会报错
- 必须有 字符串类型的返回值, 该返回值会成为 类被当作的字符串
__call(函数名, 参数)
: 调用类中 不存在的 或 无法访问的 成员函数时触发
- 若无该方法时满足触发条件会报错
- 必须有两个形参
- 触发后
函数名
会赋值为 被调用的函数名(字符串形式)
- 触发后
参数
会被赋值为 由 调用函数的实参 所组成的列表 (没有实参则为空数组)
__callStatic(函数名, 参数)
: 用静态方式中调用一个不存在的 或 无法访问的 成员函数时触发
__get(变量名)
: 调用类中 不存在的 或 无法访问的 成员变量时触发
- 若无该方法时满足触发条件会报错
- 必须有一个形参
- 触发后
变量名
会赋值为 被调用的变量名(字符串形式)
__set(变量名, 值)
: 给类中 不存在的 或 无法访问的 成员变量 赋值时触发,
- 若无该方法时满足触发条件会报错
- 必须有两个形参
- 若触发set则不会执行get
- 触发后,
变量名
会赋值为 被赋值的变量名(字符串形式)
- 触发后,
变量名
会赋值为 赋值的值
__sleep()
: 若 类 被序列化, 则在执行到序列化代码行时 先触发再序列化
__serialize()
: 与sleep()类似, 当存在serialize()时, __sleep()会被忽略
__wakeup()
: 若 类 被反序列化, 则在执行完反序列化后 触发
__unserialize(数据)
: 与wakeup()类似, 当存在unserialize()时, __wakeup()会被忽略
- 参数:当serialize方法存在时,参数为serialize的返回数组;当__serialize方法不存在时,参数为实例对象的所有属性(键)与属性值(值)组合而成的数组
__isset(变量名)
: 对不存在的 或 无法访问的 成员变量调用isset()或empty()时触发
- 必须有一个形参
- 触发后,
变量名
会赋值为 被使用isset()或empty()的变量名(字符串形式)
- 在isset() empty()函数内调用不存在的 或 无法访问的 成员变量不会执行__get
- 返回值会进行条件判断变为true或false, 并作为isset()或empty()的返回值
__unset(变量名)
: 对不存在的 或 无法访问的 成员变量使用unset()销毁时触发
- 若无该方法时满足触发条件会报错
- 必须有一个形参
- 触发后,
变量名
会赋值为 被使用unset()的变量名(字符串形式)
__invoke(形参,...)
: 以调用函数的方式调用一个对象时触发(即 对象名
(实参
,...)触发)
__clone()
: 使用clone关键词进行浅复制时, 新建的对象中的__clone()
会被调用
- 一般用该魔术方法将 clone 的 浅复制 写为 深复制
__set_state(数组)
: 当用var_export()
导出对象时触发
- 参数是一个数组,其中包含按
['property' => value, ...]
格式排列的类属性
var_export()
会输出 该方法的返回值(对象) 的类名 和 属性名 与 值
__debugInfo()
: 当用var_dump()
对象时触发
var_dump()
会输出__debugInfo()
的返回值(数组)
序列化后字符串
对象类型
:对象长度
:对象名字
:对象元素数
:{s
:变量名长度
:变量名
;变量值类型
:根据类型有差异
} 差异 s
:字符串长度
:字符串
; O
:对象长度
:对象名字
:对象元素数
:{...}; b
:1或0
; i
:数字
; 若没有值 则为 N
- 注:
变量名
必定为字符串, 所以为类型为s 成员函数只出现在类声明时,而在创建 类 时只不会包含在 创建除的类的内存里, 调用成员函数都是程序去访问声明的内容, 所以类变量中不包含成员函数而序列化字符串中也不会出现成员函数 private在序列化字符串中为 %00类名%00成员名 protect为 %00%00成员名 %00在普通字符串中可能无法显示,要转化为编码,%00与类名会改变变量字符串长度(%00算1个
字符串逃逸
- 原理: php反序列化时根据长度判断内容的, 一个字符串元素 在序列化字符串中包含 该元素值的长度, 但当该数字小于实际长度时, 由于反序列化只解析固定长度字符, 则使得反序列化字符串从最后一个字符开始不被解析, 从而达到绕过的目的
- 应用场景: 出现在用户可操控被反序列化的实例的值时(非反序列化)且出现了序列化字符串的字符替换, 以操控对应的在之后所进行的反序列化所创造的类的值
让可操控的属性为
替换字符串;s:属性名长度:属性名;s:属性值长度:属性值;}如fuck*27;s:5:token;s:5:admin;}
被替换导致的实际长度大于字符串长度的这一数值等于上面的属性字符串的长度 如后面为 27
所以若 将 fuck 替换为 ulove 则需要替换27次
Session反序列化
处理器(选项session.serialize_handler中设置,默认为php)
php 键名+竖线+经过serialize()的值(序列化的只有值,不包含名字)
//$_SESSION['benben'] = dazhuang; => benben | s:8:dazhuang;
php_serialize(php>=5.5.4) 经过serialize()处理的数组
//$_SESSION['benben'] = dazhuang; $_SESSION['b'] = 666;
//a:2:{s:6:benben;s:8:dazhuang;s:1:b;s:3:666;}
php_binary 键名长度的ASCII值+键名+经过serialize的值(序列化的只有值,不包含名字)
//$_SESSION['benben'] = dazhuang; $_SESSION['b'] = 666;
//06benbens:8:dazhuang;01bs:3:666;
- 漏洞产生: 当网站 序列化存储session 与 反序列化并读取session 的方式不同时产生
-- php_serialize/php_binary设置 php读取
设置$_SESSION['a'] = |序列化字符串; 即可
//写入时变成 a:1:{s:1:a;s:7:|序列化字符串},读取时只会将|后的内容反序列化
//写入时变成 01as:7:|序列化字符串,读取时只会将|后的内容反序列化
绕过
正则/O:\d+/
绕过
- 在长度前加上+
- 包含在数组对象(或其它对象)中 绕过(仅当
/^O:\d+/
可用) 利用serialize(array($类变量))
产生的序列化字符串
__wakeup绕过 (CVE-2016-7124)
- 当存在unserialize()时, wakeup()会被忽略 (
__serialize
与__sleep
同)
- 当序列化字符串中表示对象元素个数的值大于真实的元素个数时会跳过__wakeup的执行
如O:4:test:1:{s:1:a;s:3:abc;}会执行__wakeup
但O:4:test:2:{s:1:a;s:3:abc;}不会执行__wakeup
__destruct不执行绕过
- 原因: 当php产生非语法错误报错时, 程序立即停止而__destruct也不会执行
- 方法: 使得序列化变得畸形(元素数,变量名长度,字符串长度等不正确, 或格式不正确等)
直接将最后的序列化后的}去掉
O:7:ctfshow:2:{s:8:username;s:6:xxxxxx;s:8:password;s:6:xxxxxx;
或者修改属性个数有点类似于绕过__wakeup魔法函数
O:7:ctfshow:3:{s:8:username;s:6:xxxxxx;s:8:password;s:6:xxxxxx;}
反正就是让他变得畸形就可以
O:7:ctfshow:3:{abc}
%00过滤绕过
- 使用16进制绕过字符的过滤方法,将%00转换为
\00
- 可以直接传入public字段,并不会报错(php>7.1)
引用绕过
- 可将类的一个属性引用传值给另一个属性, 这样在序列化字符串中就会包含两个属性地址相同的信息, 这样对其中一个变量操作时, 另一个变量的值也会随之改变
16进制绕过字符的过滤
- 原理: 序列字符串中表示字符类型的s大写时,内容先进行16进制解析(此时也可以有非16进制字符)
- 格式:
\70\61\73\73\74\68\72\75\28\22\63\61\74\20\66\22\29
没有可利用的类绕过
--Error类(php7/8) Exception(php5/7/8) 需开启报错 --两个类使用相同
后果:xss,md5/sha1相等绕过(因为两个类是不同的,但可以让其__toString返回相同值)
利用:利用该类的__toString(), 会返回Error的message属性 (利用:如echo md5() sha1())
构造:利用__construct构造即可
$a = new Error(<script>alert('xss')</script>);
$b = serialize($a);
echo urlencode($b);
--SoapClient类
后果:ssrf
利用:触发__call魔术方法,会调用SoapClient类的构造方法
构造方法:public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location
和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri是SOAP服务的目标命名空间,
uri可随意设置,而可新增user_agent选项,使用其来进行CRLF漏洞利用
构造:利用CRLF漏洞
$a = new SoapClient(null,array('location'=>'http://xxx', 'uri'=>'127.0.0.1', 'user_agent' => Firefox\r\nContent-Type:application/x-www-form-urlencoded\r\nX-Forwarded-For:127.0.0.1,127.0.0.1\r\nContent-Length:13\r\n\r\ntoken=ctfshow));
$b = serialize($a);
echo $b;
--DirectoryIterator类(php>5) 和 FilesystemIterator类(php>=5.3) 和 Globlterator类(php>=5.3)
后果:绕过open_basedir,并进行任意目录扫描
利用:配合glob:// 在rce中使用, Globlterator自己就可以模糊搜索,无需glob://
$a=new DirectoryIterator(glob:///*);foreach($a as $f){echo($f->__toString().'<br>');}
$a=new FilesystemIterator(glob:///*);foreach($a as $f){echo($f->__toString().'<br>');}
$a=new GlobIterator(/*);foreach($a as $f){echo($f->__toString().'<br>');}
--SplFileObject类(php>=5.1.2) 待定
后果:绕过open_basedir,并进行任意文件读取
利用:直接创建对象,传入要读取的文件路径,在将返回值打印
$a=new SplFileObject(/flag);foreach($a as $f){echo $f;}
--SimpleXMLElement类
利用:当可控制目标调用的类的时候,利用该类的构造方法
构造方法:__construct(string $data, int $options = 0, bool $dataIsURL = false, string $namespaceOrPrefix = , bool $isPrefix = false )
第一个参数为 格式正确的 XML 字符串或 XML 文档的路径或 URL
第二个参数填 2 即可
第三个参数 为ture第一个参数即可使用xml文档路径或url
后果:xxe
new SimpleXMLElement(xml的远程路径,2,true)
--ReflectionClass类
后果:若关键信息在类里(看不见内容的类), 可以使用反射类/打印实例来获取类的信息
利用:可用echo new ReflectionClass(类名); 来获取类的详细信息(包括 方法 和 成员变量 命名空间等)
(用echo print,不能用var_dump var_export print_r)
而用print_r(new 类名)来获取 成员变量 信息
(用var_dump var_export print_r,不能用echo print)
后果/利用2:绕过 new 可操控(可操控) 的情况, 可 new ReflectionClass(rce方法),该类会返回参数值
(可用echo print var_dump var_export print_r)