[c] 단일 인수 (변환 지정자 없음)가있는 printf가 더 이상 사용되지 않는 이유는 무엇입니까?

내가 읽고있는 책 printf에서 단일 인수 (변환 지정자 없음)가 더 이상 사용되지 않는다고 기록되어 있습니다. 대체하는 것이 좋습니다

printf("Hello World!");

puts("Hello World!");

또는

printf("%s", "Hello World!");

누군가가 왜 printf("Hello World!");잘못된 것인지 말해 줄 수 있습니까 ? 책에 취약점이 있다고 기록되어 있습니다. 이 취약점은 무엇입니까?



답변

printf("Hello World!"); IMHO는 취약하지 않지만 다음을 고려하십시오.

const char *str;
...
printf(str);

경우 str문자열을 포함에 포인트로 발생하는 %s형식 지정, 프로그램은 반면에 정의되지 않은 동작 (주로 충돌)를 전시 할 예정 puts(str)이기 때문에 단지 문자열을 표시합니다.

예:

printf("%s");   //undefined behaviour (mostly crash)
puts("%s");     // displays "%s\n"


답변

printf("Hello world");

괜찮으며 보안 취약점이 없습니다.

문제는 다음과 같습니다.

printf(p);

여기서 p사용자에 의해 제어되는 입력에 대한 포인터이다. 그것은하는 경향이 형식 문자열 공격 : 변환 사양을 삽입 할 수 있습니다 사용자는, 예를 들어, 프로그램 제어 할 %x메모리 나 덤프 %n덮어 쓰기 메모리에 있습니다.

puts("Hello world")동작 printf("Hello world")printf("Hello world\n"). 컴파일러는 일반적으로 후자의 호출을 최적화하여 puts.


답변

다른 답변 외에도 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)는 절대적으로 같은 것이 아닙니다.이 경우 프로그래머는 “verbatim string”사고 방식이 아닙니다. 그녀는 “format string”사고 방식에 있습니다.)


답변

여기에 취약성 부분 에 대한 정보 약간 추가 하겠습니다.

printf 문자열 형식 취약점으로 인해 취약한 것으로 알려져 있습니다. 귀하의 예에서 문자열이 하드 코딩 된 경우 무해합니다 (이와 같은 하드 코딩 문자열이 완전히 권장되지 않더라도). 그러나 매개 변수의 유형을 지정하는 것은 좋은 습관입니다. 이 예를 보자 :

누군가가 일반 문자열 대신에 형식 문자열 문자를 printf에 넣으면 (예를 들어 프로그램 stdin을 인쇄하려는 경우) printf는 스택에서 가능한 모든 것을 가져옵니다.

예를 들어 숨겨진 정보에 액세스하거나 인증을 우회하기 위해 스택을 탐색하도록 프로그램을 악용하는 데 매우 사용되었습니다.

예 (C) :

int main(int argc, char *argv[])
{
    printf(argv[argc - 1]); // takes the first argument if it exists
}

이 프로그램의 입력으로 넣으면 "%08x %08x %08x %08x %08x\n"

printf ("%08x %08x %08x %08x %08x\n"); 

이것은 printf 함수가 스택에서 5 개의 매개 변수를 검색하고 8 자리 패딩 된 16 진수로 표시하도록 지시합니다. 따라서 가능한 출력은 다음과 같습니다.

40012980 080628c4 bffff7a4 00000005 08059c04

더 완전한 설명과 다른 예는 이것을 참조하십시오 .


답변

printf리터럴 형식 문자열로 호출 하는 것은 안전하고 효율적이며 printf사용자 제공 형식 문자열로 호출하는 것이 안전하지 않은 경우 자동으로 경고하는 도구가 있습니다 .

가장 심각한 공격 printf%n형식 지정자를 활용 합니다. 다른 모든 형식 지정자와 달리 예를 들어 %d%n실제로 형식 인수 중 하나에 제공된 메모리 주소에 값을 씁니다. 이는 공격자가 메모리를 덮어 쓸 수 있으므로 잠재적으로 프로그램을 제어 할 수 있음을 의미합니다. Wikipedia
는 더 자세한 정보를 제공합니다.

printf리터럴 형식 문자열로 호출 하면 공격자가 형식 문자열에 잠입 %n할 수 없으므로 안전합니다. 실제로 gcc는에 대한 호출을 printf에 대한 호출로 변경 puts하므로 별다른 차이가 없습니다 (실행하여 테스트 gcc -O3 -S).

printf사용자가 제공 한 형식 문자열을 사용하여 호출 하면 공격자가 잠재적 %n으로 형식 문자열에 침투하여 프로그램을 제어 할 수 있습니다. 컴파일러는 일반적으로 안전하지 않다고 경고합니다 -Wformat-security.을 참조하십시오
. 또한 printf사용자가 제공 한 형식 문자열을 사용해도를 안전하게 호출 할 수 있도록하는 고급 도구가 있으며 , 올바른 수와 유형의 인수를에 전달하는지 확인할 수도 있습니다
printf. 예를 들어 Java의 경우 Google의 Error Prone
Checker Framework가 있습니다.


답변

이것은 잘못된 조언입니다. 예, 인쇄 할 런타임 문자열이있는 경우

printf(str);

매우 위험하므로 항상

printf("%s", str);

대신 일반적 str으로 %기호 가 포함되어 있는지 여부를 알 수 없기 때문입니다 . 그러나 컴파일 타임 상수 문자열이 있으면 아무런 문제가 없습니다.

printf("Hello, world!\n");

(다른 것들 중에서, 그것은 말 그대로 Genesis의 C 프로그래밍 책에서 가져온 가장 고전적인 C 프로그램입니다. 따라서 그 사용법을 비난하는 사람은 다소 이단 적이며, 나는 다소 불쾌 할 것입니다!)


답변

의 다소 불쾌한 측면은 printf스트레이 메모리 읽기가 제한된 (및 허용 가능한) 해를 입힐 수있는 플랫폼에서도 서식 지정 문자 중 하나가 %n다음 인수를 쓰기 가능한 정수에 대한 포인터로 해석하도록하고 식별 된 변수에 저장 될 지금까지 출력 된 문자 수. 나는 그 기능을 직접 사용하지 않았고, 가끔은 내가 실제로 사용하는 기능만을 포함하도록 작성한 경량 printf 스타일 메소드를 사용하지만 (그 중 하나 또는 유사한 것을 포함하지 않음) 수신 된 표준 printf 함수 문자열을 공급합니다. 신뢰할 수없는 출처의 경우 임의 저장소를 읽을 수있는 능력 이상의 보안 취약성이 노출 될 수 있습니다.