27

Apache Tomcat CVE-2020-1938以及安全防御 - ThoughtWorks洞见

 3 years ago
source link: https://insights.thoughtworks.cn/apache-tomcat-cve-2020-1938/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

这是个什么漏洞

最近(2020年2月20日)Apache Tomcat爆出一个高危的服务器文件包含漏洞(CVE-2020-1938),据国家信息安全漏洞共享平台上的漏洞描述来看,攻击者可以利用这个漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件。

这次的漏洞引起了轩然大波,漏洞被定为高危可能仅仅是因为没有比这更加高的漏洞危害等级了,我仿佛闻到了类似PM2.5爆表的味道。

漏洞评分

是任意文件读取啊!

从漏洞描述来看,这个漏洞允许攻击者读取Tomcat上所有webapp目录下的任意文件。敲黑板,划重点,注意是读取webapp目录下的任意文件,而你用java开发的应用程序的war包自然是放在webapp目录下,当然也能够被攻击者读取到。这意味着,如果你把数据库用户名密码、连接其他后端服务的账号、JWT签名secret、OAuth AppSecret等密钥信息放在properties文件里的话,那么,攻击者可能现在也拿到了这些信息,并且正在试着入侵你的服务器。

经典war结构

经典war结构

泄露的不仅仅只是密钥,war包其实就是个压缩文件,解压后不仅能拿到properties文件,还能获得class文件,因此攻击者还能逆向获取到应用程序源码,进而从源代码中挖掘出更多其他漏洞加以利用。比如某些隐藏API或者参数、业务逻辑漏洞等,在有源代码的情况,能够极大的缩短攻击者找到这些漏洞的时间。对了,如果你把密钥硬编码到源码里(希望你早就不这么干了),同样也会泄露。

还有一种可能很少遇到但影响确实很大的场景:如果某家公司对外提供的服务主要依赖于其投入巨资打造的算法,这个算法是公司的核心竞争力,现在有可能因为这个漏洞而泄露了源代码,进而导致核心算法流失,那么这造成的损失恐怕已经不能用“巨大”这个词来形容了。

不硬编码密钥,并且密钥抽离到配置文件,这么做还远远不够

因为这个漏洞而泄露源代码的情况不是这篇文章要讨论的重点,我们收回来,把关注点放到密钥泄露上面。

现如今的应用程序,尤其是企业级应用程序通常都会和其他系统进行交互,尤其是微服务的盛行,后端系统的数量变得更为庞大。应用程序在集成这些内部或外部系统的时候,通常都需要账号或者密钥。与此同时,如果应用程序涉及加解密、签名功能的话,还需要对应的密钥。

为方便描述,让我们把这些账号、Key、密码、密钥等统称为密钥。不要把密钥硬编码到源代码里,这是人尽皆知的共识,一方面是担心密钥随着源码泄露而泄露,另一方面也是便于维护,轮换密钥可以更加容易一些。

既然密钥不硬编码到源代码,那这些密钥总要有一个地方存放吧,大多数时候密钥会被存放在一个properties文件里,并且和源代码存放于同一个代码仓库。当然也有团队把所有密钥抽离到一个单独properties文件,并将其存放到一个单独的代码仓库,在部署的时候再读取出来并且和应用程序合并到一起。

密钥放到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。

3层网络防御

3层网络防御

再进一步,通常后端应用都会有一个Ngnix反向代理顶在最前面,而在它前面可能还有网络防火墙,这两者也有管理网络映射、路由的功能。因此,在这种场景下,Tomcat服务器至少受到了3层防御工事的保护。除非这3层防御都配置失误……

Apache Tomcat CVE-2020-1938这个漏洞确实凶猛,攻击者可以读取到webapp目录下的任意文件,包括war包。而war包里有properties文件,不少开发团队都把连接数据库的用户名密码、JWT 签名secret、加解密密钥等重要信息放在这个文件里。这个漏洞的存在,允许攻击者可以最终读取到这些密钥数据,当然源码也是能通过反编译war包里的class文件得到的。

为了避免密钥泄露,常规做法(不要硬编码密钥到源代码、密钥单独放置在properties文件并且和源代码分别存储在不同的代码仓库)并不奏效,更为妥善的办法是使用密钥管理服务,你可以直接使用云服务提供商的密钥管理服务,也可以自己搭建一个。

安全原则是很重要的,尤其是最小权限和纵深防御原则。在这个漏洞案例中,就算你使用的Tomcat有问题,但由于相关端口已经关闭,而且还有好几层的网络映射和路由配置的防御,所以也不会受到影响。当然,第三方组件安全管理、安全补丁管理、实施端口监控等手段也有助于减轻或避免这个漏洞带来的影响,但这就是另一个话题了,我们下次再聊。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK