1. 什么是格式化字符串漏洞?
格式化字符串漏洞是一种常见的安全漏洞,它通过格式化串攻击向应用程序注入恶意代码或获取敏感信息。在C语言中,格式化字符串是一种语法,用于根据特定格式打印或读取数据到内存中。格式化字符串漏洞的出现通常是因为程序在处理格式化字符串时,没有正确检查用户输入的参数。
1.1 漏洞形成的原因:
模板字符串中使用的格式化符号(如:%s、%n、%x、%d等)要求格式化函数知道默认数据的存储地址、数据类型和格式化字符串中数据类型的数据长度。如果这些条件中任何一个没有得到满足,程序可能会导致格式化串漏洞。例如:如果数据类型与格式化字符串中指定的数据类型不一致,或者格式化符号使用范围错误,都可能导致漏洞的产生。
缓冲区溢出是一种常见的格式化字符串漏洞。攻击者可以使用缓冲区溢出来覆盖我们所希望访问的地址的内容,从而获得敏感信息或者将控制权转交给恶意代码。
1.2 示例:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char c[100];
fgets(c,sizeof(c),stdin);
printf(c);
return 0;
}
上面的代码中,当用户输入的格式化字符串中使用了%s格式化符号,并且传递了超过100个字符的字符串,将会出现缓冲区溢出的问题。
2. C语言中避免格式化字符串漏洞的预防措施
2.1 根据指定的格式控制变量:
在使用格式化函数时,开发人员应该根据具体的格式来控制变量,这样可以在编译期间检查格式,防止格式字符串注入攻击。
// 防止字符串格式化注入漏洞
char buffer[20];
snprintf(buffer, 20, "%s", argv[1]);
printf("Hello world %s\n",buffer);
2.2 变量格式化的类型应该明确指定:
当使用了特定的变量时,应该确保变量是特定类型。例如使用printf函数进行字符串格式化输出时,参数类型应该明确定义所格式化的字符串。
// 先检查一个格式控制字符串,然后再传递它
int fmtscanf(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
int rval = vfscanf(stdin, fmt, ap);
va_end(ap);
return rval;
}
2.3 使用格式说明符:
在使用格式化函数时,需要使用格式说明符来指定变量类型。这样有利于编译器检查变量类型是否与格式指定符所指定的类型匹配,从而提高了程序的安全性。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char buffer[1024];
snprintf(buffer, sizeof(buffer), "Test: %d, %s", 123, "test");
printf("%s", buffer);
return EXIT_SUCCESS;
}
2.4 使用限定符:
在使用格式化函数时,应该尽可能地使用限定符。限定符的作用是指定变量的长度,如:%4f、%8d等,这样可以大大减少格式化串漏洞的发生。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char buffer[100];
snprintf(buffer, sizeof(buffer), "username=%s\nemail=%s\n", argv[1],argv[2]);
fprintf(stderr, buffer);
return 0;
}
2.5 使用函数的返回值:
格式化函数的返回值可以防范攻击者的恶意输入。比如:在读取用户输入之前,使用格式化函数来计算格式化串的长度,然后再使用接收器确保接收缓冲区有足够的空间存储接收到的格式化串。
int print(char *format_string, ...)
{
char buffer[1024];
char *fmt;
int len, printed;
printf("[%d] ", getpid());
va_start(ap, format_string);
vsnprintf(buffer, 1023, format_string, ap);
va_end(ap);
len = strlen(buffer);
do {
fmt = strchr(buffer, '%');
if (fmt != NULL) {
printed = fmt - buffer;
fwrite(buffer, 1, printed, stdout);
buffer += printed;
len -= printed;
fmt++;
printf("%%");
}
} while (fmt != NULL);
fwrite(buffer, 1, len, stdout);
return 0;
}
3. 结论
本文介绍了格式化字符串漏洞的产生原因和避免漏洞的方法。使用正确的输入验证和数据检查策略将是我们避免格式化字符串漏洞的最佳方法。我们应该在向格式化函数传递参数时,非常小心,同时避免使用易受攻击的函数。