2013年7月25日星期四

高并发应对:淘宝CDN缓存服务器部署探秘

“好,时间到,开抢!”坐在电脑前早已等待多时的宋兰(化名)一看时间已到2011年11月11日零时,便迫不及待地投身于 淘宝网  一年一度的大型网购促销活动——“ 淘宝  双11”购物狂欢节。
宋兰不知道,就在11日零点过后的这一分钟内,全国有342万人和她一起涌入淘宝网。当然,她更不知道,此时此刻,在淘宝公司杭州总部的一间办公室里,淘宝技术部核心系统负责人兼高级研究员章文嵩和淘宝的一群技术高手们,正在黑板上纷纷写下注,看谁能最准确地猜中“淘宝双11”CDN流量峰值和全天的交易总额。
不过,看似轻松的氛围下,章文嵩和他的同事们内心多少有些忐忑:本次网购盛会对淘宝的各个服务系统而言都是一次巨大的考验,其中最大的考验则是对章文嵩所负责的,刚刚上线的,基于 英特尔  凌动(ATOM) 处理器  定制的低功耗 服务器  而新搭建的CDN系统。因为,虽然已有测试证明该CDN系统可以应付超大流量,但那毕竟是测试结果,真实情况会怎样,谁都没底。
淘宝技术部核心系统负责人兼高级研究员 章文嵩
24小时之后,淘宝网和 淘宝商城  高达52亿的交易额让淘宝笑了,而章文嵩的心也终于落定。
始自 节能  之需
对 互联网  相关技术比较熟悉的人大概都了解CDN。CDN全称Content Delivery Network,即 内容分发  网络。其目的是通过在现有的Internet中增加一层新的网络架构,将网站的内容发布到最接近用户的网络“边缘”,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。
在淘宝,章文嵩表示,为了保障近四亿淘宝用户的购物体验,该公司已在全网部署了103个用于加速的CDN节点,单节点服务能力大于10Gbps,CDN Web Cache服务器数量超过4000台。
当然,支撑整个淘宝日常运营的远不止这几千台CDN web cache服务器。这些服务器每天的耗电量非常惊人,在淘宝网运营成本中占据了相当比重并逐年上升。因此,节约服务器用电量,已经成为不得不考虑的问题。而最直接的方法,就是在满足性能要求的前提下,采用比传统服务器省电的低功耗服务器。
低功耗服务器之所以比传统服务器的省电,主要在于采用了低功耗CPU。低功耗CPU在带来低功耗的同时,也损失了处理速度。因此,消耗CPU资源少的应用是低功耗服务器首要应用场景。而从淘宝整个服务器体系来看,满足这一要求的恰好是CDN Web Cache服务器,最终,该公司决定在CDN节点尝试使用定制低功耗服务器来替换传统服务器,并于2009年底正式启动绿色低功耗服务器定制项目。
“不只是攒个机器”
“定制低功耗服务器,表面上看是攒个机器,不过实际上并非如此简单。”章文嵩告诉记者。
市场上传统的低功耗处理器,其节省功耗的原理,是在原有高性能处理器的基础上,通过控制处理器在闲时的主频和耗电,同时简化乱序处理的逻辑 模块  ,来减少不必要的消耗。但该方式受限于原有的高性能架构,整体功耗降低效果并不明显,特别是在IO密集型的业务上。而淘宝网用于静态内容加速的CDN Web Cache服务器,主要功能正是对静态的网页和图片进行读写操作,属于IO密集型业务。
“因此,针对IO密集型业务,我们需要寻找区别于以往传统的、更低功耗的处理器和服务器 方案  。”章文嵩说。
项目启动之初,淘宝选择某知名 芯片  平台供应商作为合作伙伴,并于2010年6月制作出原型机,但遗憾的是,该原型机未能通过线上测试。因此,2010年8月,淘宝选择在整机解决方案方面比较有经验的美国超微公司和英特尔。最终,在三方的努力下,基于凌动处理器D525的低功耗服务器定制成功,并于2011年6月开始在淘宝实施规模化部署。
淘宝产品经理 何燕锋
在杭州华数 机房  ,负责该服务器定制的产品经理何燕锋带领记者实地参观了一处由该低功耗服务器所搭建的CDN节点机群。其中,一个2U空间里能放8台凌动低功耗服务器(8台服务器共享一个机箱),单台服务器功耗仅25W,也就是说,这8台低功耗服务器的总体功耗也才200瓦。这200瓦是什么概念呢?何燕锋指着该CDN节点机群中一台服务器,告诉我们说,那就是市面上的普通服务器,主要负责调度功能,其功耗为150W,占1U空间。两相对比,低功耗服务器的低功耗高密度的优势十分明显。
值得一提的是,章文嵩和他的同事发现,淘宝CDN缓存对象具有的特征是:18KB以下的对象数量占总数量的80%,而 存储  量只有不到40%;同时,80%被访问对象所占用的存储空间不到20%。这意味着“热数据”(访问频次高的内容)占的空间其实很小,而“冷数据”(访问频次低的内容)所需存储量很大。因此,淘宝特意为该低功耗服务器引入 分层存储  机制,所以单台低功耗服务器的硬盘其实是由一块80GB的 SSD  盘和两块500GB的 SATA  盘组成。这样,“热数据”存放在SSD盘上,“冷数据”就存放在SATA盘上,从而兼顾速度、容量与成本。而分层存储调度软件则由淘宝独立开发。
2011年2月,该凌动低功耗服务器投入量产。随即,淘宝人又紧锣密鼓地开始了系统优化工作。章文嵩告诉记者,在没有优化之前,单台低功耗服务器的QPS(每秒查询率)数大约在900左右,离他们 既定  的目标(1000QPS)还差一点。然而,当他们将硬盘访问模式改为ACHI/ RAID  之后,QPS立刻跃升至1300;再通过优化IO、网络、SMP affinity等工作,QPS数到达1700,大大超过原来的预期。
“我们正在重写一个轻量级Cache软件,希望能将OPS数提升到1900。” 章文嵩说。
据悉,目前淘宝一共部署了十多个低功耗CDN节点机群,共部署了约800台凌动低功耗服务器。大部分凌动节点都服务到了上联链路的限制值——10Gbps,此时的单台低功耗服务器的服务QPS数在1200以上,而CPU负载不超过70%,整体的I/O也不过50%左右,且整体缓存服务的响应时间都在20毫秒以下。
“本次淘宝‘双11’购物狂欢节,我们CDN承受了全网近90%以上的流量,这一天的CDN带宽峰值到达800多Gbps,可以说真正通过了考验。”章文嵩说。之后的淘宝“双12”带来了更大的流量“洪峰”,淘宝CDN亦安然承受。
此外,何燕锋亦透露,他们还拿到了英特尔SandyBridge(15W)低功耗处理器(英特尔在微服务器上的主打处理器之一)样片,并在CDN缓存应用上进行了性能测试。
“SandyBrige(15W)低功耗处理器采用双核架构,主频只有1.2GHz,但L3的缓存有3MB。如果单看主频的话,很难想象这样的处理器能有多么卓越的性能,测试结果却出乎意外,单台服务器的性能能够到4500QPS, 几乎是我们现在这款低功耗服务器的3倍,而功耗则相差不到2W。”何燕锋说。
因此,只要该款 处理器价格  合适,淘宝将在下一代低功耗服务器中采用SandyBrige(15W)处理器。
心系环保、践行开源
“我们定制服务器跟传统的定制不一样。传统的定制是一种买断形式,例如,我向你(厂家)定制了服务器,你不能卖给第三方。而我们淘宝是一种开源的心态,我们觉得尽管这款低功耗服务器是针对淘宝的CDN需求而定制,但低功耗服务器环保节能,同时CDN系统是整个互联网服务的基础,所以淘宝将它开源出去并鼓励厂家将这款产品卖给别人,一方面促进整个互联网“绿色”服务器产业的发展,另外一方面也彰显淘宝的开发策略。”何燕峰告诉记者。
淘宝部署在杭州华数机房的CDN节点
需要指出的是,淘宝低功耗服务器定制项目在业界是相当超前的,其价值不言而喻。另一方面,虽然目前国内外有不少研究机构、 企业  正在研发低功耗服务器,但象淘宝这样将低功耗服务器项目(设计规格和应用数据)无私开源的,用章文嵩的话来说:“全世界也是第一家 ”。
并且,淘宝不只是简单地将其低功耗服务器定制项目开源,而是以此为出发点,联合英特尔、超微等厂商,共同发起了“开源绿色计算”项目(http://www.greencompute.org/)。章文嵩强调,该项目的目标是推动互联网整体硬件基础设施(包括服务器、网络设备、IDC机房、机架和电源等)的节能环保。
目前在开源绿色计算网站上还只有凌动低功耗服务器这一款定制化的“绿色”产品,章文嵩殷切地希望更多的同行能加入进来,共同推动“绿色计算”产业的发展。

金银花

金银花,为中药材植物的统称。植物金银花又名忍冬,为忍冬科多年生半常绿缠绕木质藤本植物。“金银花”一名出自《本草纲目》,由于忍冬花初开为白色,后转为黄色,因此得名金银花。药材金银花为忍冬科忍冬属植物忍冬及同属植物干燥花蕾或带初开的花。金银花自古被誉为清热解毒的良药。它性甘寒气芳香,甘寒清热而不伤胃,芳香透达又可祛邪。金银花既能宣散风热,还善清解血毒,用于各种热性病,如身热、发疹、发斑、热毒疮痈、咽喉肿痛等证,均效果显著。
金银花性寒,味甘,入、心、经,具有清热解毒、抗炎、补虚疗风的功效,主治胀满下疾、温病发热,热毒痈疡和肿瘤等症。其对于头昏头晕、口干作渴、多汗烦闷、肠炎菌痢麻疹肺炎乙脑流脑、急性乳腺炎、败血症阑尾炎、皮肤感染、痈疽疔疮、丹毒、腮腺炎、化脓性扁桃体炎等病症均有一定疗效。[1]
同时金银花茶有独特的减肥功能,还能抑制与杀灭咽喉部的病原菌,对老人和儿童有抗感染功效。所以,经常服用金银花浸泡或煎剂有利于风火目赤、咽喉肿痛、肥胖症、肝热症和肝热型高血压的治疗与康复。[1]

  此外,用连翘,板蓝根煎金银花汤可以治疗腮腺炎;金银花茶可以祛暑明目;连翘金银花凉汤可治疗外感发热咳嗽。同时将金银花、菊花、桔梗和甘草加水煮沸10分钟,侯凉,当饮料饮用,可治疗咽喉炎和扁桃体炎。[1]
参考资料

用 Google Authenticator 加强 VPS 及 WordPress 甚至桌面电脑的安全性

传统地用 SSH 登录 VPS 的时候,靠的只是一串字符密码,如果密码被泄露、被猜解、被暴力枚举成功,那么 VPS 就完全暴露在坏人手中了。安全意识高一些的用户会使用公私钥代替字符来登录,但是这样的缺点是如果在陌生的电脑上想要临时登录一下,由于没有私钥,也就没办法了。传统地登录 WordPress 的时候,更是只有一个密码,如果被泄露、猜解、暴力枚举,辛辛苦苦经营的博客就完蛋了。本文介绍如何通过 Google Authenticator 构建“物理屏障”,最大限度地阻断来自网络的密码攻击。本文假定你已经了解 Google Authenticator  的工作原理。

一、前言

虽然从小就被教导说操作系统一定要设置强劲的密码,但实际上,小时候用的大多是“远程桌面连接”默认被禁用的盗版 Windows,且在家里上网时候总是在路由器后面,没有独立公网 IP,因此就算电脑设置为空密码,也没有什么大问题。
后来,上了大学了,网络环境也有所改变:在宿舍上网的时候,电脑能分配到独立公网 IP,并且能直接从校外网络连入(甚至可以跑 Apache 玩),而我的 Xubuntu  又装了 OpenSSH Server,所以这时候密码就非常重要了,如果密码不够强劲的话,别人可以在互联网的任何一个地方通过 SSH 完全控制我的电脑。这太可怕了。
虽然不是所有人的电脑都有公网 IP 可以方便地从外网连入,但是显然 VPS 是有公网 IP 的。我以前居然没有意识到 VPS 是多么地脆弱,但是某天突然灵光一现:如果 VPS 的 root 密码被泄露、猜解或是暴力枚举,那么任何人都可以通过 ssh root@wzyboy.im来完全控制我的网站!
这么迟才想到这一点的确比较奇怪,但是好在,目前还没有“亡羊”,赶紧先“补牢”吧。个人又非常喜欢 Google Authenticator  这种二步验证工具(以前写过文章)于是便结合 Google Authenticator 折腾出了这篇简陋但是还算安全的教程。本教程中环境默认为:
  • 服务器为 512MiB RAM 的 LAMP VPS,在 XeHost  买的(半广告推荐:此家 VPS 物美价廉,想买的点我 ),装着 Ubuntu 11.04 amd64。
  • 客户机为装着 Xubuntu 11.10 amd64 的笔记本电脑。
  • 验证器为装有 Android 版 Google Authenticator  的 HTC Desire Z (Vision)。

二、用 Google Authenticator 加强 SSH 登录安全性

安装相关 PMA 模块

首先需要在服务器上安装 libpam-google-authenticator 这个包。Ubuntu 11.10 及以上的官方源里自带了这个包,直接使用 sudo apt-get install libpam-google-authenticator 命令便可自动解决依赖关系并安装。但是如果是 11.10 以上或者是其他发行版,就要自己的编译安装了。输入以下几条命令就行了:
sudo apt-get install apt-get install libpam0g-dev libqrencode3 #这是它依赖的两个包,各发行版里大多带了。
git clone https://code.google.com/p/google-authenticator/ #下载源代码
cd google-authenticator/libpam/
make install #编译并安装
如果用着 Ubuntu 11.10 以下又不想自己编译的话(比如 VPS 是 11.04 的我),其实也有偷懒的办法的,就是直接拿官方源里编译好的 11.10 的二进制包来充数。这回是不需要 libpam0g-dev 这个包了,直接用下面的命令就好了:
sudo apt-get install apt-get install libqrencode3 #这个包依然是要的
wget http://us.archive.ubuntu.com/ubuntu/pool/universe/g/google-authenticator/libpam-google-authenticator_20110413.68230188bdc7-1ubuntu1_amd64.deb #下载二进制包,如果是 32 位的操作系统的话请把 amd64 换成 i386
sudo dpkg -i libpam-google-authenticator_20110413.68230188bdc7-1ubuntu1_amd64.deb #安装之

配置 Google Authenticator

Google Authenticator 的服务器端已经安装好了,那么客户端呢?Android 用户请点这里 安装,iOS 用户请点这里 安装,其他智能手机用户也有相应的开源解决方案,请自行搜索下载。非智能手机用户暂时无解。:-( 不过,正在阅读本文的你一定早就用过 Google Authenticator 了吧?
Google Authenticator 其实是一套开源的解决方案,所以不仅在 Google 的网站上能用,在其他地方也能用的。然而,在 Google 的网站上,会直接给你一个 QR 码让你扫的,而自己配置的 Google Authenticator 则要自己生成了。
首先需要切换到对应的用户,如果 VPS 上只有一个用户的话,自然是可以省略这一步的,但是多用户的 VPS 需要先切换到对应的用户,再运行 google-authenticator 命令,结果类似这样:
这个 QR 码自然是给 Google Authenticator 应用程序来扫描的,也可以访问上面的那个链接,用 Google Chart API 生成的 QR 码来扫描。还可以照着 QR 码下面的文字密钥手工输入。当 Google Authenticator 识别了这个账号之后,验证器就配置好了。在文字密钥下面还提供了几个应急码,为手机丢了等情况下所用的,可以妥善保管。
这时 Google Authenticator 虽然运行了,但是相关设置还没有保存,程序会问你 Do you want me to update your "~/.google_authenticator" file (y/n) (是否将配置写入家目录的配置文件),当然是回答 y 了。又会问
Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n)
大意是说是否禁止一个口令多用,自然也是答 y。下一个问题是
By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n)
大意是问是否打开时间容错以防止客户端与服务器时间相差太大导致认证失败。这个可以根据实际情况来。我的 Android 设备时间很准(与网络同步的),所以答 n,如果一些 Android 平板电脑不怎么连网的,可以答 y 以防止时间错误导致认证失败。再一个问题是
If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n)
选择是否打开尝试次数限制(防止暴力攻击),自然答 y
问题答完了,家目录中多出一个 .google_authenticator 文件(默认权限为 400),这时客户端与服务端已经配套起来了,以后不用再运行 google-authenticator 命令了,否则会重新生成一组密码。

