Typecho 开发者:关于最近的 Typecho 安全漏洞

作者:joyqi

已经跟报告漏洞的相关方讨论过这个事情,大家的初心还都是技术层面的交流,之前在流程上由于沟通不畅造成了一些误解,现在误解已经消除,大家也不要恶意去揣测他人的意图,让我们把焦点放在技术本身。我们也一致表达了在安全层面加强合作的意向。

作为开发者其实不太想写这种针对性的回复文章。一般针对安全问题,如果有人报告我都是在第一时间提交修复。但这两天很多关心 Typecho 的朋友通过各种渠道向我询问最新爆出来的两个严重的安全漏洞,我看了网络上的分析文章,前面技术性的分析我在后面做回复。但是文中某些技术细节之外的无端猜测,是促使我写这篇文章的理由。

原文链接:
https://mp.weixin.qq.com/s/IE9g6OqfzAZVjtag-M_W6Q

修复方法

这一点是你务必知道的,为了你的站点安全,你可以先暂时删除掉根目录下掉 install.php 文件,或者你也可以直接升级到最新的 Beta 版

注意在升级的时候也要覆盖 install.php 文件。

关于 XMLRPC 漏洞

第一个漏洞是 XMLRPC 里的 Pingback 协议,很多人可能不知道 Pingback 协议是干嘛的。我在这里简单解释下,这个协议诞生在 Web 2.0 概念诞生之初,由于在互联网世界各个博客站点之间是独立的存在,而它们之间又经常存在互相引用的情况。作为一个原创博主,我是无法知道我这篇文章被哪些站点引用过的,因此 Pingback 协议就是为了解决这个问题存在的。

当你在写的文章发表后,如果文中引用了某个链接,系统会自动向那个链接发一个 PING,告诉对方我引用了这篇文章,地址是: xxx。对方收到这个 PING 以后会根据你给的原文地址回去检验一下是否存在这个引用,这就是一次 BACK。检验完以后,会把这次引用记录下来,大家经常在 Typecho 或者 WordPress 之类博客评论列表里看到的引用记录,就是这么来的。

而造成这个漏洞的关键就在于这个 BACK 过程。系统回访的时候会调用网络接口(在 Typecho 里还对 CURL 做了基于 Socks 的 fallback),而当时的代码没有对回访的 URL 做验证,结果本意是让这个网络接口去访问 http 或者 https 的地址,但由于没做验证,导致它可以被用来访问任何协议,这就为攻击者创造了便利条件。

这个漏洞已经在上个月得到了修复,我们对访问的协议和地址都做了双重验证,只限于访问 http 或者 https 协议,并且限制了它的访问范围,不能用于访问内网或者本机的地址,而且还针对今后可能普及的 IPV6 做了预防。

关于 install.php 漏洞

这个漏洞本质其实比上一个漏洞更简单,原文的分析有点绕,其实只看最开始的部分就可以了,一句话解释就是 install.php 本身对安装状态验证存在漏洞,导致了可以绕过它向系统写入一些非法代码。

下面解释三个大家最关心的问题

首先安装的过程会分很多步骤,而且每一步都要验证后再跳转到下一步。具体到填写配置信息的这一步,我们在你填写完以后会验证你的配置信息是否合法,比如数据库连接是否正确等等,这些信息是通过 HTTP POST 传递到 PHP 的。当这一步做完验证后,并写入相应信息后(也有可能不写入,比如 GAE 之类的容器环境,根本没有可写的环境),再 Location 跳转到下一步,也就是去写入初始数据。

熟悉一点编程的朋友,请回答我一个问题。如何在两个 GET 请求间传递配置数据?用 querystring?太丑陋了吧。用 session?对不起,很多主机都没有配好 session。用临时文件?不好意思,就像上面说的,很多运行环境根本没有可写的权限。用数据库?不行,数据表是在下一步的时候才建的。

所以我最后就用了 Cookie 来传递数据,这样你的安装过程会显得比较干净。

