×
嵌入式 > 技术百科 > 详情

常见的嵌入式漏洞 第一部分:代码注入

发布时间:2024-05-08 发布时间:
|

由于许多嵌入式系统历来没有连接到网络,又或者因为人们预期设备将在受信任的环境中运行,其出现安全漏洞的风险相对较低。然而如今物联网时代已经到来,这种情况也随之改变了。

目前嵌入式设备需要高水平的网络连接,这一点需要我们时刻谨记。这些设备将被部署在敌对的环境中,在这里神通广大的攻击者们正信誓旦旦地准备利用这些源代码级的安全漏洞。嵌入式系统软件的开发人员最好能够理解不同类型的安全漏洞,以便能够采取措施抵御黑客的入侵。

这将会是讨论攻击者如何利用应用程序源代码缺陷的系列文章。而本文是这个系列的首篇文章,着重讨论代码注入这一漏洞。

什么是代码注入?

代码注入”这一术语是指一个常规数据输入到程序中制作成包含代码,并且这个程序可以诱使操作该代码。对于攻击者而言,代码注入缺陷就是至宝,因为他打开了一扇门,使得攻击者能够劫持业已存在的进程,然后可以执行任何和原始进程具有同等权限的代码。

在许多嵌入式系统中,一个进程想要得以运转,需要得到最高的权限,因此一个成功的代码注入攻击可以使攻击者完全控制整个机器。一旦进入,便可能窃取数据,造成设备故障,感染蠕虫病毒,甚至是永远无法操作。

在过去的几十年里,许多著名的计算机安全事故都是由于代码注入漏洞引起的。例如,某些类型的缓冲超限可以采用代码注入。同样,SQL注入缺陷也属于同类。在这里我们可以举出一些比较好的技术说明和例子。

正如上面所提到的,代码注入漏洞的关键方面如下:

•程序从输入通道读取数据。

•程序将数据视为代码并对其进行解释。

大多数情况下,一个程序把数据执行为代码是不寻常的。然而,数据被用来架构一个故意执行的对象,这是很常见的。

例如,一个天真的程序员想要发出一个SQL查询(如nameString)可能会读取来自用户的一个字符串,将其转化为一个变量,然后架构一个字符串中的查询,如下所示:

"SELECT * FROM Names WHERE Id = " + nameString

如果用户输入一个形式合法的名字,那么就万事大吉了。然而,恶意用户可以通过输入一个包含SQL语句的字符串"x;DROP TABLE Users;"来利用这个漏洞,它执行的SQL查询:

"SELECT * FROM Names WHERE Id = x; DROP TABLE Users;"

最终的结果是,其中的一个表从数据库中被删除。

在上面的例子中,“代码”是注入到SQL解释器中的SQL查询。一个嵌入式系统不可能包含一个SQL解释器(虽然也有特殊情况),但是大量的代码注入漏洞的例子更可能出现在嵌入式代码中。以C语言为例,就很容易出现格式串漏洞。


格式串漏洞

几乎所有的C++程序员都熟悉打印系列函数。粗略地说,这些都需要一个格式字符串后面跟随一列其他的参数,这个格式字符串被解释为一组将其余的参数绘制为字符串的指令。

用于指定格式的语言是相当复杂,甚至可能是非常棘手的。大多数用户都熟悉写最常用的说明符的方法,如字符串,小数,和浮点数(%S,%D,%F),但很少有人意识到,一些格式字符串指令被严重滥用了。

在我解释代码注入漏洞如何产生之前,我要指出打印函数最常见的滥用情况。很不幸的是,一些程序员打印字符串的习惯是这样的:

printf(str);

虽然大多数时候,这样都会收到预期的效果,但它仍然是错误的。因为打印函数的第一个参数将被解释为一个格式字符串。因此,如果str包含任何格式字符串,它们都会被这样解释。

例如,如果str包含“%D”,它会解释参数列表中的下一个值,解释成一个整数输出出来,并将其转换为字符串。在这种情况下,没有更多的参数,但是实现并不知道。它所知道的是,一些实现的参数被推入到栈。因为在C运行时没有机制使它知道有没有更多的参数,输出将只挑选出现在栈上的下一个项目,解释成一个整数,并打印出来。可以很容易地看出,这可以用来从堆栈输出任意数量的任意量。比如说,如果str包含“%d %d %d %d”,然后将输出堆栈中的接下来四个字的值。

这是代码注入本身的安全漏洞,但可以得出的结论是唯一潜在的危害是它被用于获取堆栈中的数据。如果其中包含敏感数据,如密码或证书密钥等,这是非常不利的。但是情况也可能更糟糕,因为攻击者可以写入任意的内存地址。