配置 SSH 验证

此时虽然 Google Authenticator 已经配置好了,但是并没有任何程序会去调用它。所以需要设置 SSH 登录的时候去通过它验证。
打开 /etc/pam.d/sshd 文件,添加
auth required pam_google_authenticator.so
这一行,保存。再打开 /etc/ssh/sshd_config 文件,找到
ChallengeResponseAuthentication no
把它改成
ChallengeResponseAuthentication yes
并保存。最后,输入
sudo service ssh restart
来重启 SSH 服务以应用新的配置。
这时候再用 SSH 登录的话就会这样了:
wzyboy@vermilion:~$ ssh root@natatio
Password:[输入密码]
Verification code:[输入验证码]
Welcome to Ubuntu 11.04 (GNU/Linux 2.6.38-8-generic x86_64)
于是就这样成功了。
当然,如果经常要登录 SSH 的话,每次这样输入未免太麻烦了,好在,这个额外的认证步骤与以前的公私钥认证是可以同时使用的。所以在自己的桌面电脑上可以做一下公私钥认证:
ssh-keygen   #生成密钥对,一路回车即可,已经生成过的不用生成了
ssh-copy-id root@example.org #把公钥添加到 VPS 上
这样达到的效果是,以后在自己的电脑上 SSH 到 VPS 的时候,是不需要输入任何密码的,可以直接连接,而在陌生的电脑上需要管理 VPS 时,需要输入账户密码及 Google Authenticator 的验证码。而想要从网络上攻击你的 VPS 的坏人,就算猜出、枚举出 VPS 的密码由于没有手机上 Google Authenticator 的验证码,就没办法了……

