固件安全性—防止内存损坏和注入攻击
固件安全性—防止内存损坏和注入攻击
Firmware Security – Preventing memory corruption and injection attacks
构成物联网(IoT)主干的连接设备存在多个漏洞,可供黑客入侵。为了减轻这些设备中底层固件的威胁,开发人员需要熟悉大量的安全技术。本文引导开发人员了解固件保护的最佳实践,讨论了在固件中防止内存损坏漏洞和注入攻击的机制。
Firmware Security Best Practices
Covering the following recipes:
- Preventing memory-corruption vulnerabilities
- Preventing injection attacks
- Securing firmware updates
- Securing sensitive information
- Hardening embedded frameworks
- Securing third-party code and components
介绍以下配方:
防止内存损坏漏洞
防止注入攻击
保护固件更新
保护敏感信息
加固嵌入式框架
保护第三方代码和组件
介绍
嵌入式软件是物联网的核心,尽管嵌入式应用程序的安全性通常不被认为是嵌入式开发人员和物联网设备制造商的首要任务。这可能是由于缺乏安全编码知识或团队代码库之外的其挑战造成的。开发人员面临的其挑战可能包括但不限于原始设计制造商(ODM)供应链、有限的内存、较小的堆栈以及将固件更新安全地推送到端点的挑战。提供开发人员可以在嵌入式固件应用程序中使用的实用最佳实践指南。根据OWASP的嵌入式应用程序安全项目(https://www.owasp.org/index.php/OWASP_Embedded_Application_Security),嵌入式最佳实践包括:
- Buffer and stack overflow protection
- Injection attack prevention
- Securing firmware updates
- Securing sensitive information
- Identity management controls
- Embedded framework and C-based toolchain hardening
- Usage of debugging code and interfaces
- Securing device communications
- Usage of data collection and storage
- Securing third-party code
- Threat modeling
缓冲区和堆栈溢出保护
预防注入攻击
保护固件更新
保护敏感信息
身份管理控制
嵌入式框架与基于C的工具链强化
调试代码和接口的使用
保护设备通信
数据收集和存储的使用
保护第三方代码
威胁建模
将讨论前面提到的几个主要针对POSIX环境定制的最佳实践,但是这些原则的设计与平台无关。
防止内存损坏漏洞
在使用诸如C之类的低级语言时,如果开发人员没有通过编程方式正确检查和验证边界,则很有可能出现内存损坏错误。防止使用已知的危险函数和api有助于防止固件中的内存损坏漏洞。例如,已知的、不安全的C函数的非详尽列表包括:strcat、strcpy、sprintf、scanf和gets。
常见的内存损坏漏洞(如缓冲区溢出或堆溢出)可能由堆栈或堆溢出组成。这些特定的内存损坏漏洞被利用时的影响因操作系统平台而异。例如,商业RTOS平台(如QNX Neutrino)将每个进程及其堆栈与文件系统隔离开来,从而使攻击面最小化。但是,对于常见的嵌入式Linux发行版,情况可能并非如此。嵌入式Linux中的缓冲区溢出可能导致攻击者任意执行恶意代码和修改操作系统。在这个表单中,将展示工具如何帮助检测易受攻击的C函数,并提供安全控制以及防止内存损坏漏洞的最佳实践。
将使用以下工具:
Flawfinder: Flawfinder是一个免费的C/C++静态代码分析工具,报告潜在的安全漏洞。
How to do it…
通用Linux实用程序有助于通过C/C++代码文件进行搜索。尽管如此,在防止开发人员可以使用的IDE插件的内存损坏漏洞方面,有一些商用的源代码分析工具比普通实用工具做得更好。出于演示的目的,将在下面的步骤中演示如何使用grep和fluxfinder在代码文件中搜索预定义的函数易受攻击调用和规则列表。
要发现不安全的C函数,可以使用几种方法。最简单的形式是使用类似于以下示例的grepexpression:
$ grep -E '(strcpy|strcat|sprintf|strlen|memcpy|fopen|gets)' code.c
可以调整此表达式以使其更智能,或者封装在脚本中,该脚本可以在每个构建中执行,也可以在特定的基础上执行。
可以使用免费工具(如fluenfinder)通过调用flawfinder和代码段的路径来搜索易受攻击的函数,如下例所示:
$ flawfinder fuzzgoat.c
Flawfinder version 1.31, (C) 2001-2014 David A. Wheeler.
Number of rules (primarily dangerous function names) in C/C++
ruleset: 169
Examining fuzzgoat.c
FINAL RESULTS:
fuzzgoat.c:1049: [4] (buffer) strcpy:
Does not check for buffer overflows when copying to destination (CWE-120).
Consider using strcpy_s, strncpy, or strlcpy (warning, strncpy is easily misused).
fuzzgoat.c:368: [2] (buffer) memcpy:
Does not check for buffer overflows when copying to destination (CWE-120).
Make sure destination can always hold the source data.
fuzzgoat.c:401: [2] (buffer) sprintf:
Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length.
<SNIP>
fuzzgoat.c:1036: [2] (buffer) strcpy:
Does not check for buffer overflows when copying to destination (CWE-120).
Consider using strcpy_s, strncpy, or strlcpy (warning, strncpy is easily misused). Risk is low because the source is a constant string.
fuzzgoat.c:1041: [2] (buffer) sprintf:
Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length.
fuzzgoat.c:1051: [2] (buffer) strcpy:
Does not check for buffer overflows when copying to destination (CWE-120).
Consider using strcpy_s, strncpy, or strlcpy (warning, strncpy is easily misused). Risk is low because the source is a constant string.
ANALYSIS SUMMARY:
Hits = 24
Lines analyzed = 1082 in approximately 0.02 seconds (59316 lines/second)
Physical Source Lines of Code (SLOC) = 765 Hits@level = [0] 0 [1] 0 [2] 23 [3] 0 [4] 1 [5] 0
Hits@level+ = [0+] 24 [1+] 24 [2+] 24 [3+] 1 [4+] 1 [5+] 0
Hits/KSLOC@level+ = [0+] 31.3725 [1+] 31.3725 [2+] 31.3725 [3+]
1.30719 [4+] 1.30719 [5+] 0
Minimum risk level = 1
Not every hit is necessarily a security vulnerability.
There may be other security vulnerabilities; review your code!
See 'Secure Programming for Linux and Unix HOWTO' (http://www.dwheeler.com/secure-programs) for more information.
在发现正在使用的易受攻击的C函数时,必须包含安全的替代方法。例如,以下易受攻击的代码使用不安全的gets()函数,该函数不检查缓冲区长度:
#include
int main () {
char userid[8];
int allow = 0;
printf external link("Enter your userID, please: ");
gets(userid);
if (grantAccess(userid)) {
allow = 1;
}
if (allow != 0) {
privilegedAction();
}
return 0;
}
用户标识可以使用超过8个的任意数量的字符溢出,例如带有自定义执行函数的缓冲区溢出漏洞利用(BoF)负载。为了减少缓冲区溢出,可以使用fgets()函数作为一个安全的替代方法。以下示例代码演示如何安全地使用fgets()并正确分配内存:
#include <stdio.h>
#include <stdlib.h>
#define LENGTH 8
int main () {
char* userid, *nlptr;
int allow = 0;
userid = malloc(LENGTH * sizeof(*userid));
if (!userid)
return EXIT_FAILURE;
printf external link("Enter your userid, please: ");
fgets(userid,LENGTH, stdin);
nlptr = strchr(userid, '\n');
if (nlptr) *nlptr = '\0';
if (grantAccess(userid)) {
allow = 1;
}
if (allow != 0) {
priviledgedAction();
}
free(userid);
return 0;
}
同样的缓解也可以用于其安全的替代函数,如snprintf()、strlcpy()和strlcat()。根据操作系统平台的不同,一些安全的替代方案可能不可用。执行自己的研究以确定特定架构和平台的安全替代方案非常重要。英特尔创建了一个名为safestringlib的开源跨平台库,以防止使用这些不安全的禁用函数;使用另一个安全替换函数。有关safestringlib的详细信息,请访问GitHub页面:https://github.com/01org/safestringlib。
其内存安全控件可用于防止内存损坏漏洞,例如:
请使用安全编译器标志,例如-fPIE, -fstack-protector-all, -Wl,- z,noexecstack, -Wl,-z,noexecheap和其可能取决于特定编译器版本的代码。
首选片上系统(SoC)和微控制器(MCU)包含内存管理单元(MMU)。MMU隔离线程和进程,以在内存漏洞被利用时减少攻击面。
首选系统芯片(SoC)和微控制器(MCU)包含内存保护单元(MPU)。MPU强制内存和独立进程的访问规则,以及强制特权规则。
如果没有可用的MMU或MPU,则使用已知位监视堆栈,通过确定堆栈中有多少不再包含已知位来监视堆栈的消耗量。
使用后要注意缓冲区和空闲缓冲区的位置是什么。
利用地址空间布局随机化(ASLR)和其堆栈控件攻击内存漏洞确实需要攻击者付出大量努力才能利用。不过,在某些情况下还是有可能的。确保代码具有弹性,并对存储在内存中的数据采用深度防御的方法,将有助于嵌入式设备的安全态势。
另请参见
- For further secure memory management guidelines, reference Carnegie Mellon’s Secure CERT C Coding Standard (https://www.securecoding.cert.org/confluence/display/c/SEI+CERT+C+Coding+Standard).
- For further secure memory management guidelines, reference Carnegie Mellon’s Secure CERT C++ Coding Standard (https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=637)
Preventing injection attacks
注入攻击是任何web应用程序的主要漏洞之一,尤其是在物联网系统中。事实上,自2010年以来,injection一直被评为OWASP前10名的前2名。有许多类型的注入攻击,如操作系统(OS)命令注入、跨站点脚本(例如,JavaScript注入)、SQL注入、日志注入,以及其类型的注入,如表达式语言注入。在物联网和嵌入式系统中,最常见的注入攻击类型是操作系统命令注入;当应用程序接受不可信的用户输入并将该值传递给执行shell命令时,没有输入验证或正确的转义和跨站点脚本(XSS)。这个方法将向展示如何通过确保所有不可信的数据和用户输入都经过验证、清理并使用其安全功能来减轻命令注入攻击。
How to do it…
当物联网设备运行时,命令注入漏洞不难测试静态和动态。固件可以调用system()、exec()和类似的变量来执行OS命令,或者调用一个外部脚本,该脚本从解释语言(如Lua)运行OS调用。命令注入漏洞也可能由缓冲区溢出引起。下面的步骤和示例显示了易受命令注入攻击的代码,以及如何减轻命令注入的影响。之后,将列出常见的安全控制,以防止常见的注入攻击。
下面的代码片段调用dangerous system() C函数来删除home中的.cfg文件。如果攻击者能够控制该函数,则可以连接后续的shell命令来执行未经授权的操作。此外,攻击者还可以操纵环境变量来删除以.cfg结尾的任何文件:
#include <stdlib.h>
void func(void) {
system("rm ~/.cfg");
}
为了减轻前面的易受攻击的代码,将使用unlink()函数而不是system()。unlink()函数不易受到符号链接和命令注入攻击的影响。函数的作用是:删除符号链接,不影响由符号链接内容命名的文件或目录。这降低了unlink()函数对symlink攻击的敏感度,但是不能完全阻止symlink攻击;如果命名目录相同,也可以删除。unlink()函数可以阻止命令注入攻击,应该使用类似的上下文函数,而不是执行操作系统调用:
#include <pwd.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
void func(void) {
const char *file_format = "%s/.cfg";
size_t len;
char *pathname;
struct passwd *pwd;
pwd = getpwuid(getuid());
if (pwd == NULL) {
/* Handle error */
}
len = strlen(pwd->pw_dir) + strlen(file_format) + 1;
pathname = (char *)malloc(len);
if (NULL == pathname) {
/* Handle error */
}
int r = snprintf(pathname, len, file_format, pwd->pw_dir);
if (r < 0 || r >= len) {
/* Handle error */
}
if (unlink(pathname) != 0) {
/* Handle error */
}
free(pathname);
}
还有几种其方法可以减轻注入攻击。以下是预防注入攻击的常见最佳实践和控制措施的列表:
如果可能,避免直接调用操作系统调用。
如果需要,白名单接受命令并在执行之前验证输入值。
对于可能传递给操作系统(如{ 1:ping -c 5 })的用户驱动字符串numbers-to-command-strings,使用数字的查找映射来命令字符串。
在代码库上执行静态代码分析,并在语言使用操作系统命令时发出警报,例如操作系统().
将所有用户输入视为不受信任的,并且输出对呈现给用户的数据进行编码字符。(for example, Convert & to &, Convert < to <, Convert > to >, and so on.)
对于XSS,使用HTTP响应头,如X-XSS-Protection and Content- Security-Policy,并配置适当的指令。
确保在生产固件版本上禁用了带有命令执行的调试接口(例如,http://example.com/command.php).
在生产环境中使用固件之前,前面提到的控件总是需要测试。通过注入攻击,设备和用户面临被攻击者和胭脂设备接管的风险。