C++ 了解sprintf的危险(…)

C++ 了解sprintf的危险(…),c++,printf,C++,Printf,说: “C库函数,如strcpy ()、strcat()、sprintf()和vsprintf ()对以null结尾的字符串进行操作 并执行无边界检查。” 将格式化数据写入字符串 intsprintf(字符*str,常量字符*格式,…) 例如: sprintf(str, "%s", message); // assume declaration and // initialization of variables 如果我理解OWASP

说:

“C库函数,如strcpy ()、strcat()、sprintf()和vsprintf ()对以null结尾的字符串进行操作 并执行无边界检查。”

将格式化数据写入字符串 intsprintf(字符*str,常量字符*格式,…)

例如:

sprintf(str, "%s", message); // assume declaration and 
                             // initialization of variables
如果我理解OWASP的评论,那么使用sprintf的危险在于

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

2) 如果消息未以
\0
终止为空,则消息可能被复制到str中,超出消息的内存地址,从而导致缓冲区溢出


请确认/否认。谢谢

您在这两个问题上都是正确的,尽管它们实际上都是同一个问题(即访问数组边界以外的数据)

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

第二个问题的解决方案是为
snprintf
提供一个最大长度参数。例如:

char buffer[128];

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

// std::strcmp(buffer, "This is a test\n") == 0
char buf[1024] = {0};
float f = 42.0f;
sprintf(buf, "%s", f);  // `f` isn't a string.  the sun may explode here
如果要存储整个字符串(例如,如果
sizeof(buffer)
太小),请运行
snprintf
两次:

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

++length;           // +1 for null terminator
char *buffer = new char[length];

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

(您可能可以使用
va
或可变模板将其放入函数中。)

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

是的,这主要是缓冲区溢出的问题。然而,这些是当今相当严重的事务,因为缓冲区溢出是系统破解者用来绕过软件或系统安全的主要攻击向量。如果你把这样的东西暴露给用户输入,你很有可能把程序(甚至是电脑本身)的钥匙交给破解者

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


现在,让我们假设有恶意的人通过我们的web浏览器传递了一个比我们选择的缓冲区大得多的字符串。他的额外数据将覆盖附近的数据。如果他把它做得足够大,他的一些数据将通过Web服务器的指令而不是数据进行复制。现在他可以让我们的Web服务器执行他的代码了,你的两个断言都是正确的

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

char buffer[128];

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

// std::strcmp(buffer, "This is a test\n") == 0
char buf[1024] = {0};
float f = 42.0f;
sprintf(buf, "%s", f);  // `f` isn't a string.  the sun may explode here
这对于调试来说可能特别讨厌

所有这些都引导了许多C++开发者得出结论:你永远不应该使用<代码> SeavtF及其兄弟。事实上,您可以使用一些设施来避免上述所有问题。一个是streams,它是语言的核心:

#include <sstream>
#include <string>

// ...

float f = 42.0f;

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

你应该接受“永远不要使用sprintf”的咒语吗?你自己决定吧。通常有一个最好的工具来完成这项工作,这取决于你在做什么,
sprintf
可能就是它。

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

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

还有一个额外的风险:

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

这里,
格式
不是以NULL结尾的
sprintf()
也不检查。

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

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

char buf[3] = {'1', '2'};
int n = sprintf(buf, "A");
还要注意,sprintf()的返回值不包括null终止字符。在上面的示例中,写入了2个字节,但函数返回“1”

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


我已经给出了一个小例子,说明了如何去掉sprintf的缓冲区大小声明(当然,如果您愿意的话!)和nosnprintf的解决方案


注意:这是一个附加/连接示例,看看当
snprintf
只解决第一个问题时,它们是如何“都是同一个问题”的?@Rob Kennedy,它们在不同的地方是同一个问题。对不起,不清楚。你是对的
snprintf
盲目替换
sprintf
只会修复第一个。snprintf还会截断传递的参数,解决第2个问题(如果消息不是以NULL结尾的,则只会写入前N个字符)。@dash tom bang,虽然这可能是真的,但它仍然可能会踏入未定义行为的领域。但是@dash,如果N是巨大的,并且
message
应该是短的,但没有空终止符,那么N的巨大值不能保护您免受由于读取太远而导致的访问冲突,也不能保护您免受泄露本应保密的内存内容的侵害。此外,还有程序员错误,例如
sprintf(str,message)
或类似文件是