三、用 Google Authenticator 增强 WordPress 安全性

SSH 登录已经有 Google Authenticator 保护了,但是服务器运行的程序还没有,比如 WordPress。相对于 MySQL 漏洞攻击等高难度操作,我觉得 WordPress 被攻破的可能性更大:毕竟也只有一个短短的密码保护着。好在由于 WordPress 是“大路货”,用的人很多,插件自然也不少,有人便开发出了 WordPress 用的 Google Authenticator 的插件。启用方法如下:
  1. 在 WordPress 后台搜索并安装 Google Authenticator 这个插件,启用之。
  2. 在 WordPress 后台个人 Profile 页面 Google Authenticator Settings 选项下,选中 Active,填好 Description(只是在手机上显示的名字而已),再点击 Show/Hide QR Code
  3. 在 Google Authenticator 应用程序中扫描这个 QR 码以添加账号,或者手工输入 Secret 也行。
配置成功以后,再登录 WordPress 后台的时候就会是这样的:
不输入正确的 Google Authenticator code 是不能登录的。当然在自己的电脑上是可以勾选 Remember Me 以减少麻烦的。

四、用 Google Authenticator 增强桌面电脑的安全性?

本文第二节讲了如何把 Authenticator 用在 VPS 上以增强 SSH 登录时的安全性。自然,Authenticator 也是能用在装有 GNU/Linux 的桌面电脑上的。安装模块的方法和在 VPS 上是一样的,手机上配置也是一样,但是调用的时候是不同的。
进入 /etc/pam.d/ 目录,可以看到一些文件:
wzyboy@vermilion:/etc/pam.d$ ls
atd             common-password                lightdm-autologin  ppp
chfn            common-session                 login              samba
chpasswd        common-session-noninteractive  newusers           sshd
chsh            cron                           other              su
common-account  cups                           passwd             sudo
common-auth     lightdm                        polkit-1           xscreensaver
这些文件从文件名就能看出它们是干嘛的:控制一些重要操作的认证。在 VPS 中,我们在 sshd 中添加了 auth required pam_google_authenticator.so 这一行,于是在 SSH 登录的时候就会调用 Authenticator 来认证。在别的文件中加入 auth required pam_google_authenticator.so 这一行的话,就会在相应的操作中调用 Authenticator 来认证了。下面是几个重要的:
  • lightdm。这个会使通过图形界面登录的时候要求输入验证码,效果如下:
    输入密码之后还要再输入验证码:
    需要注意的是,lightdm 只是 Ubuntu 11.10 和 Xubuntu 11.10 及以上版本默认的显示管理器,其他的发行版可能是 gdm、kdm 等,请自行修改。
  • xscreensaver。这个会使屏保解锁的时候也要输入密码和验证码,效果如下:
  • login。这个会使在字符界面下(如 tty)登录的时候也会要求输入验证码。
  • passwd。这个会使设置用户密码的时候要求输入验证码。
  • sudo。这个会使普通用户试图提权执行系统操作的时候要求输入验证码。很适合给多人共享电脑用。