格式化指定符“%N”使这一切成为可能。通常情况下,相应的参数是一个指向整数的指针。因为格式字符串被解释用于构建结果字符串,当%n被看到的时候,目前为止已写入的字节被放置在由该指针指定的存储器位置。例如,下面的打印函数完成后,i=4:

printf("1234%n", &i);

要记住,如果输出的实际参数比格式指定符少,打印函数会把出现在堆栈的任何数据解释成参数。因此,如果攻击者可以控制格式字符串,那么他实际上可以在堆栈位置写入任意值。堆栈是局部变量所在的位置,从而使改变它们的值成为可能。如果这些变量是指针,则这会给攻击者提供一个平台,使他们到达其它存储器中其他非堆栈的地址。

攻击者真正关注的是那些可以使他们控制程序执行的目标。如果其中一个局部变量是一个函数指针,则通过该指针的后续调用可以成为攻击者选择的代码。或者,攻击可以将指令地址重写到可以控制转移函数的地方。


避免代码注入

通过设计来避免代码注入是最好的方法。如果你可以用一个语言,使这些漏洞不能显示出来,通过结构使代码免疫,这是最好的。或者设计代码禁止可能导致这类问题的接口出现。

不幸的是,在嵌入式系统中,这些选择并不总是可行的。尽管C是诸多漏洞,高度危险的语言,它仍然是许多结构的首选语言。鉴于此,开发人员应该知道其他的避免方法。

有两条防止代码注入漏洞的黄金法则:

•如果能避免,不要把数据解释成代码

•如果无法避免,一定要在使用该数据前进行验证

为了避免格式串漏洞,第一条规则是最合适的。你可以将代码写成如下格式:

printf("%s", str);

通过这种方式,str的内容仅仅被视为数据。这是一件非常简单的事情,只要你可以很容易地找到所有应该被改变过的地方就可以,然而对于大项目来说这是非常棘手的事,特别是在当你使用的是第三方代码库的情况下。

为了避免上面例子中的SQL注入,你可以检查nameString,以确保它是没有空格或分号的单个字。

动态分析

对于这类漏洞进行测试可能会非常困难。即使达到非常高的代码覆盖率,测试仍可能会失败而引发这些问题。一般,测试用例在正常情况下被构造用于验证功能。当测试安全漏洞时,测试人员必须换位思考,将自己想象成采取态度坚决而行事巧妙的攻击者。模糊测试这样的技术对于搜索特定类型的代码注入是非常有用的,比如在一些把数据用作格式字符串的地方。但是,该技术的可靠性尚不稳定。

静态分析

静态分析在发现代码注入漏洞方面非常有效。需要注意的是初代静态分析工具(如lint及它的后续产品)在发现这些漏洞方面是比较薄弱,因为想要得到精确的结果,需要着眼于整个程序和路径敏感的分析。近几年出现的先进的静态分析工具效率更高。这些工具的供应商在判断哪些接口有害方面已经积累了大量的经验,并开发了寻找什么,以及如何使有效运作的知识库。

我们所用的关键技术被称为“污点分析”,有时也叫做“危险信息流动分析”。这些工具首先要识别存在潜在风险的数据源,并跟踪这些信息如何流过代码而无需得到验证。这是允许你实现可视化流程的最好工具。


在IRC服务器程序中,发现了一个命令注入漏洞

为了说明其有效性,举出下面的例子。在所实施的互联网中继聊天(IRC)服务器程序中,发现了一个命令注入漏洞(见图)。这是来自于网络的未净化数据输入到系统()函数,通过后所在的存储单元。截图来自于CodeSonar。

在这种情况下,系统()调用在宏中被埋葬了两层深,这个宏名义上负责一些良性的日志活动。然而,一旦宏被扩展,很显然这个表面上看起来无害的东西,实际上是被恶意和有意插入的一个后门。当部署之后,它可以允许任何知道代码的人在主机上获取与服务器相同的权限并运行代码。

结论

代码注入漏洞是一个非常危险的安全问题,因为他们可以允许攻击者破坏程序,甚至完全控制计算机。开发人员要想确保他们的嵌入式代码能够在一个潜在的敌对网络环境中使用,应尽量在开发周期的早期消除这些漏洞。他们会发现使用传统的检测技术是非常困难的,所以我们强烈推荐进行严格的代码检查并采用先进的静态分析工具。


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
分布式光纤温度传感器(DTS)系统的应用