了解sprintf(...)的危险

时间:2010-09-07 21:26:25

标签: c++ printf

OWASP说:

  

“C库函数,如strcpy   (),strcat(),sprintf()和vsprintf   ()对空终止字符串进行操作   并且不进行边界检查。“

sprintf 将格式化数据写入字符串     int sprintf(char * str,const char * format,...);

示例:

sprintf(str, "%s", message); // assume declaration and 
                             // initialization of variables

如果我理解OWASP的评论,那么使用sprintf的危险就是那个

1)如果消息的长度> str 的长度,有一个缓冲区溢出

2)如果消息没有以\0终止,则消息可能会被复制到 str 之外的内存中消息的地址,导致缓冲区溢出

请确认/否认。感谢

8 个答案:

答案 0 :(得分:22)

你对这两个问题都是正确的,尽管它们实际上都是同一个问题(访问超出数组边界的数据)。

第一个问题的解决方案是使用snprintf,它接受​​缓冲区大小作为参数。

第二个问题的解决方案是给s[n]printf一个最大长度参数。例如:

char buffer[128];

snprintf(buffer, sizeof(buffer), "This is a %.4s\n", "testGARBAGE DATA");

// strcmp(buffer, "This is a test\n") == 0

如果您想存储整个字符串(例如,sizeof(buffer)太小),请运行snprintf两次:

// Behaviour is different in SUSv2; see
// "conforming to" section of man 3 sprintf

int length = snprintf(NULL, 0, "This is a %.4s\n", "testGARBAGE DATA");

++length;           // +1 for null terminator
char *buffer = malloc(length);

snprintf(buffer, length, "This is a %.4s\n", "testGARBAGE DATA");

(您可以使用va将其放入函数中。)

答案 1 :(得分:10)

你的两个断言都是正确的。

还有一个未提及的问题。没有类型检查参数。如果格式字符串和参数不匹配,可能会导致未定义和不良行为。例如:

char buf[1024] = {0};
float f = 42.0f;
sprintf(buf, "%s", f);  // `f` isn't a string.  the sun may explode here

这对于调试来说尤其令人讨厌。

所有这些导致许多C ++开发人员得出的结论是,你永远不应该使用sprintf及其兄弟。实际上,您可以使用一些设施来避免上述所有问题。其中一个是内置的语言:

#include <sstream>
#include <string>

// ...

float f = 42.0f;

stringstream ss;
ss << f;
string s = ss.str();

...对于那些像我一样仍然喜欢使用sprintf的人来说,另一个受欢迎的选择来自boost Format libraries

#include <string>
#include <boost\format.hpp>

// ...

float f = 42.0f;
string s = (boost::format("%1%") %f).str();

你应该采用“从不使用sprintf”的咒语吗?自行决定。这通常是工作的最佳工具,取决于你正在做什么,sprintf可能就是这样。

答案 2 :(得分:4)

是的,主要是缓冲区溢出问题。然而,由于缓冲区溢出是系统破解者用来规避软件或系统安全性的主要攻击媒介,因此这些都是非常严肃的业务。如果您将这样的内容暴露给用户输入,那么您很有可能将密钥交给您的程序(甚至是您的计算机本身)给破解者。

从OWASP的角度来看,让我们假装我们正在编写一个Web服务器,我们使用sprintf来解析浏览器传递给我们的输入。

现在让我们假设某个恶意的人通过我们的网络浏览器,这个字符串比我们选择的缓冲区大得多。他的额外数据将覆盖附近的数据。如果他足够大,他的一些数据将被复制到网络服务器的指令而不是数据上。现在他可以让我们的网络服务器执行他的代码

答案 3 :(得分:3)

您的2个编号结论是正确的,但不完整。

还有一个风险:

char* format = 0;
char buf[128];
sprintf(buf, format, "hello");

这里,format不是以NULL结尾的。 sprintf()也没有检查过。

答案 4 :(得分:1)

您的解释似乎是正确的。但是,你的情况#2并不是真正的缓冲区溢出。它更多的是内存访问冲突。这只是术语,但它仍然是一个主要问题。

答案 5 :(得分:1)

当sprintf函数与某些格式说明符一起使用时,会产生两种类型的安全风险:(1)写入内存不应该; (2)阅读记忆不应该。如果snprintf与一个与缓冲区匹配的size参数一起使用,它将不会写任何不应该写入的内容。根据参数,它仍然可以读取它不应该的东西。根据操作环境以及程序正在执行的操作,不正确读取的危险可能会或可能不会比不正确的写入更严重。

答案 6 :(得分:0)

记住sprintf()在每个字符串的末尾添加ASCII 0字符作为字符串终止符非常重要。因此,目标缓冲区必须至少有n + 1个字节(要打印单词“HELLO”,需要6个字节的缓冲区,不是5个)

在下面的示例中,它可能不是很明显,但在2字节目标缓冲区中,第二个字节将被ASCII 0字符覆盖。如果仅为缓冲区分配了1个字节,则会导致缓冲区溢出。

char buf[3] = {'1', '2'};
int n = sprintf(buf, "A");

另请注意,sprintf()的返回值不包括空终止字符。在上面的示例中,写入了2个字节,但函数返回“1”。

在下面的例子中,类成员变量'i'的第一个字节将被sprintf()部分覆盖(在32位系统上)。

struct S
{
    char buf[4];
    int i;
};


int main()
{
    struct S s = { };
    s.i = 12345;

    int num = sprintf(s.buf, "ABCD");
    // The value of s.i is NOT 12345 anymore !

    return 0;
}

答案 7 :(得分:0)

我几乎已经举了一个小例子,说明如何摆脱 sprintf 的缓冲区大小声明(当然,如果您愿意!),而没有 snprintf 参与....

注意:这是一个APPEND / CONCATENATION示例,请查看here