当然,如果你的电脑像我一样装了 OpenSSH Server 的话,也可以在 sshd 中加入 auth required pam_google_authenticator.so 这一行,使坏人无法从公网上登录你的电脑。

五、尾声

相对前几篇博客,这篇博客写得有一些仓促。并且,教程的内容并不怎么复杂,Linux 的高级用户肯定都会的,所以本文的目标读者只能是刚接触 Linux VPS 的用户了,希望能帮到他们。
转载请注明本文出处:http://wzyboy.im/post/765.html 谢谢合作。

Android软件安全开发实践(下)


我们讨论了数据存储、网络通信、密码和认证策略等安全问题和解决方案,本期将继续从组件间通信、数据验证和保全保护等方面来实践Android软件安全开发之路。
组件间通信
组件间通信的安全问题是Android所独有的,也是目前软件中最常出现的一种问题。
我们先回顾一下组件间通信机制。Android有四类组件:activity、service、broadcast receiver和content provider。在同一个软件之中或不同软件之间,前三种组件使用Intent相互调用,使用ContentResolver对象访问content provider,共同实现软件的功能。使用Intent,可以显式或隐式地调用:
  • 显式(explicit):调用者知道要调用谁,通过组件名指定具体的被调用者;
  • 隐式(implicit):调用者不知道要调用谁,只知道执行的动作,由系统选择组件处理这个请求。
如下面的代码所示:

无论是显式还是隐式,如果要跨应用调用,还需要被调用的组件是对外暴露的。默认情况下,service、broadcast receiver和content provider是暴露的,申明了Intent-filter的actvity也是暴露的。
抽象地说,组件A要调用组件B,以期待B完成某个功能;它可以发送一些数据给组件B,也可以获得B执行后的返回结果。在这个模型中,问题出现在A和B之间不一定互相可信。
如果B是暴露的,任何软件都可以调用它,包括攻击者编写的软件。攻击者可能但并非总能成功:
  • 直接调用暴露的B,以获得其执行结果;
  • 构造特定的数据,并用于调用暴露的B,从而试图影响B的执行;
  • 调用暴露的B,并获取它执行完返回的结果。
