이 코드는 왜 버퍼 오버 플로우 공격에 취약합니까?


148
int func(char* str)
{
   char buffer[100];
   unsigned short len = strlen(str);

   if(len >= 100)
   {
        return (-1);
   }

   strncpy(buffer,str,strlen(str));
   return 0;
}

이 코드는 버퍼 오버플로 공격에 취약하며 이유를 알아 내려고 노력 중입니다. 나는 그것이 대신 len선언 된 것과 관련이 있다고 생각 하지만 실제로는 확실하지 않습니다.shortint

어떤 아이디어?


3
이 코드에는 여러 가지 문제가 있습니다. C 문자열은 null로 끝나는 것을 기억하십시오.
Dmitri Chubarov

4
@DmitriChubarov, null이 아니라 문자열을 종료하는 것은 호출 후 문자열이 사용 된 경우에만 문제가됩니다 strncpy. 이 경우에는 그렇지 않습니다.
R Sahu

43
이 코드의 문제 strlen는 계산되어 유효성 검사에 사용 된 사실에서 직접 흐른 다음 다시 잘못 계산됩니다 . 이는 DRY 실패입니다. 두 번째 strlen(str)가로 대체 되면의 len유형에 관계없이 버퍼 오버플로 가능성이 없습니다 len. 대답은이 요점을 다루지 않고 단지 피하려고합니다.
Jim Balter

3
@CiaPan : null로 끝나지 않은 문자열을 전달하면 strlen은 정의되지 않은 동작을 보여줍니다.
Kaiserludi

3
@ JimBalter 아냐, 나는 그들을 떠날 것 같아요. 어쩌면 다른 누군가가 같은 어리석은 오해를 가지고 배울 것입니다. 그들이 당신을 자극하면 그들에게 자유롭게 표시하십시오. 누군가가 와서 그들을 삭제할 수 있습니다.
Asad Saeeduddin

답변:


192

대부분의 컴파일러에서 최대 값 unsigned short은 65535입니다.

이 값을 초과하면 65536이 0이되고 65600이 65가됩니다.

이는 올바른 길이의 긴 문자열 (예 : 65600)이 검사를 통과하고 버퍼가 오버 플로우됨을 의미합니다.


사용 size_t의 결과를 저장하는 strlen()하지를, unsigned short및 비교 len바로의 크기를 인코딩한다는 표현 buffer. 예를 들어 :

char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0]))  return -1;
memcpy(buffer, str, len + 1);

2
@PatrickRoberts 이론적으로는 그렇습니다. 그러나 코드의 10 %가 런타임의 90 %를 담당하므로 성능이 보안보다 먼저 떨어지지 않도록해야합니다. 그리고 시간이 지남에 따라 코드가 변경되어 갑자기 이전 검사가 실패했음을 의미 할 수 있습니다.
orlp

3
버퍼 오버 플로우를 방지하려면 lenstrncpy의 세 번째 인수로 사용 하십시오. strlen을 다시 사용하는 것은 어리석은 일입니다.
Jim Balter

15
/ sizeof(buffer[0])- 참고 sizeof(char)C에 항상 1 (a CHAR는 gazillion 비트를 포함하는 경우에도) 다른 데이터 유형을 이용 가능성이 없을 때 그 불필요한 그래서. 여전히 ... 완전한 답변을 제공합니다 (댓글에 응답 해 주셔서 감사합니다).
Jim Balter

3
@ RR-: char[]char*같은 것이 아니다. a 가 묵시적으로로 변환 되는 많은 상황 이 있습니다 . 예를 들어, 함수 인수의 유형으로 사용될 때 와 동일 합니다. 그러나에 대한 변환은 수행되지 않습니다 . char[]char*char[]char*sizeof()
Dietrich Epp

4
@Controll buffer어떤 시점에서 크기를 변경 하면 표현식이 자동으로 업데이트 되기 때문 입니다. 의 선언은 buffer실제 코드의 검사와 상당히 다른 라인 일 수 있으므로 보안에 중요 합니다. 따라서 버퍼 크기를 쉽게 변경할 수 있지만 크기가 사용되는 모든 위치에서 업데이트하는 것을 잊지 마십시오.
orlp

28

문제는 여기 있습니다 :

strncpy(buffer,str,strlen(str));
                   ^^^^^^^^^^^

문자열이 대상 버퍼의 길이보다 큰 경우 strncpy는 여전히이를 복사합니다. 버퍼의 크기 대신 문자열의 문자 수를 복사 할 숫자로 지정합니다. 이를 수행하는 올바른 방법은 다음과 같습니다.

strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = '\0';

이것이하는 일은 버퍼의 실제 크기에서 널 종료 문자에 대한 1을 뺀 것으로 복사되는 데이터의 양을 제한합니다. 그런 다음 추가 된 보호 수단으로 버퍼의 마지막 바이트를 널 문자로 설정합니다. 그 이유는 strlenpy가 strlen (str) <len-1 인 경우 종료 널을 포함하여 최대 n 바이트까지 복사하기 때문입니다. 그렇지 않으면 널이 복사되지 않고 버퍼가 종료되지 않으므로 충돌 시나리오가 발생합니다. 끈.

도움이 되었기를 바랍니다.

편집 : 다른 사람의 추가 조사 및 입력시 기능에 대한 가능한 코딩은 다음과 같습니다.

int func (char *str)
  {
    char buffer[100];
    unsigned short size = sizeof(buffer);
    unsigned short len = strlen(str);

    if (len > size - 1) return(-1);
    memcpy(buffer, str, len + 1);
    buffer[size - 1] = '\0';
    return(0);
  }

문자열의 길이를 이미 알고 있으므로 memcpy를 사용하여 str이 참조하는 위치에서 버퍼로 문자열을 복사 할 수 있습니다. strlen (3)의 매뉴얼 페이지 (FreeBSD 9.3 시스템)에 따라 다음이 언급됩니다.

 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.

문자열의 길이에 null이 포함되어 있지 않다고 해석합니다. 그렇기 때문에 len + 1 바이트를 복사하여 null을 포함시키고 테스트는 길이 <buffer-2의 크기인지 확인합니다. 널을 위해.

편집 : 것으로 나타났습니다. 액세스가 0으로 시작하는 동안 무언가의 크기가 1로 시작하므로 98 바이트 이상이면 오류가 반환되지만 99 바이트 이상이어야합니다.

편집 : 부호없는 short에 대한 대답은 일반적으로 표현 할 수있는 최대 길이가 65,535 자이므로 정확하지만 문자열이 그보다 길면 값이 줄 바꿈되므로 실제로 중요하지 않습니다. 75,231 (0x000125DF)을 가져 와서 상위 16 비트를 마스킹하여 9695 (0x000025DF)를 제공하는 것과 같습니다. 길이 확인으로 복사가 가능하기 때문에 65,535 이후의 첫 100 문자는이 문제로 볼 수 있지만 문제는 문자열의 처음 100 자까지만 복사하고 null은 문자열을 종료합니다 . 따라서 랩 어라운드 문제가 있어도 버퍼가 여전히 오버플로되지 않습니다.

이것은 문자열의 내용과 사용하는 내용에 따라 보안 위험을 초래할 수도 있고 그렇지 않을 수도 있습니다. 사람이 읽을 수있는 단순한 텍스트라면 일반적으로 문제가 없습니다. 당신은 잘린 문자열을 얻습니다. 그러나 URL이나 SQL 명령 시퀀스와 같은 것이면 문제가있을 수 있습니다.


2
사실이지만 그것은 그 질문의 범위를 벗어납니다. 이 코드는 함수가 char 포인터로 전달되고 있음을 분명히 보여줍니다. 우리는 그 기능의 범위 밖에서 신경 쓰지 않습니다.
Daniel Rudy

"str이 저장된 버퍼"-이것은 버퍼 오버 플로우 가 아니며, 여기서 문제가됩니다. 그리고 모든 대답에는 "문제"가 있습니다. 이것은 func... 의 서명과 불가피하게 NUL로 끝나는 문자열을 인수로 사용하는 다른 모든 C 함수 의 서명을 감안할 때 불가피합니다 . 입력이 NUL로 종료되지 않을 가능성을 제기하는 것은 완전히 단서가 아닙니다.
Jim Balter

"문제의 범위를 벗어난 것"은 슬프게도 일부 사람들이 이해할 수없는 능력입니다.
Jim Balter

"문제는 여기에 있습니다."-맞습니다.하지만 여전히 핵심 문제가 누락되었습니다. 즉, 테스트 ( len >= 100)가 한 값에 대해 수행되었지만 사본 길이에 ​​다른 값이 부여되었습니다. DRY 원칙을 위반하는 것입니다. 단순히 호출 strncpy(buffer, str, len)하면 버퍼 오버플로의 가능성을 피할 수 있으며 strncpy(buffer,str,sizeof(buffer) - 1)여기서 보다 작업 속도가 느리지 만 ... 보다 작업이 적습니다 memcpy(buffer, str, len).
Jim Balter

