服务器安装typecho之后没有删除install.php,导致可以在前台利用反序列化漏洞执行任意代码。
PHP序列化的函数是serialize(),它可以把php对象转化为字符串,加上base64编码,方便传输。
例如对象:
可以通过serialize()转化为字符串(x00开头说明是私有变量):
再base64编码:
反序列化函数是unserialize(),作用和serialize()相反。所以当用户可以控制unserialize()的参数时,就可能有漏洞产生。
php的魔术方法有很多,容易利用的有__wakeup()、__destruct()、__toString()。
这里还要使用一个方法__get()
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
PHP 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
读取不可访问属性的值时,__get() 会被调用。
PHP手册有详细的解释:魔术方法
PHP 5.6.31 Typecho 1.0 (14.10.10)
1.查找反序列化函数和用户可控点
/install.php 230行:
程序获取用户cookie,base64解码后反序列化成$config,然后$config[‘adapter’]被当成字符串了,我们来看看有什么类可以利用__toString()。
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $… ]] )
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
这里把$this->_filter中的元素当做回调函数,把刚才的$this->_params[‘screenName’]作为回调函数的参数。所以用户可以通过修改cookie为构造好的序列化对象,执行任意命令。
4.构造payload
只要按上面的思路构造即可,但有个地方需要注意,install.php 54行用了ob_start(),所有输出会先留在缓冲区中,call_user_func()运行完我们的命令后__toString()会抛出致命错误,导致清空缓冲区,返回状态码500(不知道有没有理解错)。
原文:
在install.php的开始,调用了ob_start()
在php.net上关于ob_start的解释是这样的。
ob_start因为我们上面对象注入的代码触发了原本的exception,导致ob_end_clean()执行,原本的输出会在缓冲区被清理。
我们必须想一个办法强制退出,使得代码不会执行到exception,这样原本的缓冲区数据就会被输出出来。
这里有两个办法。 1、因为call_user_func函数处是一个循环,我们可以通过设置数组来控制第二次执行的函数,然后找一处exit跳出,缓冲区中的数据就会被输出出来。 2、第二个办法就是在命令执行之后,想办法造成一个报错,语句报错就会强制停止,这样缓冲区中的数据仍然会被输出出来。
解决了这个问题,整个利用ROP链就成立了
如果只是想种一句话木马的话就不用会显啦,判断状态码500就证明成功。
综合了两篇文章的payload:
typecho.py
删除install.php
第一次写博客,图很多,字不多,代码审计是跟着文章按图索骥,也不知道自己理解的有没有错,以后还要努力学习一个。