如果A用的是隐式调用,任何软件都可以实现它的action从而响应调用。攻击者可能(但并非总能成功):
  • 构造伪造的组件C,响应A的Intent,以读取A要发给B的数据;
  • 构造伪造的组件C,响应A的Intent,弹出虚假的用户界面以展开进一步攻击(例如钓鱼);
  • 构造伪造的组件C,响应A的Intent,返回伪造的执行结果。
这样说可能比较抽象。下面我们对这两种情况分别讨论。
组件暴露的问题
看一个例子。在一个第三方深度定制的ROM中,预装了名为Cit.apk的软件,用于手机的硬件测试。它的AndroidManifest.xml局部如下:

可以看到,它申明一个名为.CitBroadcastReceiver的receiver,响应名为android.provider.Telephony.SECRET_CODE的action,并且指定了URI格式。
再来看这个receiver的代码片段(下面的代码是我反编译得到的,不一定与软件源码完全一致):

可以看到,当调用这个receiver,并且提供的URI中host字段为284时,会以root权限调用本地的bugreport工具,并将结果输出至m_logFileName指定的文件中。
默认情况下receiver是暴露的,因此这个receiver可以被其他软件调用,代码如下:

当这四行代码执行时,就会触发CitBroadcast-Receiver的那段代码。从上下文看,输出文件m_logFileName位于SD卡,任何软件都可以随意读写。因此,攻击者可以获得bugreport的输出结果,其中包含大量系统数据和用户数据。
请注意,在这个例子中,攻击者的软件不需要任何特殊权限,尤其是不需要root权限。这种由于组件暴露获得额外权限的攻击,被称之为permission re-delegation(权限重委派)。
怎么避免由于组件暴露产生的安全问题?有的组件必须暴露,例如入口activity,或者确实对外提供服务或跨软件协作;但也有的组件没必要暴露。接下来我们分别讨论。
不需要暴露的组件
再次回顾,默认情况下,service、broadcast receiver和content provider是暴露的,申明了Intent-filter的actvity也是暴露的。如果它们只被同一个软件中的代码调用,应该设置为不暴露。很容 易做到—在AndroidManifest.xml中为这个组件加上属性android:exported=”false”即可。
需要暴露的组件
如果组件需要对外暴露,应该通过自定义权限限制对它的调用。
首先,在实现了被调用组件的软件的Android-Manifest.xml中自定义一个权限:

接下来,为被调用组件添加这个权限限制,即在AndroidManifest.xml中为这个组件添加android:permission属性:

另一种方法是在组件的实现代码中使用Context.checkCallingPermission()检查调用者是否拥有这个权限。
最后,要调用这个暴露的组件,调用者所在的软件应该申明使用这个权限,即在AndroidManifest.xml中添加相应的use-permission申明。
进一步地,还可以将这种组件暴露的需求分为两种情况。
  • 如果这个组件只打算给自己开发的其他软件使用,而不希望暴露给第三方软件,在定义权限时,protectionLevel字段应该选择signature。 这种设置要求权限使用者(即调用者)与权限定义者(即被调用者)必须由相同的证书进行签名,因此第三方无法使用该权限,也就无法调用该组件。
  • 如果这个组件要暴露给第三方,则protection-Level应使用normal或dangerous。此时,任何软件都可以使用该权限,只在安装时会 通知用户。考虑到用户一般会忽略权限提示,此时自定义权限实际失去了保护效果,我们依然要仔细审查该组件的代码,避免出现能力泄露或权限重委派等问题。
此外,对content provider,可以更细粒度地为读取数据和写入数据设置不同的权限,对应的manifest标签分别为android:readPermission和android:writePermission。
隐式调用的问题
隐式调用的主要问题是被劫持,或Intent携带的数据被读取。
为了劫持调用,攻击者可以实现一个恶意的组件,申明相同的Intent-filter。在多个组件都可以响应同一个Intent的情况下,如果是调用 activity,系统会弹出界面要求用户对多个软件做出选择,攻击者可以模仿真实软件的图标和名称吸引用户点击;如果是调用service,系统会随机 选择一个service;如果是调用receiver,系统会逐一地将Intent发给这些receiver。
劫持了调用后,攻击者可以(但并非总能成功):
  • 启动一个虚假的软件界面,展开钓鱼攻击(例如要求用户输入账户密码)
  • 读取Intent携带的数据
  • 拦截broadcast的进一步发送,导致真正的receiver无法收到消息,从而拒绝服务
  • 如果是用startActivityForResult()调用了虚假的activity,可以返回恶意或虚假的结果给调用者
  • 执行其他恶意代码
限于篇幅,对这些情形我们不提供示例代码。来看一下怎么解决这类问题。
不需要隐式调用
除了基于Intent类中已有ACTIONs的隐式调用,绝大部分隐式调用都属于这两种情况:同一软件中不同组件的调用;同一开发者不同软件间的调用。
这两种情况下,事实上,开发时都已可以确定要调用的组件是哪个。因此可以避免隐式调用,改为基于组件名的显式调用。
需要隐式调用
发送broadcast除了使用sendBroadcast(Intent),还有一个方法是sendBroadcast(Intent, String),它的第二个参数可以指定接受者需要的权限。
如果是调用activity或者service,目前我所知,还没有简单的方法实现接收者的权限限制。在Android文档中提出可以自定义Binder和AIDL实现通信双方的互相验证,但真正实现并不容易,也不为官方所推荐。
数据验证
无论是客户端还是服务器,在处理外部获得的数据之前,都应先判断和验证数据的有效性。这里主要指是否包含畸形的数据。
在 Web开发中,服务器需要对用户提交的数据进行有效性验证,否则很容易出现众所周知的SQL注入等攻击。在移动开发中也不例外。虽然客户端与服务器在底层 通信协议上对用户是透明、不可见的,但开发者不应因此就假设双方传输的数据永远会和预先设计的一致。类似的,在读取用户从UI元素输入的输入、读取存储在 本地的数据后,使用前也应进行有效性验证。
软件版权保护
攻击者对Android软件进行逆向分析,除了寻找其中的安全漏洞,还可以直接攻击软件本身。
  • 破解软件的收费机制、License验证或功能限制;
  • 修改软件代码,去掉广告库,或者修改广告库(一般是改变推介ID字段),或者增加广告库,然后重新打包并分发;
  • 重新打包,植入恶意代码并分发;
  • 逆向分析,学习软件特色功能的实现方法,或者获得可复用的代码;
