原文: What’s Up With All of These Software Security Vulnerabilities? https://www.simplethread.com/whats-up-with-all-of-these-softw...
当你在读这篇文章的时候,你可能已经听说了最近发生在log4j库的一个漏洞。 或者也可能是在几年以后,又发现了另一个巨大的软件漏洞,有人把这篇文章发给你,希望能帮助你解释发生了什么。无论哪种方式,你可能都会想到底发生了软件世界中发生了什么,使得这些关键的未发现的漏洞能够潜伏多年,然后突然被发现并导致整个技术界集体失控。我将从一个相当基本的层面来解释究竟发生了什么,如果你是一个软件工程师,你可能不会有什么收获。然而如果你并不在IT行业,而你又想理解究竟发生了什么,那你就来对地方了。
什么是软件?
我们都大致知道什么是软件。我们知道它在我们的计算机上运行。但实际上,它究竟是什么?好,在你电脑上运行的所有东西都是软件,当你的计算机启动时,你所登录的操作系统,你启动的应用程序,如word。你收到五年内没有更新你的反病毒软件的小弹出窗口提醒。它们都是软件。
所有的这些软件都只是由一堆文件组成,你的计算机知道如何处理。桌面上word图标是一个特殊的图片文件,Word应用程序本身由一堆文件组成,你朋友发送给你的图片也是一个文件。一切都只是你的计算机可以与之交互的不同类型的文件。
你桌面上的word图标和你朋友发给你的图片唯一的不同的是,你的计算知道当你点击word图标,需要启动的是word应用程序。
在这个层面上,这就是你需要知道的全部。你计算机上上的一切都由成千上万个文件组成。 而你的计算知道如何与其中大多数文件进行交互。点击一个图像文件,你的计算机会打开一个处理图像的应用程序。点击一个网站链接,你的计算机会打开一个网络浏览器。
文件,一切都是文件。
但这可能有点复杂,当你在浏览器上打开一个网站,比如使用chrome浏览器、edge浏览器,访问网站得到的数据是一个计算机通过互联网发送给你的数据。
其他计算机,被称为服务器,躺在这个星球的某个地方。当你输入”www.google.com“时,谷歌的服务器会向你发送一系列文件,而你的浏览器则利用这些文件来显示一个网页给你。这相当酷。 然而,他们仍然是一堆文件,只是这次他们通过网络发送给你。
但这些文件由什么组成?
所以你的计算机只是由一堆文件组成,这些文件里面实际上是什么,这有点难以解释,但我会尝试说明一下。软件被程序语言书写。编程语言只是一堆文本,可以被转化为一组你计算机理解的操作。下面有一个编程语言的小代码块:
import "fmt"
func main() {
fmt.Println("hello world")
}
这就是人们常说的代码。这段代码是做什么的并不重要,只要知道所有的软件都是由一堆文件组成,这些文件有大量的文本,就像你上面看到的。而却要知道,某个人在某个地方必须编写所有这些文本。
为了让这些文本可以被你的计算机识别,需要一个被称为“编译器”的特殊程序。编译器将这些文本转化为你的计算机可以运行的一堆指令,并将这些指令塞入一个文件(或一堆文件)。所以当你启用应用程序像chrome,你的计算机会找到被编译后的chrome文件,打开的,然后运行指令。这就是你的电脑所做的一切,整天都是如此。它只是躺在那里,打开一个又一个文件,读出指令,然后一遍一遍地运行它们。一整天,每天。
我还是没理解安全漏洞是什么意思
我知道,我知道,再忍耐一下,我们在接近答案了。所以我们的计算机上有一堆特殊的文件,里面是编译器创建的一堆指令。我们的计算机只需按照这些指令执行操作。这些指令非常简单,他们并不像“显示chrome窗口给用户看”这些复杂,而是一些简单的指令,像是将两个数相加,或者在屏幕的某个位置画一个点。实际的指令会更原始,但我想你会明白我的意思。指令是非常基本的,而应用程序需要大量的这些指令才能做任何事情。
这些指令必须非常简单,这样你就可以用各种不同的方式组合大量指令,从而可以让你的计算机几乎可以做一切事情。
如果这些指令像“在屏幕上画一个按钮”这样复杂的话,那么你需要大量非常具体的指令才能完成几乎任何你想让它执行的任务。你需要绿色按钮指令去绘制绿色按钮,红色按钮指令去绘制红色按钮。很快就会失去控制。 相反,我们有的只是一堆基本的指令,那我们就可以将其组合起来去绘制红色按钮或绿色按钮。这些非常简单的指令驱使计算机逐像素的去绘制按钮的每个部分。
因为使用了非常简单的指令,我们可以无限组合它们让这些指令可以在屏幕上绘制任何东西,包括您现在正在阅读的字母。为了生产所有这些指令,我们需要大量的代码。您的计算机相当愚笨,整天只是坐在那里执行指令。因此,我们编写的代码需要以及其详细的方式告诉您的计算机需要做每一件事情......,详细到令人痛苦的程度。
例如,让我们回顾一下之前提到的网络浏览器示例,您在浏览器中输入了谷歌的地址,为了让你浏览到网页发生了什么? 如果我告诉您,您可能不会相信,发生了数不胜数的事情。这包括读取您按下键盘的键位在屏幕上绘制字母,再通过互联网向Google发送请求的一切。但是每个动作都是由成千上万个非常简单的指令组成。
真不可思议,是嘛? 不要担心,即使是程序员也难以理解其中的每一个细节。这件事是如此复杂,以至于很难完全理解。这几乎是所有软件工程师共享的一个重要秘密之一。我们之中的大多数人对于它的出色工作,感觉惊讶不已。它是令人难以置信的复杂,而且没有人真正理解这一切。
是不是开始觉得像纸牌屋了
现在你应该知道软件工程师的感受了。也许您会对这个问题有疑问,如果要完成非常简单的任务需要数百万条指令,那么计算机是如何完成世界上形形色色的一切呢? 我很高兴你这么问,因为软件工程师不必编写他们应用程序的所有代码。如果软件工程师不得不编写现代应用程序的每一行代码,那么即使是最简单的应用程序也需要花费数年,即使是一个团队,也不例外。
原因在于每个应用程序都利用了成千上万其他工程师编写的代码。成千上万? 是的,举个例子,应用程序使用了大量您操作系统的代码,这就是运行在Windows上的应用程序,运行在Mac OS的程序也很相似的原因。这些应用程序和操作系统协同工作,在屏幕上绘制,连接到互联网,弹出通知。您计算机上的操作系统,代码量是巨大的。 Windows 10 大约有5000万行代码,是的,5000万行! 假设一本小说每页有20行文字,那么我们就需要250万页来容纳这么多代码,相当于超过11000本《哈利.波特》第一部的页数。堆积起来的高度将和埃菲尔铁塔一样高,或者帝国大厦的三分之二。 这些代码也不仅仅是英语文本,而是经过无数人编写和修改的细致复杂的计算代码。
这一点很难让人相信,不是吗?
而这只是作为Windows一部分的软件。我们的应用程序也需要完成很多相似应用完成的任务。
比如处理图片文件,显示PDF,显示页面,播放音乐,支付等,几乎是你能想象的任何功能。应用共享功能,程序语言使用称为”库“的特殊文件。库是包含应用程序需要使用的功能的一捆文件。我们可以将其想象为应用程序的工具箱,你的应用需要个锤子? 那么自己制作一个锤子是不值当的,我们这个库里面有这个锤子,那么就用这个锤子吧。
独立开发者可以利用成千上万个库来执行各种任务。软件工程师利用这些库,如果别人已经为你编写了这些代码,你为什么还要自己再写一遍呢?所以一些应用里面,代码库的代码要比应用自身的还要多。从这个角度来看,假设我有一个相对比较小的应用程序,我们团队编写的代码在大约50000行左右。我可能使用了数百万行代码的外部库,而且我还要在由数千万行代码组成的操作系统上运行。现代应用程序利用了大量的外部代码,漏洞就可能发生在这些代码中。
终于到达了安全漏洞
在这数百万行代码中,安全漏洞可能埋藏在任何一处。实际上,我所指的安全漏洞, 指的是代码中有些缺陷,导致代码以一种不应该的方式运行,
举个例子,在这数百万行代码中,假设有一个程序员编写的指令: "如果来自用户的文本值是以hacked: 开头,那么将后面的内容将被视为链接"。 所以如果有人访问你的应用输入: hacked: www.google.com, 它将会导致你的应用访问www.google.com. 听起来好像没有那么糟糕,除非代码支还支持获取该地址的内容,然后然尝试去做一些事情。也许这个程序的意图是加载特定位置的内容,并尝试将其作为应用程序的一部分运行?
然后用户输入"hacked:www.myevildomain.com" , 让你的应用程序将他们的恶意代码加载到你的应用程序。如果这个应用程序像facebook、google、Instagram的运行模式一样等,那么攻击者可能会利用该漏洞运行代码,从而获取用户存储的数据,这时就会出现安全漏洞的情况。
但是为什么有人这么做呢?
这个例子非常牵强。没有程序员会提供一种方式将别人的代码记载进自己的应用。这是软件基础的应用常识。应用程序经常要从用户那里获取数据,无论是浏览器的表单中的文本,还是用户上传的图片。应用程序从用户那里接收了大量的数据,然后以某种方式对其进行处理。有大量的用户需要清理来确保数据安全。通过接收和处理这些数据,会为特意构造的某个数据片段提供很多机会,使程序以程序员未曾预料的方式运行。
我们假设一个黑客将文件上传到图片共享服务,这个文件看起来像是一个图片文件,但实际上里面填充的是黑客希望运行的一些命令。黑客知道他们正在上传文件操作的网站使用了一些特别的库来处理图片上传,他们知道库会查看文件开头的一组特定值,然后根据这些值执行一些特殊的命令,并将图片文件数据传递出去。这种情况经常发生在处理上传到网站的图像上,用于调整大小、叠加效果、添加滤镜等等。如果该库在确保图片数据有效性方面不够谨慎,就可能意外执行黑客传递给它的命令,类似的情况在过去发生了很多。
这就是大多数软件安全漏洞的源头,处理来自用户的数据,图片,文件,文本,然后被诱导以不恰当的方式来处理。由于大多数应用使用了数百万行代码,因此不可能对其进行全面的审计来所有的缺陷。这些缺陷之所以不经常出现原因在于大多数应用有相当不错的安全实践,并且大多数库都有很多人审计代码。但是偶尔就会有一个微妙的错误进入到一个库中,并且由于它很少被使用,这个错误可能在多年间被忽略,这就是发生在log4j身上的情况。
log4j呢
log4j的安全漏洞和我上面描述的网站例子十分相似。log4j库在应用程序里面被用于向日志文件里面写入信息。日志文件记录了应用做的一切,被用于定位问题。日志文件里面的内容像下面这样
06:42:16.423Z INFO cool_app: start
06:42:16.427Z INFO cool_app: responding to request from 52.38.16.32 path /hello
06:42:16.433Z INFO cool_app: responding to request from 12.22.18.45 path /whatever
06:42:16.438Z INFO cool_app: something weird happened
Log4J有个特性就是允许在日志文件里面放置一个特殊的文本片段,log4j会这个特殊文本做特殊操作,它可能像下面这样:
“${my_value}”
这允许开发者在日志信息里面插入一个特殊的值,用其他值去替代这个特殊的文本片段
“${date:MM-dd-yyyy} - My Log Message”
实际上写入文件内容的如下所示:
09-12-2021 - My Log Message
这是非常实用的功能,允许你在日志消息中替换值,而无需预先知道这些值。这些替换可以和另一个被称为JNDI的库结合起来使用,JNDI是另外一个库。
JNDI允许以多种方式查找值,其中一些方式是允许你通过网络来发送请求获取数据,看起来像下面这样。
“${jndi:ldap://www.mywebsite.com/somepath}”
它将对ldap://www.mywebsite.com/somepath进行访问,甚至运行从这个地址返回的代码。 这可能是个有用的功能,但是这些替代应当只在应用程序提供的文本上发生,而不是由用户提供的文本上。在很长一段时间里,Log4j允许这些替换在可能出现在日志中的任何用户提供的文本中发生。所以用户只需对可能导致某个值被写入日志的网站执行一个操作,就可以使这个功能运行起来。因为这只是一个大型库的一个小功能(log4j有超过17.5w行代码,分布在2000个文件中),所以很长时间没有被注意到了(为什么一个将日志写入文件的库有17.5w行的代码,不幸的是这是另外一个话题了)
现在你已经知道了软件行业的秘辛了
软件难以相信的复杂建立在数百万行代码上,没有人有能力能够审计所有的代码。软件工程师依赖于一些流行的库,像log4j,如果他们想要自己开发就需要花费大量的时间。软件工程师也喜欢log4j这样的库,因为被数百万开发者使用,有这么多的使用量和这么多眼睛盯着一个软件,可以帮忙发现很多安全问题。然而,每隔一段时间就会发生这样的事情,一个被广泛使用的库,在这个库里面有一个很少被使用的功能,在这个功能里有一个微妙的缺陷,这个缺陷可能很多年都没被注意到。
现在的人会不可避免的搜索log4j类似的库,寻找类似的漏洞,然后可能会找到一些。有大量的安全漏洞在等着被发现。软件工程师社区创建了一个叫CVE的项目,CVE是Common Vulnerabilities Exposures(通用安全漏洞)的缩写,用于提醒软件工程师在他们使用的库里面发现的缺陷。即使是非常小的安全漏洞也会获得一个CVE编号,并通过大多数软件工程师使用的工具报告。如此,软件工程师就能够知道何时更新他们的库来保证用户的安全。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。