最近(2020年2月20日)Apache Tomcat爆出一个高危的服务器文件包含漏洞(CVE-2020-1938),据国家信息安全漏洞共享平台上的漏洞描述来看,攻击者可以利用这个漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件。
这次的漏洞引起了轩然大波,漏洞被定为高危可能仅仅是因为没有比这更加高的漏洞危害等级了,我仿佛闻到了类似PM2.5爆表的味道。
从漏洞描述来看,这个漏洞允许攻击者读取Tomcat上所有webapp目录下的任意文件。敲黑板,划重点,注意是读取webapp目录下的任意文件,而你用java开发的应用程序的war包自然是放在webapp目录下,当然也能够被攻击者读取到。这意味着,如果你把数据库用户名密码、连接其他后端服务的账号、JWT签名secret、OAuth AppSecret等密钥信息放在properties文件里的话,那么,攻击者可能现在也拿到了这些信息,并且正在试着入侵你的服务器。
(典型的war包结构,properties及class文件都在里面)
泄露的不仅仅只是密钥,war包其实就是个压缩文件,解压后不仅能拿到properties文件,还能获得class文件,因此攻击者还能逆向获取到应用程序源码,进而从源代码中挖掘出更多其他漏洞加以利用。比如某些隐藏API或者参数、业务逻辑漏洞等,在有源代码的情况,能够极大的缩短攻击者找到这些漏洞的时间。对了,如果你把密钥硬编码到源码里(希望你早就不这么干了),同样也会泄露。
还有一种可能很少遇到但影响确实很大的场景:如果某家公司对外提供的服务主要依赖于其投入巨资打造的算法,这个算法是公司的核心竞争力,现在有可能因为这个漏洞而泄露了源代码,进而导致核心算法流失,那么这造成的损失恐怕已经不能用“巨大”这个词来形容了。
因为这个漏洞而泄露源代码的情况不是这篇文章要讨论的重点,我们收回来,把关注点放到密钥泄露上面。
现如今的应用程序,尤其是企业级应用程序通常都会和其他系统进行交互,尤其是微服务的盛行,后端系统的数量变得更为庞大。应用程序在集成这些内部或外部系统的时候,通常都需要账号或者密钥。与此同时,如果应用程序涉及加解密、签名功能的话,还需要对应的密钥。
为方便描述,让我们把这些账号、Key、密码、密钥等统称为密钥。不要把密钥硬编码到源代码里,这是人尽皆知的共识,一方面是担心密钥随着源码泄露而泄露,另一方面也是便于维护,轮换密钥可以更加容易一些。
既然密钥不硬编码到源代码,那这些密钥总要有一个地方存放吧,大多数时候密钥会被存放在一个properties文件里,并且和源代码存放于同一个代码仓库。当然也有团队把所有密钥抽离到一个单独properties文件,并将其存放到一个单独的代码仓库,在部署的时候再读取出来并且和应用程序合并到一起。
以上做法看上去似乎有助于保护密钥不泄露,但这次的Apache Tomcat 文件包含漏洞估计让不少团队吓出一身冷汗:不管你是硬编码还是费尽心思的通过划分代码仓库来管理properties文件,都没用,一旦war包被攻击者读取到,密钥也就泄露了。 以上做法只能对防止开发阶段的密钥泄露有一定的帮助。
为了更好的保护密钥不泄露,建议使用专门的密钥管理服务。你可以选择云服务提供商的密钥管理服务(比如AWS KMS、Azure KeyVault、腾讯和阿里等国内云服务提供商也有对应的密钥管理服务),也可以自己基于开源软件搭建(比如HashiCorp Vault)。限于篇幅原因,我们就不展开讲如何具体配置、使用这些密钥管理服务了。
那为什么密钥管理服务就能化解这个问题呢?原因在于,密钥管理服务将密钥加密后存储在专门的安全存储空间里,而不是放置在应用程序里,比如说war包或jar包中的properties文件里。在应用程序启动时,应用程序会从密钥管理服务读取到对应的密钥,然后再使用。如此一来,应用程序的properties文件中不再有任何密钥出现,就算攻击者拿到了这个文件,也无法读取到密钥。
如果你的架构设计遵循了安全原则,就算你把密钥硬编码到源代码或者放到了properties文件里,那也不意味着就一定会被攻击者获取到(排除运气成分)。
首先是最小权限原则。此次漏洞是因为Apache Tomcat的AJP协议的问题,这个协议默认使用8009端口,但其实绝大多数时候你都用不到这个协议。虽然Apache Tomcat默认开启这个协议,但如果你基于最小权限原则,只对外开放必要协议及端口(比如就开HTTP 8080),把其他的统统都禁用掉,那么你也并不会受此次漏洞影响。
然后是纵深防御原则。就算疏忽大意没有关闭AJP 8009端口,但如果你的应用程序运行在Docker容器里,那么容器和宿主机之间的网络映射就是一层防御,默认只开启必要服务的端口,比如只开HTTP 8080。尽管容器中实际运行的是一个受此次漏洞影响的Tomcat,并且还开启了AJP 8009端口,但因为这一层网络映射的存在,攻击者也无法从外部连接到Tomcat。
再进一步,通常后端应用都会有一个Ngnix反向代理顶在最前面,而在它前面可能还有网络防火墙,这两者也有管理网络映射、路由的功能。因此,在这种场景下,Tomcat服务器至少受到了3层防御工事的保护。除非这3层防御都配置失误……
Apache Tomcat CVE-2020-1938这个漏洞确实凶猛,攻击者可以读取到webapp目录下的任意文件,包括war包。而war包里有properties文件,不少开发团队都把连接数据库的用户名密码、JWT 签名secret、加解密密钥等重要信息放在这个文件里。这个漏洞的存在,允许攻击者可以最终读取到这些密钥数据,当然源码也是能通过反编译war包里的class文件得到的。
为了避免密钥泄露,常规做法(不要硬编码密钥到源代码、密钥单独放置在properties文件并且和源代码分别存储在不同的代码仓库)并不奏效,更为妥善的办法是使用密钥管理服务,你可以直接使用云服务提供商的密钥管理服务,也可以自己搭建一个。
安全原则是很重要的,尤其是最小权限和纵深防御原则。在这个漏洞案例中,就算你使用的Tomcat有问题,但由于相关端口已经关闭,而且还有好几层的网络映射和路由配置的防御,所以也不会受到影响。当然,第三方组件安全管理、安全补丁管理、实施端口监控等手段也有助于减轻或避免这个漏洞带来的影响,但这就是另一个话题了,我们下次再聊。
文/ThoughtWorks 马伟
更多精彩洞见,请关注微信公众号:ThoughtWorks洞见