我个人认为,原文作者认为这可能是个后门之类的原因就在于,我对 Cookie 做了个 base64 的编码,这是黑客最爱的做事风格,把不可告人的代码隐藏在无意义的 base64 编码下面。

但是,我用 base64 只是为了避免可能存在的 Cookie 编码问题,这样一种很正常的思路,给它预设不好的前提后,往往会得出令人不快的结论。

原文作者指出他不明白为什么这里要有这段代码
https://github.com/typecho/typecho/blob/242fc1a4cb3d6076505f851fdcd9c1bbf3e431a5/install.php#L230

                <?php else : ?>
<?php
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
Typecho_Cookie::delete('__typecho_config');
$db = new Typecho_Db($config['adapter'], $config['prefix']);
$db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set($db);
?>

如果你不联系上下文,当然不知道为什么要有这段代码。

首先解释为什么要在这一步连接数据库,因为这段代码看起来就是这个作用。初一看这一步好像跟数据库没什么关系,该写入的数据上一步已经写完了,这一步就是告诉用户安装成功

但是我们的目光再往下移
https://github.com/typecho/typecho/blob/242fc1a4cb3d6076505f851fdcd9c1bbf3e431a5/install.php#L258

                    <?php
if (isset($_REQUEST['user']) && isset($_REQUEST['password'])) {
$loginUrl = _u() . '/index.php/action/login?name=' . urlencode(_r('user')) . '&password='
. urlencode(_r('password')) . '&referer=' . _u() . '/admin/index.php';
$loginUrl = Typecho_Widget::widget('Widget_Security')->getTokenUrl($loginUrl);
} else {
$loginUrl = _u() . '/admin/index.php';
}
?>

这是一段生成快速登录链接的代码,方便你在安装完成不需要输入密码直接进入后台,请关注 Typecho_Widget::widget('Widget_Security')->getTokenUrl($loginUrl) 这一段代码。

在 Typecho 0.9 里面,我们加入了防跨站模块,而它的核心就是在每次提交的时候加入一个 Token 供系统验证。而这个 Token 的生成是需要加盐 salt 的,而每个站点的 salt 都是在安装的时候写入到数据库中的随机字符串。

所以看到了么?生成一个合法的后台 URL 是需要数据库连接的。

然后再解释为什么要从 Cookie 里取数据,我们不是已经创建了 config.inc.php 文件了么?但是由于 config.inc.php 里定义的常量以及一些初始化动作,会与 install.php 头部的代码有所冲突。所以,我们无法在 install.php 去直接 require 它(这一点已经在新版里解决,我在这里只是解释当时那么做的理由)。因此,我们又要初始化数据库,就只能从 Cookie 里读取信息并解码了。

为什么不删除 install.php?

首先,删除 install.php 意味着需要给根目录赋予额外的写入权限,这本身就会造成安全问题。其次,很多容器环境,比如 GAE SAE BAE 之类的,代码是基于版本控制管理的,根本不可能让你去更改文件。那安装完成后提醒用户修改删除可以吗?首先,这么做没有技术问题,但我认为安装完以后再去删除,是一件很麻烦的事情,在保证没有漏洞的前提下,应该做到安装后即可使用,当然用户也可以自行去删除 install.php。

写在最后

首先,我欢迎任何以技术为目的质疑和交流,Typecho 这个项目也是依靠大家的力量一点点完善起来的。其次,我希望仅仅将这种质疑局限在技术本身,毕竟取一个吸引眼球的标题,最后再加一些揣测,又有多少人会真的去细思呢?大多数人都是会被带着节奏,而接受预设立场,这一点不利于问题的解决。

所以你们看,即使我认真写了这么多,仔细去看去分析的人估计也不会有多少。

Typecho 的社区个性一直是低调踏实,我们会认真对待每一项改进的提议。我想向那些因为此次软件漏洞可能造成损失的用户,表达歉意。

新的正式版,会在本周放出。