Iu *_*Tub 99 c security printf format-specifiers puts
在我正在阅读的一本书中,写printf了一个单一的参数(没有转换说明符)被弃用了.它建议替代
printf("Hello World!");
Run Code Online (Sandbox Code Playgroud)
同
puts("Hello World!");
Run Code Online (Sandbox Code Playgroud)
要么
printf("%s", "Hello World!");
Run Code Online (Sandbox Code Playgroud)
谁能告诉我为什么printf("Hello World!");是错的?书中写道它包含漏洞.这些漏洞是什么?
Jab*_*cky 119
printf("Hello World!"); 恕我直言不容易受到影响,但考虑一下
const char *str;
...
printf(str);
Run Code Online (Sandbox Code Playgroud)
如果str恰好指向包含%s格式说明符的字符串,则程序将显示未定义的行为(主要是崩溃),而puts(str)只是按原样显示字符串.
例:
printf("%s"); //undefined behaviour (mostly crash)
puts("%s"); // displays "%s"
Run Code Online (Sandbox Code Playgroud)
oua*_*uah 73
printf("Hello world");
很好,没有安全漏洞.
问题在于:
printf(p);
Run Code Online (Sandbox Code Playgroud)
where p是指向由用户控制的输入的指针.它易于 格式化字符串攻击:用户可以插入转换规范来控制程序,例如%x转储内存或%n覆盖内存.
请注意,puts("Hello world")是不是在行为等同printf("Hello world"),但到printf("Hello world\n").编译器通常足够聪明,可以优化后一个调用以替换它puts.
Lig*_*ica 32
除了其他答案,printf("Hello world! I am 50% happy today")是一个容易犯的错误,可能导致各种令人讨厌的内存问题(它是UB!).
它只是更简单,更容易和更强大,"要求"程序员在他们想要一个逐字字符串而不是其他任何东西时绝对清楚.
这就是你的printf("%s", "Hello world! I am 50% happy today")利益所在.这完全是万无一失的.
(史蒂夫,当然printf("He has %d cherries\n", ncherries)绝对不是一回事;在这种情况下,程序员不是"逐字字符串"的心态;她是"格式字符串"的心态.)
P1k*_*chu 16
我将在这里添加一些有关漏洞部分的信息.
据说因printf字符串格式漏洞而易受攻击.在您的示例中,字符串是硬编码的,它是无害的(即使从未完全建议像这样的硬编码字符串).但是指定参数的类型是一个很好的习惯.举个例子:
如果有人将格式字符串字符放在printf中而不是常规字符串中(例如,如果要打印程序stdin),printf将尽可能地在堆栈中使用.
它(并且仍然)非常习惯于利用程序来探索堆栈以访问隐藏信息或绕过身份验证.
例(C):
int main(int argc, char *argv[])
{
printf(argv[argc - 1]); // takes the first argument if it exists
}
Run Code Online (Sandbox Code Playgroud)
如果我把这个程序作为输入 "%08x %08x %08x %08x %08x\n"
printf ("%08x %08x %08x %08x %08x\n");
Run Code Online (Sandbox Code Playgroud)
这指示printf函数从堆栈中检索五个参数并将它们显示为8位填充的十六进制数字.所以可能的输出可能如下所示:
40012980 080628c4 bffff7a4 00000005 08059c04
Run Code Online (Sandbox Code Playgroud)
见这对一个更完整的解释和其他的例子.
Kon*_*itz 12
printf使用文字格式字符串调用是安全有效的,如果printf使用用户提供的格式字符串调用不安全,则存在自动警告您的工具.
最严重的攻击是printf利用%n格式说明符.与所有其他格式说明符相比,例如%d,%n实际上将值写入其中一个格式参数中提供的内存地址.这意味着攻击者可以覆盖内存,从而可能控制您的程序.维基百科
提供更多细节.
如果您printf使用文字格式字符串调用,攻击者无法潜入%n您的格式字符串,因此您是安全的.实际上,gcc会将你的调用改为printf调用puts,所以没有任何区别(通过运行来测试gcc -O3 -S).
如果printf使用用户提供的格式字符串进行调用,攻击者可能会潜入%n您的格式字符串,并控制您的程序.您的编译器通常会警告您,他的不安全,请参阅
-Wformat-security.还有更高级的工具可以确保printf即使用户提供的格式字符串也可以安全地调用,甚至可以检查是否传递了正确的参数数量和类型
printf.例如,对于Java,有Google的Error Prone
和Checker Framework.
Ste*_*mit 11
这是错误的建议.是的,如果你有一个运行时字符串要打印,
printf(str);
Run Code Online (Sandbox Code Playgroud)
非常危险,你应该经常使用
printf("%s", str);
Run Code Online (Sandbox Code Playgroud)
相反,因为通常你永远不知道是否str可能包含一个%标志.但是,如果你有一个编译时常量字符串,那么没有任何错误
printf("Hello, world!\n");
Run Code Online (Sandbox Code Playgroud)
(除此之外,这是有史以来最经典的C程序,实际上来自Genesis的C编程书.所以任何人都不赞成使用这种用法是相当异端的,而且我会有点冒犯!)
一个相当讨厌的方面printf是,即使在杂散内存读取的平台上只能造成有限(和可接受)的伤害,其中一个格式化字符%n会导致下一个参数被解释为指向可写整数的指针,并导致到目前为止输出的字符数将被存储到由此识别的变量中.我自己从来没有使用过这个功能,有时我使用轻量级的printf风格的方法,我写的只包括我实际使用的功能(并且不包括那个或类似的东西)但是接收标准的printf函数字符串从不值得信任的来源可能会暴露安全漏洞超出读取任意存储的能力.
由于没有人提到,我会添加一个关于他们表现的说明.
在正常情况下,假设没有使用编译器优化(即printf()实际调用printf()而不是fputs()),我希望printf()执行效率较低,特别是对于长字符串.这是因为printf()必须解析字符串以检查是否有任何转换说明符.
为了证实这一点,我已经进行了一些测试.测试在Ubuntu 14.04上进行,使用gcc 4.8.4.我的机器使用Intel i5 cpu.正在测试的程序如下:
#include <stdio.h>
int main() {
int count = 10000000;
while(count--) {
// either
printf("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM");
// or
fputs("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM", stdout);
}
fflush(stdout);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
两者都是用gcc -Wall -O0.编译的.时间是用来衡量的time ./a.out > /dev/null.以下是典型运行的结果(我运行了五次,所有结果都在0.002秒内).
对于printf()变体:
real 0m0.416s
user 0m0.384s
sys 0m0.033s
Run Code Online (Sandbox Code Playgroud)
对于fputs()变体:
real 0m0.297s
user 0m0.265s
sys 0m0.032s
Run Code Online (Sandbox Code Playgroud)
如果你有一个很长的字符串,这个效果会被放大.
#include <stdio.h>
#define STR "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
#define STR2 STR STR
#define STR4 STR2 STR2
#define STR8 STR4 STR4
#define STR16 STR8 STR8
#define STR32 STR16 STR16
#define STR64 STR32 STR32
#define STR128 STR64 STR64
#define STR256 STR128 STR128
#define STR512 STR256 STR256
#define STR1024 STR512 STR512
int main() {
int count = 10000000;
while(count--) {
// either
printf(STR1024);
// or
fputs(STR1024, stdout);
}
fflush(stdout);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
对于printf()变体(运行三次,实际加/减1.5秒):
real 0m39.259s
user 0m34.445s
sys 0m4.839s
Run Code Online (Sandbox Code Playgroud)
对于fputs()变体(运行三次,真正的正负0.2s):
real 0m12.726s
user 0m8.152s
sys 0m4.581s
Run Code Online (Sandbox Code Playgroud)
注意:在检查gcc生成的程序集后,我意识到gcc优化了对fputs()调用的fwrite()调用,即使是-O0.(printf()调用保持不变.)我不确定这是否会使我的测试无效,因为编译器会在编译时计算字符串长度fwrite().
printf("Hello World\n")
Run Code Online (Sandbox Code Playgroud)
自动编译为
puts("Hello World")
Run Code Online (Sandbox Code Playgroud)
您可以通过反汇编您的可执行文件来检查它:
push rbp
mov rbp,rsp
mov edi,str.Helloworld!
call dword imp.puts
mov eax,0x0
pop rbp
ret
Run Code Online (Sandbox Code Playgroud)
运用
char *variable;
...
printf(variable)
Run Code Online (Sandbox Code Playgroud)
会导致安全问题,不要那样使用printf!
因此你的书实际上是正确的,使用带有一个变量的printf已被弃用但你仍然可以使用printf("my string \n"),因为它会自动成为puts
对于gcc,可以启用用于检查printf()和的特定警告scanf()。
gcc文档指出:
-Wformat包含在中-Wall。在过去的检查中,选择格式的某些方面更多的控制-Wformat-y2k,-Wno-format-extra-args,-Wno-format-zero-length,-Wformat-nonliteral,-Wformat-security,和-Wformat=2可用,但不包括在-Wall。
将-Wformat其在中启用-Wall选项,不会使一些特殊的警告,帮助找到这些情况:
-Wformat-nonliteral 如果您没有传递字符串格式的格式说明符,则会发出警告。-Wformat-security如果您传递的字符串可能包含危险的构造,则会发出警告。这是的子集-Wformat-nonliteral。我必须承认,启用功能-Wformat-security揭示了我们代码库中的几个错误(日志记录模块,错误处理模块,xml输出模块,所有这些功能都有一些函数,如果使用参数中的%字符调用它们,它们可能会执行未定义的操作。有关信息,我们的代码库现在已有20多年的历史了,即使我们意识到了这类问题,当我们启用这些警告时,我们仍然对代码库中仍然有多少个错误感到非常惊讶。