了解 sprintf(...) 的危险

发布于 2024-09-18 07:39:56 字数 907 浏览 6 评论 0原文

OWASP 说:

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

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

示例:

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

如果我理解 OWASP 的注释,那么使用 sprintf 的危险在于

1) if message's length > str 的长度,存在缓冲区溢出

,并且

2) 如果 message 不以 \0 空终止,则 message< /strong> 可能会被复制到超出 message 内存地址的 str 中,从而导致缓冲区溢出,

请确认/拒绝。谢谢

OWASP says:

"C library functions such as strcpy
(), strcat (), sprintf () and vsprintf
() operate on null terminated strings
and perform no bounds checking."

sprintf writes formatted data to string
int sprintf ( char * str, const char * format, ... );

Example:

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

If I understand OWASP's comment, then the dangers of using sprintf are that

1) if message's length > str's length, there's a buffer overflow

and

2) if message does not null-terminate with \0, then message could get copied into str beyond the memory address of message, causing a buffer overflow

Please confirm/deny. Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

归途 2024-09-25 07:39:56

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

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

第二个问题的解决方案是为 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

如果您想存储整个字符串(例如,在 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 或可变参数模板的函数。)

You're correct on both problems, though they're really both the same problem (which is accessing data beyond the boundaries of an array).

A solution to your first problem is to instead use std::snprintf, which accepts a buffer size as an argument.

A solution to your second problem is to give a maximum length argument to snprintf. For example:

char buffer[128];

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

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

If you want to store the entire string (e.g. in the case sizeof(buffer) is too small), run snprintf twice:

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");

(You can probably fit this into a function using va or variadic templates.)

只等公子 2024-09-25 07:39:56

你的两种说法都是正确的。

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

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 格式库

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

// ...

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

您应该采用“永远不要使用 sprintf”的口头禅吗?自己决定吧。通常有一个最适合这项工作的工具,根据您正在做的事情,sprintf 可能就是它。

Both of your assertions are correct.

There's an additional problem not mentioned. There is no type checking on the parameters. If you mismatch the format string and the parameters, undefined and undesirable behavior could result. For example:

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

This can be particularly nasty to debug.

All of the above lead many C++ developers to the conclusion that you should never use sprintf and its brethren. Indeed, there are facilities you can use to avoid all of the above problems. One, streams, is built right in to the language:

#include <sstream>
#include <string>

// ...

float f = 42.0f;

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

...and another popular choice for those who, like me, still prefer to use sprintf comes from the boost Format libraries:

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

// ...

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

Should you adopt the "never use sprintf" mantra? Decide for yourself. There's usually a best tool for the job and depending on what you're doing, sprintf just might be it.

恬淡成诗 2024-09-25 07:39:56

是的,这主要是缓冲区溢出的问题。然而,这些现在是相当严重的事情,因为缓冲区溢出是系统破解者用来规避软件或系统安全的主要攻击媒介。如果您将类似的内容暴露给用户输入,那么您很有可能将程序(甚至计算机本身)的密钥交给了破解者。

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

现在,假设有人恶意向我们的网络浏览器传递了一个远大于我们选择的缓冲区所能容纳的字符串。他的额外数据将覆盖附近的数据。如果他将其设置得足够大,他的一些数据将通过网络服务器的指令而不是其数据进行复制。现在他可以让我们的网络服务器执行他的代码

Yes, it is mostly a matter of buffer overflows. However, those are quite serious business nowdays, since buffer overflows are the prime attack vector used by system crackers to circumvent software or system security. If you expose something like this to user input, there's a very good chance you are handing the keys to your program (or even your computer itself) to the crackers.

From OWASP's perspective, let's pretend we are writing a web server, and we use sprintf to parse the input that a browser passes us.

Now let's suppose someone malicious out there passes our web browser a string far larger than will fit in the buffer we chose. His extra data will instead overwrite nearby data. If he makes it large enough, some of his data will get copied over the webserver's instructions rather than its data. Now he can get our webserver to execute his code.

标点 2024-09-25 07:39:56

你的 2 个结论是正确的,但不完整。

还有一个额外的风险:

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

这里,format 不是 NULL 终止的。 sprintf() 也不检查这一点。

Your 2 numbered conclusions are correct, but incomplete.

There is an additional risk:

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

Here, format is not NULL-terminated. sprintf() doesn't check that either.

倾其所爱 2024-09-25 07:39:56

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

Your interpretation seems to be correct. However, your case #2 isn't really a buffer overflow. It's more of a memory access violation. That's just terminology though, it's still a major problem.

梦与时光遇 2024-09-25 07:39:56

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

The sprintf function, when used with certain format specifiers, poses two types of security risk: (1) writing memory it shouldn't; (2) reading memory it shouldn't. If snprintf is used with a size parameter that matches the buffer, it won't write anything it shouldn't. Depending upon the parameters, it may still read stuff it shouldn't. Depending upon the operating environment and what else a program is doing, the danger from improper reads may or may not be less severe than that from improper writes.

酒绊 2024-09-25 07:39:56

记住 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;
}

It is very important to remember that sprintf() adds the ASCII 0 character as string terminator at the end of each string. Therefore, the destination buffer must have at least n+1 bytes (To print the word "HELLO", a 6-byte buffer is required, NOT 5)

In the example below, it may not be obvious, but in the 2-byte destination buffer, the second byte will be overwritten by ASCII 0 character. If only 1 byte was allocated for the buffer, this would cause buffer overrun.

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

Also note that the return value of sprintf() does NOT include the null-terminating character. In the example above, 2 bytes were written, but the function returns '1'.

In the example below, the first byte of class member variable 'i' would be partially overwritten by sprintf() (on a 32-bit system).

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;
}
带上头具痛哭 2024-09-25 07:39:56

我几乎已经阐述了一个小例子,如何摆脱 sprintf 的缓冲区大小声明(当然,如果您打算这样做!)并且不涉及 snprintf 。 ...

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

I pretty much have stated a small example how you could get rid of the buffer size declaration for the sprintf (if you intended to, of course!) and no snprintf envolved ....

Note: This is an APPEND/CONCATENATION example, take a look at here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文