可以采取多种措施来增加破解、修改和逆向分析的难度,减少被攻击的可能。这些措施包括:
  • 使用代码混淆工具,例如SDK带的ProGuard以及其他Java混淆器。
  • 采用NDK开发核心模块。
  • 使用官方或第三方的软件保护方案,例如SDK的android.drm包、Google Play的软件许可(Application Licensing)支持。
  • 去掉开发时的调试代码,关闭调试开关,删除多余的Log代码。
然而,采取了这些措施并不等于就万无一失了。例如,用NDK开发的Native代码,也可以使用IDA Pro及其插件来反汇编、反编译和调试;用Google的DRM方案,也有AntiLVL这样的破解工具。理论上,现有防御手段都可能找到方法继续攻击, 其价值只是提高攻击难度和成本。
总结
已出现和将要出现的威胁
到 目前为止,在学术研究以外,针对Android软件漏洞的攻击只出现一起—劫持国外多个社交网站客户端登陆会话的黑客工具FaceNiff。但随着攻击者 制造传播恶意代码的成本增加和收益降低,以及移动终端隐私数据逐渐成为地下产业链的交易资源,针对Android流行软件漏洞的攻击在未来几年之内几乎一 定会出现并爆发。
统一的安全模型
限于篇幅,本文只介绍了几种常见且简单的安全问题,还存在许多我们知道的、还不知道的漏洞。如何找到和解决这些问题?
回顾已介绍的内容,我们可以发现它们有类似的安全模型:通信双方的信任问题。
  • 在数据存储中,读写数据的代码和存储在本地的数据互相不可信。
  • 在数据通信中,发送者和接受者互相不可信。
  • 在登录认证中,发起认证请求的用户的和接受认证请求的服务器互相不可信。
  • 在组件间通信中,发起Intent的组件与接收Intent的组件互相不可信。
  • 在数据验证中,处理数据的模块不能相信产生数据的源。
面对将来的问题,我们也可以尝试抽象出这种模型,区分互相不可信的实体,然后在不可信、不安全的基础上,尽可能地实现相对的可信和安全。
进一步学习和行动
Android 的开发文档Best Practices: Designing for Security和源码文档Tech Info: Security分别从开发和系统实现的角度介绍了系统的安全机制。另外,viaForensics提供了名为42+ Best Practices: Secure mobile development for iOS and Android的在线教程,更详细地介绍了移动软件面临的安全威胁,并给出了安全开发实践策略。
社区方面,从Android安全开发的角 度,Stack-Overflow并不一定是很好的选择—其中一些最佳回答没有考虑安全,直接使用可能产生问题。Google Group的anroid-security-discuss讨论组则更为专业和准确。OWASP成立了一个Mobile Security工作组,目前已发布Top Ten Mobile Risks等多份白皮书,并举办了AppSec会议。这个工作组的效率虽然不高,但产出质量非常棒。
学术方面,2011和2012年的四大会议及其work-shop上均有移动软件漏洞挖掘和攻击阻止的论文出现,从它们的related works部分可以综合快速地了解学术界的思路。
目前的移动开发还没有形成如此成熟的体系,这也许与其轻快敏捷的互联网产品开发风格有关。但我相信,真正实效的移动软件安全开发,最终依然会融合到需求分析、系统设计、开发实现、测试验证、部署运维等每一个环节,从而与PC平台的SDL殊途同归。
作者肖梓航,安天实验室高级研究员,主要方向是移动反病毒和移动软件安全,发起或参与了多个移动安全开源项目。

Android软件安全开发实践(上)

文/肖梓航
Android开发是当前最火的话题之一,但很少有人讨论这个领域的安全问题。本系列将分两期,探讨Android开发中常见的安全隐患和解决方案。第一期将从数据存储、网络通信、密码和认证策略这三个角度,带你走上Android软件安全开发实践之旅。
过去两年,研究人员已发现Android上的流行软件普遍存在安全缺陷或安全漏洞。漏洞频发的原因可能有很多,例如以下几种。
  • 与一切都是集中管理的iOS相比,Android提供了一种开放的环境,在获得了灵活性、可以满足各种定制需求的同时,也损失了部分安全性。
  • 开发团队通常将精力集中在产品设计、功能实现、用户体验和系统效率等方面,而很少考虑安全问题。
  • Android提供的安全机制比较复杂,开发者需要理解它们,并对常见的攻击思路和攻击方法有所了解,才能有效地保护软件。
  • 一方面,目前很少出现对特定移动软件安全漏洞的大规模针对性攻击,在真实的攻击出现之前,许多人对此并不重视。另一方面,利用这些漏洞展开攻击并不太难,许 多攻击方法和工具都已经成熟。一旦出现这种攻击,用户的个人隐私数据可能发生泄漏,账户信息可能被盗取,如果与钓鱼等攻击结合,甚至可能产生经济损失。产 品开发团队则可能由此面临信任危机和法律风险。