@JimBalter 그것은 질문의 범위를 벗어 났지만, 나는 틀렸다. 테스트에서 사용되는 값과 strncpy에서 사용되는 값은 서로 다른 두 가지라는 것을 이해합니다. 그러나 일반적인 코딩 관행에 따르면 복사 한도는 sizeof (buffer)-1이어야하므로 복사본의 str 길이가 중요하지 않습니다. strncpy는 널에 도달하거나 n 바이트를 복사 할 때 바이트 복사를 중지합니다. 다음 행은 버퍼의 마지막 바이트가 널 문자임을 보증합니다. 코드는 안전합니다. 이전 진술을 준수합니다.
Daniel Rudy

11

을 사용하더라도 strncpy컷오프의 길이는 여전히 전달 된 문자열 포인터에 따라 다릅니다. 그 문자열의 길이 (포인터에 대한 null 종결 자의 위치)를 모릅니다. 따라서 전화 strlen만하면 취약점이 생길 수 있습니다. 보다 안전하게하려면을 사용하십시오 strnlen(str, 100).

수정 된 전체 코드는 다음과 같습니다.

int func(char *str) {
   char buffer[100];
   unsigned short len = strnlen(str, 100); // sizeof buffer

   if (len >= 100) {
     return -1;
   }

   strcpy(buffer, str); // this is safe since null terminator is less than 100th index
   return 0;
}

@ user3386109 strlen그런 다음 버퍼 끝을 지나서 액세스 하지 않겠습니까 ?
Patrick Roberts

2
@ user3386109 당신이 지적한 것은 orlp의 대답을 내 것만 큼 유효하지 않게 만듭니다. strnlenorlp가 제안하는 것이 어쨌든 정확하다면 왜 문제를 해결하지 못하는지 알 수 없습니다.
Patrick Roberts

1
"나는 strnlen이 여기서 아무것도 해결하지 못한다고 생각한다"-물론 그것은; 오버플로를 방지합니다 buffer. "str은 2 바이트의 버퍼를 가리킬 수 있기 때문에 NUL이 아닙니다." - 그것의 사실로, 무관의 그 어떤 구현 func. 여기서 질문은 입력이 NUL로 끝나지 않기 때문에 UB가 아닌 버퍼 오버플로에 관한 것입니다.
Jim Balter

1
"strnlen에 전달 된 두 번째 매개 변수는 첫 번째 매개 변수가 가리키는 오브젝트의 크기이거나 strnlen이 쓸모가 없어야합니다."-이것은 완전하고 말도 안됩니다. strnlen의 두 번째 인수가 입력 문자열의 길이 인 경우 strnlen은 strlen과 같습니다. 이 번호를 어떻게 구할 수 있으며, 가지고 있다면 왜 str [n] len에게 전화해야합니까? 그것은 strnlen이 전혀 아닙니다.
Jim Balter

1
는 영업 이익의 코드와 일치하지 않는 때문에이 답변 비록 하나가 불완전 - strncpy에서 NUL 패드와 NUL 종료하지 않고, strcpy와 NUL - 종료 반면하지 NUL 패드를 않습니다, 그것은 않는 사람, 반대로 문제를 해결 위의 어리 석고 무지한 의견.
Jim Balter

4

포장에 대한 답이 맞습니다. 그러나 if (len> = 100)라고 언급되지 않은 문제가 있습니다.

Len이 100이라면 우리는 100 개의 요소를 복사 할 것입니다. 즉, 적절한 종료 문자열에 따라 다른 함수가 원래 배열을 넘어서는 것을 의미합니다.

C에서 문제가되는 문자열은 해결할 수 없습니다. 통화하기 전에 항상 약간의 제한이 있지만 더 도움이되지 않습니다. 경계 검사가 없으므로 버퍼 오버플로는 항상 발생할 수 있으며 불행히도 발생합니다 ....


문제 되는 문자열 해결할 수 있습니다. 적절한 기능을 사용하십시오. 나. 하지 strncpy() 와 친구,하지만 같은 기능을 할당하는 메모리 strdup()와 친구. POSIX-2008 표준이므로 일부 전용 시스템에서는 사용할 수 없지만 이식성이 뛰어납니다.
cmaster-monica reinstate

"올바른 종료 문자열에 따른 다른 함수"- buffer이 함수의 로컬이며 다른 곳에서는 사용되지 않습니다. 실제 프로그램에서 우리는 그것이 어떻게 사용되는지 검사해야 할 것입니다 ... 때로는 NUL 종료가 올바르지 않습니다 (strncpy의 원래 사용은 UNIX의 14 바이트 디렉토리 항목을 NUL로 패딩하고 NUL로 종료하지 않는 것입니다). "C에서 문제가되는 문자열은 IMHO로 해결할 수 없습니다."-C는 훨씬 더 나은 기술로 능가되는 놀라운 언어이지만 충분한 훈련이 사용되면 안전한 코드를 작성할 수 있습니다.
Jim Balter

