• MongoDB 服务公测上线

    mongodb-banner

    MongoDB 是基于新浪云分布式系统并结合多年来新浪新闻、财经等系统丰富成熟的经验精心打造的高可用高性能无间断支持海量访问的原生 MongoDB,2016 年 6 月 7 日到 6 月 21 日 23:59:59 公测期间完全免费使用。

  • CVE-2016-3714 - ImageMagick 漏洞分析和解决

    2016 年 5 月 3 号,一个被广泛使用的图片处理库 ImageMagick 被爆出存在一处远程命令执行漏洞(CVE-2016–3714),当其处理的上传图片带有攻击代码时,攻击代码中的远程命令将会被执行,进而可能控制服务器。

    这个漏洞被命名为 ImageTragick,甚至还有了一个属于这个漏洞自己的网站( https://imagetragick.com/ )。

    同样,新浪云上也是安装了这个库的,在 PHP 运行环境中,也是有 PHP-Imagick 这个扩展的,这就意味着新浪云的 PHP 环境也是有相同的远程命令执行漏洞的。

    因此新浪云在第一时间修复了该漏洞,并执行了更严格的策略,和官方提供的解决方案相比,更加严格的杜绝了类似现象的发生。下面就来看看这个漏洞的产生,以及新浪云是如何修复这个漏洞的。

    漏洞原因

    这个漏洞产生的原因,在 ImageTragick 的网站也是有说明,主要是由于一个 ImageMagick 有一个功能叫做 delegate(委托),作用是调用外部的 lib 来处理文件。而调用外部 lib 的过程是使用系统的 system 命令来执行的, 相关代码

    所有的委托都是在一个配置文件中指定的,默认的配置文件在/etc/ImageMagick/delegates.xml(不同的系统和版本位置有一定区别),其中:

    <delegatemap>
        ...
        <delegate decode="https" command="&quot;curl&quot; -s -k -o &quot;%o&quot; &quot;https:%M&quot;"/>
        ...
    </delegatemap>
    

    在文件的注释里可以看到它定义了很多占位符,比如 %i 是输入的文件名,%l 是图片 exif label 信息。而在后面 command 的位置,%i 和 %l 等占位符被拼接在命令行中。这个漏洞也因此而来,被拼接后的命令行传入了系统的 system 函数,因此只需使用反引号(`)或闭合双引号,就可以执行任意命令。

    看官方给的 POC:

    push graphic-context
    viewbox 0 0 640 480
    fill 'url(https://"|id; ")'
    pop graphic-context
    

    就会在调用 curl 的同时,调用了 id 命令。于是漏洞就产生了。

    漏洞修复

    ImageTragick 网站上给出了两种修复或者规避这个漏洞的方式:

    1. 处理图片前,先检查图片的 "magic bytes", 如果图片头不是你想要的格式,那么就不调用 ImageMagick 处理图片。
    2. 使用一个 policy 文件来禁止一些有问题的操作,这个文件默认位置在 /etc/ImageMagick/policy.xml(不同的系统和版本位置有一定区别),可以按如下配置:
     <policymap>
      <policy domain="coder" rights="none" pattern="EPHEMERAL" />
      <policy domain="coder" rights="none" pattern="URL" />
      <policy domain="coder" rights="none" pattern="HTTPS" />
      <policy domain="coder" rights="none" pattern="MVG" />
      <policy domain="coder" rights="none" pattern="MSL" />
      <policy domain="coder" rights="none" pattern="TEXT" />
      <policy domain="coder" rights="none" pattern="SHOW" />
      <policy domain="coder" rights="none" pattern="WIN" />
      <policy domain="coder" rights="none" pattern="PLT" />
    </policymap>
    

    当然,直接升级到最新版的 ImageMagick ,也是能解决这个问题的。

    新浪云的做法

    和上面给的做法不同的是,新浪云并没有升级 ImageMagick 版本,也没有配置 policy 文件,而是采取了一个稍微‘暴力’点的手段来解决这个漏洞。

    新浪云的 PHP 运行环境,是放在一个“沙箱”当中的,因此一般情况下,从 php 层面是无法突破这个沙箱的限制,去读取系统,或者是其他应用的文件的,但是也有例外,比如这次的漏洞,ImageMagick 成功的突破了新浪云沙箱的限制,不仅能够读取系统的文件,还可以运行外部命令,这对于新浪云的安全性来说,是无法容忍的。

    先说一下沙箱的原理,沙箱的原理,就是利用 LD_PRELOAD 这个环境变量,将很多的函数 hook 起来,替换为我们自己实现的一个版本,在这个版本里,可以进行一些权限的检查,判断是否通过,如果通过,则正常执行,否则就直接返回错误。从而保证整个系统的安全性。最简单的,我们可以把 open 这个函数 hook 起来,在里面判断每个应用是不是有权限去读写对应的文件,并根据判断结果放行或者拒绝。

    上面说道 ImageMagick 使用的是 system 函数来执行外部命令,那么很简单,只需要将 system 函数 hook 住,判断是不是 ImageMagick 调用的,如果是,则直接拒绝,从根本上解决执行外部命令的问题。

    但是,仅仅这样是不够的,为什么呢?来看一下 PHP 的源代码:

    #define DL_LOAD(libname)  dlopen(libname, RTLD_LAZY | RTLD_GLOBAL | RTLD_DEEPBIND)
    

    PHP 在加载一个扩展时,会添加上RTLD_DEEPBIND这个参数,man 中对这个参数的解释如下:

    RTLD_DEEPBIND (since glibc 2.3.4) Place the lookup scope of the symbols in this library ahead of the global scope.
    This means that a self-contained library will use its own symbols in preference to global symbols with the same name
    contained in libraries that have already been loaded. This flag is not specified in POSIX.1-2001.
    

    意味着如果使用了这个参数,则程序在寻找符号时更倾向于使用自身的而不是全局空间中的,简单来说,就是 LDPRELOAD 这种替换符号的形式对于使用 RTLDDEEPBIND 加载的动态库文件是无效的。 因为这样,所以实际上ImageMagick调用的 system 并不是新浪云沙箱中的 system,而是系统中的 system,那么如何解决?其实也很简单,把 dlopen 函数也 hook 住嘛,然后在这个函数里把 RTLD_DEEPBIND 参数去除掉,就可以了:

    if (strcmp(so_name, "imagick.so") == 0) {
        flag = flag & ~RTLD_DEEPBIND;
    }
    

    使用这种做法,即使再出现类似的漏洞,对于新浪云来说,也是安全的,因为有了沙箱的保护,只要不突破沙箱,就无法实现外部命令调用,或者任意读取文件等行为了。

  • Counter 服务下线通知

    由于产品的升级调整,我们将从即刻起下线新浪云的 Counter 服务。

    如果您的应用有使用 Counter 服务来做计数器的需求,可以使用我们的 Redis 服务来替代,具体请参考: Redis 使用案例(计数器 etc)

    现有 Counter 服务用户可以继续使用,下线不会对您的使用产生影响,但是我们还是强烈建议您迁移至我们的 Redis 服务。迁移方法如下:

    1. 参照 文档 的说明创建一个新的 Redis 实例。
    2. 下载 counter.class.php 这个类文件。
    3. 替换原应用代码里使用 SaeCounter 类的地方为 counter.class.php 里的 Counter 类,Counter 类的初始化参数为 Redis 实例的 URL,详细见类文件中的文档说明。
    4. 调用 SaeCounter.getall() 方法 dump 出原 Counter 服务的数据再灌入 Redis 。完成。
  • Redis 使用案例(计数器 etc)

    redis-logo

    新浪云目前正式支持了 Redis 服务,用户可以简单的初始化一个 Redis,直接在应用里使用。无疑 Redis 功能是十分强大的,下面就来举两个简单的例子来描述一下 Redis 的一些应用场景。

    作为计数器

    自从有了 Redis 服务,就可以不需要原来提供的 Counter 服务了,Redis 非常适合作为计数器使用,而且也非常方便,只需要很少的几行代码:

    <?php
    
    $redis = new Redis();
    $redis->connect('host', port);
    $redis->auth('password');
    
    if (isset($_REQUEST['id'])) {
        echo '当前页面访问人数' . $redis->hIncrBy('PV_SET', $_REQUEST['id'], 1);
    } else {
        $pvs = $redis->hGetAll('PV_SET');
        foreach($pvs as $id => $count) {
            echo "页面" . $id . "被访问" . $count . "次<br>";
        }
    }
    

    这是一个简单的统计页面,当请求参数中 id 不为空时,给对应 id 的页面访问加 1,如果 id 为空时,把所有页面的访问次数都打印出来。 所以,当不停访问某个 id 的文章时,就会出现 当前页面访问人数 1,2,3,4 不停的递增。 当不加 id 时,就会打印出所有页面的访问次数:

    页面 1 被访问 4 次
    页面 2 被访问 3 次
    页面 3 被访问 2 次
    

    咦,这个貌似和 Counter 的用法没啥区别哎。其实还是有区别的,至少不用再一个一个去新建 Counter 了。

    取最新 N 个数据

    有的时候,我们可能只需要显示最新的一部分数据,比如最新的 100 条评论,最新的 10 条新闻,如果每次都去读数据库,那效率就比较低了,可是这样的数据存 Memcache 呢,也不是特别好用。 这时候,Redis 的强大就体现出来啦,来来,还是看代码,来实现一个简单的获取最新评论的功能。

    <?php
    
    $redis = new Redis();
    $redis->connect('host', port);
    $redis->auth('password');
    
    if (isset($_REQUEST['comment'])) {
        // 有新评论,添加到列表中
        $redis->lPush('latest.comment', $_REQUEST['comment']);
        // 只保留最新的 10 条,其他都丢掉
        $redis->lTrim('latest.comment', 0, 9);
        // 存入数据库
        //...
    } else {
        // 取出最新的 10 条评论
        $comments = $redis->lRange('latest.comment', 0, 9);
        foreach($comments as $comment) {
            echo $comment . "<br>";
        }
    }
    

    最后的结果:

    three
    two
    one
    5
    4
    3
    2
    

    这里利用了 Redis 的一个 List 功能,将所有评论从左边一个一个放到一个列表里,并利用提供的 LTrim 功能,将右边多余的给删除掉。 在取的时候,只需要从左边取对应的条数,就剩下了所有最新的数据了。是不是很简单呢?相对于直接从数据库中取 LIMIT N,速度也是会快上很多。

    其他场景

    除了上面的两个例子,Redis 还可以用在更多的地方,比如作为消息队列,实现生产 / 消费者模型;作为数据持久化的缓冲,解决数据库的写入瓶颈问题等等。 在新浪云内部,也是有非常多的系统使用 Redis 作为缓存,消息队列等用途,依托于 Redis 的超高性能,越来越多的系统开始使用 Redis 来解决系统中遇到的性能瓶颈。

  • 使用 NewRelic 监控容器应用的性能

    NewRelic 是一个服务端代码级的应用性能监控(APM)平台。能够对应用进行实时性能监控(如数据库访问性能监控、 API 接口调用性能监控、运行环境监控、错误追踪,性能问题追踪、关键事务监控等),及时发现应用性能问题并定位性能瓶颈,提供性能问题诊断、追踪及优化依据。

    在新浪云的容器运行环境中使用 NewRelic 非常的简单,下面我们以一个 Python 应用为例,说明 NewRelic 的使用方法。

    首先,我们需要在 NewRelic 注册并创建一个新的 APM 项目。

    newrelic-create

    创建完成之后,选择 Python 语言。

    newrelic-python

    进入应用的代码目录,将 newrelic 加入 requirements.txt 文件中,让服务端在构建容器镜像的时候安装 newrelic 模块。

    在本地安装 newrelic 模块。

    $ pip install newrelic
    

    生成 NewRelic 的配置文件(中间一长串字符串是您创建的 APM 项目的 License Key)。

    $ newrelic-admin generate-config 4fd5a06************************7a2356e2b newrelic.ini
    

    登录新浪云,进入容器应用的 环境变量 管理面板,添加 NewRelic 服务相关的环境变量。

    environ

    修改 Procfileweb 的启动命令,使用 newrelic-admin 来启动进程。

    web: newrelic-admin run-program 进程实际启动的命令
    

    提交更改的代码并部署到新浪云,等几分钟,您就可以在 NewRelic 的面板里看到容器应用的性能监控图表了。

    下面是我们的 Python 示例应用 的监控图表。

    newrelic-dashboard

    NewRelic APM 提供 NodeJS、Java 等语言的支持,更多使用请参见其官方使用文档。

subscribe via RSS