我在此前进行的一些安全评估中,看到不少开发团队已具有非常高的安全开发水平,但也发现有知 名企业的软件存在各种缺陷。在本文中,我们将向大家介绍Android软件中比较常见的安全缺陷或安全漏洞,分析产生问题的原因,介绍可能的攻击方法,并 给出解决问题的建议。希望能抛砖引玉,引起大家对这类问题的关注。
数据存储
Android软件可以使用的存储区域分为外部(SD卡)和内部(NAND闪存)两种。除了大小和位置不同之外,两者在安全权限上也有很大的区别。外部存储的文件没有读写权限的管理,所有应用软件都可以随意创建、读取、修改、删除位于外部存储中的任何文件,而仅仅需要申明READ_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE权限。内部存储则为每个软件分配了私有区域,并有基于Linux的文件权限控制,其中每个文件的所有者ID均为Android为该软件设立的一个用户ID。通常情况下,其他软件无权读写这些文件。
关于数据存储可能出现的问题包括以下几种。
将隐私数据明文保存在外部存储
例如,聊天软件或社交软件将聊天记录、好友信息、社交信息等存储在SD卡上;备份软件将通信录、短信等备份到SD卡上等。如果这些数据是直接明文保存(包括 文本格式、XML格式、SQLite数据库格式等)的,那么攻击者写的软件可以将其读取出来,并回传至指定的服务器,造成隐私信息泄露。
较好的做法是对这些数据进行加密,密码保存在内部存储,由系统托管或者由用户使用时输入。
将系统数据明文保存在外部存储
例如,备份软件和系统辅助软件可能将用户已安装的其他软件数据保存至SD卡,以便刷机或升级后进行恢复等;或者将一些系统数据缓存在SD卡上供后续使用。同样的,如果这些数据是明文保存的,恶意软件可以读取它们,有可能用于展开进一步的攻击。
将软件运行时依赖的数据保存在外部存储
如果软件将配置文件存储在SD卡上,然后在运行期间读取这些配置文件,并根据其中的数据决定如何工作,也可能产生问题。攻击者编写的软件可以修改这些配置文 件,从而控制这些软件的运行。例如,如果将登录使用的服务器列表存储在SD卡中,修改后,登录连接就会被发往攻击者指定的服务器,可能导致账户泄露或会话 劫持(中间人攻击)。
对这种配置文件,较安全的方法是保存到内部存储;如果必须存储到SD卡,则应该在每次使用前判断它是否被篡改,例如,与预先保存在内部的文件哈希值进行比较。
将软件安装包或者二进制代码保存在外部存储
现在很多软件都推荐用户下载并安装其他软件;用户点击后,会联网下载另一个软件的APK文件,保存到SD卡然后安装。
也有一些软件为了实现功能扩展,选择动态加载并执行二进制代码。例如,下载包含了扩展功能的DEX文件或JAR文件,保存至SD卡,然后在软件运行时,使用 dalvik.system.DexClassLoader类或者java.lang.ClassLoader类加载这些文件,再通过Java反射,执行 其中的代码。
如果在安装或加载前,软件没有对SD卡上的文件进行完整性验证,判断其是否可能被篡改或伪造,就可能出现安全问题。
在这里,攻击者可以使用称 为“重打包”(re-packaging)的方法。目前大量Android恶意代码已采用这一技术。重打包的基本原理是,将APK文件反汇编,得到 Dalvik指令的smali语法表示;然后在其中添加、修改、删除等一些指令序列,并适当改动Manifest文件;最后,将这些指令重新汇编并打包成 新的APK文件,再次签名,就可以给其他手机安装了。通过重打包,攻击者可以加入恶意代码、改变软件的数据或指令,而软件原有功能和界面基本不会受到影 响,用户难以察觉。
如果攻击者对软件要安装的APK文件或要加载的DEX、JAR文件重打包,植入恶意代码,或修改其原始代码;然后在SD 卡上,用其替换原来的文件,或者拷贝到要执行或加载的路径,当软件没有验证这些文件的有效性时,就会运行攻击者的代码。攻击结果有很多可能,例如直接发送 扣费短信,或者将用户输入的账户密码发送给指定的服务器,或者弹出钓鱼界面等。
因此,软件应该在安装或加载位于SD卡的任何文件之前,对其完整性做验证,判断其与实现保存在内部存储中的(或从服务器下载来的)哈希值是否一致。
全局可读写的内部文件
如果开发者使用openFileOutput(String name,int mode)方法创建内部文件时,将第二个参数设置为Context.MODE_WORLD_READABLE或 Context.MODE_WORLD_WRITEABLE,就会让这个文件变为全局可读或全局可写的。
开发者也许是为了实现不同软件之间的数据共享,但这种方法的问题在于无法控制哪个软件可以读写,所以攻击者编写的恶意软件也拥有这一权限。
如果要跨应用共享数据,一种较好的方法是实现一个Content Provider组件,提供数据的读写接口,并为读写操作分别设置一个自定义权限。
内部敏感文件被root权限软件读写
如果攻击者的软件已获得root权限,自然可以随意读写其他软件的内部文件。这种情况并不少见。
  • 大量的第三方定制ROM提供了root权限管理工具,如果攻击者构造的软件伪造成一些功能强大的工具,可以欺骗用户授予它root权限。
  • 即便手机安装的官方系统,国内用户也大多乐于解锁、刷recovery并刷入root管理工具。
  • 在Android 2.2和2.3中,存在一些可以用于获取root权限的漏洞,并且对这种漏洞的利用不需要用户的确认。
