如果你至今依然在坚持写博客,在知乎或其他自媒体平台上发表文章,那你应该对Markdown很熟悉了。这是一种轻量级标记语言,借此可以用纯文本格式编写文档,并用简单的标记设置文档格式,随后即可轻松转换为具备精美排版的内容。简单来说,Markdown像纯文本格式那样简洁明了,又能提供基础的格式控制能力,但又不像Word之类的软件那么“笨重”,这种“轻写作”方式已经被很多博客和自媒体平台所采用。
然而今年初,Akamai和CredShields的研究人员通过合作,在主要面向软件开发者的博客平台Hashnode上发现一个本地文件包含(LFI)漏洞。该漏洞存在于一个批量Markdown导入功能中,借此攻击者将能从Hashnode的服务器上下载本地文件,例如绕过CDN代理直接获取SSH密钥、IP地址和其他信息。
本文我们就一起看看这个漏洞到底是怎么回事。
背景
Hashnode是一个面向开发者的平台,一个旨在教学知识的地方。但任何安全从业者都知道,任何好的事务都可能被恶意利用。在使用Hashnode平台的过程中,我们发现他们的批量Markdown导入功能存在重大漏洞,因此就深入研究了一下。最终我们吃惊地发现,借助这个漏洞,我们不仅可以拿到服务器的SSH私钥,甚至可以通过LFI绕过他们的CDN服务拿到服务器的真实IP地址。有了IP地址,后面还会发生什么简直都不用多想。
好在本次的研究中,我们发现Hashnode对自己的SSH设置了白名单,因此就算拿到密钥也无法执行任何操作,并且当我们将发现的情况上报给他们后,他们立即做出了响应。然而随着过去半年来LFI攻击飞速增加,我们觉得有必要将本次发现分享出来,以便更多安全专家能够更重视这方面的问题。
那么我们到底是如何通过批量导入功能拿到服务器的IP地址的?
一些都源自LFI
拿到一个新玩具后,最大的乐趣就是玩,对吧!因此我们开始围绕Hashnode的批量Markdown导入功能进行了各种实验。当时我们是打算将大量博客文章迁移到新平台,此时批量导入应该是最方便的方式了。Hashnode的批量Markdown导入功能需要我们提供Markdown文件,然而谁能想到,这会促使我们发现过去半年里一个最大的网络攻击载体!
那么这到底是如何做到的?我们发现,如果提供的Markdown文件中引用了图像文件,那么Markdown解析程序就会在内部寻找该文件并抛出一个错误(因为找不到该文件)。因此如果提供如下的Markdown内容:
解析程序将抛出下列错误信息:
Markdown解析程序会在存储Markdown的位置下寻找“blog.jpg”文件,并在不可避免的失败后抛出错误信息。那这是否意味着如果能找到所引用的内部文件,就可以成功获取?那自然了!
利用LFI
经典的passwd载荷足以欺骗Markdown解析程序寻找指定的本地文件,并将其上传至Hashnode的CDN,这一切都如期发生了。我们的恶意Markdown内容是这样的:
这个文件被顺利解析出来了,没有遇到任何问题。作为回应,我们得到了一个URL,借此可以获取上传的文件(因为服务器假设这是一个合法的图像文件):
接下来我们只需要访问该URL并下载这个文件(本例中为/etc/passwd文件)就可以直接查看其内容。
那么接下来,我们当然要尝试着获取一些敏感文件,包括用户的SSH私钥。幸运的是,该私钥位于默认位置,例如/home/user/.ssh/id_rsa。我们尝试着登录服务器来验证是否可行(只有当对应的公钥位于服务器上的“authorized_keys”文件中,才有可能这样登录,而符合这种要求的概率非常小)。不过在登录时我们遇到了一个障碍,Hashnode使用了CDN服务,这意味着我们无法连接到服务器上任何打开的端口,因为源IP被隐藏在CDN代理之后。但我们并未放弃。
查找服务器的IP地址
这个任务并不难,但我们事先并不了解这一点。我们有同事建议看看/proc/net/tcp文件,该文件可能会揭示出有关服务器IP地址的一些信息。这下子,我们可算是挖到宝了。
神秘的/proc/net/tcp
那么这到底是个什么文件?/proc/net/tcp包含了与所有网络连接以及对应的接口有关的十六进制信息。这个文件的内容一般看起来类似这样:
其中每一项的含义较为晦涩。好在有kernel.org文档,借此我们顺利拿到了自己需要的信息。这些信息的含义如下:
我们重点关注了本地地址,也就是分配给服务器的IP地址。每个本地地址条目都是十进制IP地址值的十六进制表达形式,只不过顺序是反的。因此如果条目为AABBCCDD而对应的IP为a.b.c.d,那么AA=d,BB=c,以此类推。
我们从/proc/net/tcp文件中拿到了三个IP地址。一个是127.0.0.1,这没什么奇怪的。另一个是10.x.x.x,一个内部IP地址。还有一个可转换为192.x.x.x,这个地址引起了我们的关注,它属于一家云服务提供商。我们尝试着通过netcat建立SSH连接,成功了:
在成功建立SSH连接后,我们没有继续执行其他操作,而是将我们的发现报告给Hashnode团队。
谈谈LFI吧
作为Akamai安全运营响应中心成员,我每天都会看到数以百万计的LFI攻击,而SQL注入则不那么普遍,并且方法也更为集中。大家都认为SQL输入很普遍,那么为何LFI这个攻击载体现在发展这么快?答案很简单:LFI的实施更简单。Hashnode已经采取了各种正确的应对措施,并且更棒的是,他们可以阻止我们进一步的探索。毕竟我们也是偶然发现这个问题并且出于研究的目的进行了尝试。
如果攻击者怀揣恶意攻击一个目标,那么LFI可能是一种可以帮助攻击者获取敏感信息(例如源IP地址)的简单方式。成功的SQL注入攻击可能需要复杂的载荷,但如果使用LFI的方法,只需要一个几个字符大小的载荷就足以造成非常严重的破坏。
作为安全专家,我们比任何人都清楚,即便看起来最良性的“功能”也可以用来作恶。保持警惕(无论是在代码发布前还是发布后)是赢得战斗的秘诀。这次经历中最让我们感到震惊的地方在于,LFI攻击的数量如此众多!
总结
在信息安全界,保护敏感信息是一个老生常谈的问题。作为研究人员,我们非常清楚信息是如何被滥用的。有鉴于LFI攻击如此庞大的规模,这方面需要引起开发者和安全专家的广泛注意。
各种报错信息看似繁杂,但其中往往蕴含着很多有价值的内容,有心的攻击者完全可以借助这些内容发起不同类型的攻击。因此到底要在对外展示的错误信息中包含什么内容,开发者必须慎重考虑。
本文介绍的,通过Markdown导入和解析功能进行的本地文件包含攻击,这种方式可能适用于更多应用程序,只不过暂时还未被发现,但这只是时间问题。
Linux的某些文件包含了大量关键信息,攻击者可以借此进一步扩大攻击操作的影响力。例如上文介绍的例子中,我们就通过/proc/net/tcp文件获得了Hashnode所用云服务提供商的IP地址。因此对待这类文件同样需要慎重。