당신의 관찰이 잘못 인도 된 것 같습니다. if (len >= 100)검사 가 실패 할 때가 아니라 검사에 실패 할 때의 조건이며 , 이는 길이가 실패 조건에 포함되어 있으므로 NUL 종료자가없는 정확히 100 바이트가 복사되는 경우가 없음을 의미합니다.
Patrick Roberts

@ cmaster. 이 경우에 당신은 틀 렸습니다. 항상 경계를 넘어 쓸 수 있기 때문에 해결할 수 없습니다. 그렇습니다. 거스르지 않은 행동이지만 완전히 막을 방법은 없습니다.
Friedrich

@ 짐 발터. 그것은 중요하지 않습니다. 잠재적 으로이 로컬 버퍼의 경계를 덮어 쓸 수 있으므로 항상 다른 데이터 구조를 손상시킬 수 있습니다.
Friedrich

3

strlen두 번 이상 호출하는 것과 관련된 보안 문제 외에도 일반적으로 길이가 정확하게 알려진 문자열에는 문자열 메서드를 사용하지 않아야합니다. [대부분의 문자열 함수의 경우 최대 길이 인 문자열에서 사용해야하는 경우는 매우 좁습니다. 길이는 보장 할 수 있지만 정확한 길이는 알려져 있지 않습니다]. 입력 문자열의 길이를 알고 출력 버퍼의 길이를 알면 영역을 복사해야하는 크기를 파악한 다음 memcpy()실제로 해당 복사를 수행하는 데 사용해야 합니다. 1-3 바이트 정도의 문자열을 복사 할 때 strcpy성능이 저하 memcpy()될 수 있지만 많은 플랫폼 memcpy()에서 더 큰 문자열을 처리 할 때 속도가 두 배 이상 빠를 수 있습니다.

보안 성능의 비용으로 올 것 어떤 상황이 있지만, 이것은 보안 접근 방식 인 상황 빨리 하나. 경우에 따라 이상하게 동작하는 입력에 대해 안전하지 않은 코드를 작성하는 것이 합리적 일 수 있습니다. 입력을 제공하는 코드가 올바르게 동작하는지 확인하고 동작이 잘못된 입력에 대해 보호하면 성능이 저하 될 수 있습니다. 문자열 길이를 한 번만 확인하면 성능과 보안 이 모두 향상 되지만 문자열 길이를 수동으로 추적 할 때도 보안을 유지하기 위해 추가로 한 가지 추가 작업을 수행 할 수 있습니다. 후행 null이있는 모든 문자열에 대해 후행 null을 명시 적으로 작성하십시오 소스 문자열을 기대하는 것보다. 따라서, strdup동등한 것을 쓰고 있다면 :

char *strdupe(char const *src)
{
  size_t len = strlen(src);
  char *dest = malloc(len+1);
  // Calculation can't wrap if string is in valid-size memory block
  if (!dest) return (OUT_OF_MEMORY(),(char*)0); 
  // OUT_OF_MEMORY is expected to halt; the return guards if it doesn't
  memcpy(dest, src, len);      
  dest[len]=0;
  return dest;
}

memcpy가 len+1바이트를 처리 한 경우 마지막 명령문은 일반적으로 생략 될 수 있지만 다른 스레드는 소스 문자열을 수정하여 NUL이 아닌 대상 문자열 일 수 있습니다.


3
두 번 이상 전화하는 데 관련된 보안 문제strlen 를 설명해 주 시겠습니까?
Bogdan Alexandru

1
@BogdanAlexandru : 일단 strlen반환 된 값 (아마 첫 번째로 호출 한 이유)에 따라 어떤 조치를 취하고 조치를 취하면 반복 된 호출 중 하나 (1)는 항상 첫 번째와 동일한 응답을 생성합니다. 이 경우 단순히 작업이 낭비되거나 (2) 때로는 (다른 스레드로 인해 다른 스레드로 인해 그 동안 문자열을 수정했기 때문에) 다른 대답을 얻을 수 있습니다.이 경우 길이에 따라 일부 작업을 수행하는 코드 (예 : 버퍼 할당)은 다른 작업을 수행하는 코드 (버퍼에 복사)와 다른 크기를 가정 할 수 있습니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.