因此,我们并不能假设其他软件无法获取root权限。即便是存在内部的数据,依然有被读取或修改的可能。
前面提到,重要、敏感、隐私的数据应使用内部存储,现在又遇到root后这些数据依然可能被读取的问题。我对这个问题的观点是,如果攻击者铤而走险获得root权限(被用户觉察或者被安全软件发现的风险),那理论上他已拥有了系统的完整控制权,可以直接获得联系人信息、短信记录等。此时,攻击者感兴趣的 软件漏洞利用更可能是获得其他由软件管理的重要数据,例如账户密码、会话凭证、账户数据等。例如,早期Google钱包将用户的信用卡数据明文存储,攻击 者获取这些数据后,可以伪装成持卡人进行进一步攻击以获得账号使用权。这种数据就是“其他由软件管理的重要数据”。
这个问题并没有通用的解决方法。开发者可能需要根据实际情况寻找方案,并在可用性与安全性之间做出恰当的选择。
网络通信
Android软件通常使用WiFi网络与服务器进行通信。WiFi并非总是可信的。例如,开放式网络或弱加密网络中,接入者可以监听网络流量;攻击者可以自己设置WiFi网络钓鱼。此外,在获得root权限后,还可以在Android系统中监听网络数据。
不加密地明文传输敏感数据
最危险的是直接使用HTTP协议登录账户或交换数据。例如,攻击者在自己设置的钓鱼网络中配置DNS服务器,将软件要连接的服务器域名解析至攻击者的另一台服务器;这台服务器就可以获得用户登录信息,或者充当客户端与原服务器的中间人,转发双方数据。
早期,国外一些著名社交网站的Android客户端的登录会话没有加密。后来出现了黑客工具FaceNiff,专门嗅探这些会话并进行劫持(它甚至支持在WEP、WPA、WPA2加密的WiFi网络上展开攻击!)。这是目前我所知的唯一一个公开攻击移动软件漏洞的案例。
这类问题的解决方法很显然—对敏感数据采用基于SSL/TLS的HTTPS进行传输。
SSL通信不检查证书有效性
在SSL/TLS通信中,客户端通过数字证书判断服务器是否可信,并采用证书中的公钥与服务器进行加密通信。
然而,有开发者在代码中不检查服务器证书的有效性,或选择接受所有的证书。例如,开发者可以自己实现一个X509TrustManager接口,将其中的 checkServerTrusted()方法实现为空,即不检查服务器是否可信;或者在SSLSocketFactory的实例中,通过 setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER),接受所有证 书。做出这两种选择的可能原因是,使用了自己生成了证书后,客户端发现证书无法与系统可信根CA形成信任链,出现了 CertificateException等异常。
这种做法可能导致的问题是中间人攻击。
在钓鱼WiFi网络中,同样地,攻 击者可以通过设置DNS服务器使客户端与指定的服务器进行通信。攻击者在服务器上部署另一个证书,在会话建立阶段,客户端会收到这张证书。如果客户端忽略 这个证书的异常,或者接受这个证书,就会成功建立会话、开始加密通信。但攻击者拥有私钥,因此可以解密得到客户端发来数据的明文。攻击者还可以模拟客户 端,与真正的服务器联系,充当中间人做监听。
解决问题的一种方法是从可信CA申请一个证书。但在移动软件开发中,不推荐这种方法。除了申请 证书的时间成本和经济成本外,这种验证只判断了证书是否CA可信的,并没有验证服务器本身是否可信。例如,攻击者可以盗用其他可信证书,或者盗取CA私钥 为自己颁发虚假证书,这样的攻击事件在过去两年已有多次出现。
事实上,移动软件大多只和固定的服务器通信,因此可以在代码中更精确地直接验 证是否某张特定的证书,这种方法称为“证书锁定”(certificate pinning)。实现证书锁定的方法有两种:一种是前文提到的实现X509TrustManager接口,另一种则是使用KeyStore。具体可参考 Android开发文档中HttpsURLConnection类的概览说明。
使用短信注册账户或接收密码
也有软件使用短信进行通信,例如自动发送短信来注册、用短信接收初始密码、用短信接收用户重置的密码等。
短 信并不是一种安全的通信方式。恶意软件只要申明了SEND_SMS、RECEIVE_SMS和READ_SMS这些权限,就可以通过系统提供的API向任 意号码发送任意短信、接收指定号码发来的短信并读取其内容,甚至拦截短信。这些方法已在Android恶意代码中普遍使用,甚至2011年就已出现拦截并 回传短信中的网银登录验证码(mTANs)的盗号木马Zitmo。
因此,这种通过短信注册或接收密码的方法,可能引起假冒注册、恶意密码重置、密码窃取等攻击。此外,这种与手机号关联的账户还可能产生增值服务,危险更大。较好的实现方式还是走Internet。
密码和认证策略
明文存储和编码存储密码
许多软件有“记住密码”的功能。如果开发者依字面含义将密码存储到本地,可能导致泄漏。
另 外,有的软件不是直接保存密码,而是用Base64、固定字节或字符串异或、ProtoBuf等方法对密码编码,然后存储在本地。这些编码也不会增加密码 的安全性。采用smali、dex2jar、jd-gui、IDA Pro等工具,攻击者可以对Android软件进行反汇编和反编译。攻击者可以借此了解软件对密码的编码方法和编码参数。
较好的做法是,使用基于凭据而不是密码的协议满足这种资源持久访问的需求,例如OAuth。
对外服务的弱密码或固定密码
另一种曾引起关注的问题是,部分软件向外提供网络服务,而不使用密码或使用固定密码。例如,系统辅助软件经常在WiFi下开启FTP服务。部分软件对这个FTP服务不用密码或者用固定密码。在开放或钓鱼的WiFi网络下,攻击者也可以扫描到这个服务并直接访问。
还有弱密码的问题。例如,早期Google钱包的本地访问密码是4位数字,这个密码的SHA256值被存储在内部存储中。4位数字一共只有10000种情况,这样攻击软件即便是在手机上直接暴力破解,都可以在短时间内获得密码。
使用IMEI或IMSI作为唯一认证凭据
IMEI、IMSI是用于标识手机设备、手机卡的唯一编号。如果使用IMSI或IMEI作为用户认证的唯一凭据,可能导致假冒用户的攻击。
首先,应用要获取手机的IMEI、手机卡的IMSI并不需要特殊权限。事实上,许多第三方广告库回传它们用于用户统计。其次,得到IMEI或IMSI后,攻 击者有多种方法伪造成用户与服务器进行通信。例如,将原软件重打包,使其中获取IMEI、IMSI的代码始终返回指定的值;或修改Android代码,使 相关API始终返回指定的值,编译为ROM在模拟器中运行;甚至可以分析客户端与服务器的通信协议,直接模拟客户端的网络行为。
因此,若使用IMEI或IMSI作为认证的唯一凭据,攻击者可能获得服务器中的用户账户及数据。
作者肖梓航,网名Claud,安天实验室高级研究员,主要方向是移动反病毒和移动软件安全,发起或参与了多个移动安全开源项目。博客:http://claudxiao.net