기본 콘텐츠로 건너뛰기

라벨이 Secure C인 게시물 표시

call-by-value_call-by-reference

call-by-value_call-by-reference Call By Value & Call By Reference 원론적으로 C 언어는 call by value만 지원한다. 단 pointer가 value로 가능하기 때문에 수동으로 저수준 reference 처리를 할 수 있다. C++ 언어에 와서야 call by value 외에 call by reference를 명시적으로 지원한다. (이는 C 언어에서 함수 포인터를 이용하여 OOP를 구현할 수 있지만 C언어가 OOP를 지원한다고 말하지 않듯이 C 언어는 call by value만 지원한다고 한다.) 참고 URL : KLDP 토론 글타래

typos

typos 오탈자 도전 1 whlie ( * src && left ) { * dst ++ = * src ++ ; if ( left = 0 ) die ( "badlen" ) ; left -- ; } if 조건에 관계 연산자 == 대신 대입 연산자 = 가 설정되어 있다. 이로 인해 left 이 0으로 설정되어도 while 구문을 벗어나지 못하고 left 값을 감소시키며 반복 수행한다. 수정 코드 if ( left == 0 ) 도전 2 int f ; f = get_security_flags ( username ) ; if ( f = FLAG_AUTHENTICATED ) { return LOGIN_OK ; } return LOGIN_FAILED ; if 조건에 관계 연산자 == 대신 대입 연산자 = 가 설정되어 있어 get_security_flags 결과와 무관하게 로그인 성공을 반환하고 있다. 수정 코드 if ( f == FLAG_AUTHENTICATED ) 도전 3 for ( i == 5 ; src [ i ] && i < 10 ; i ++ ) { dst [ i -5 ] = src [ i ] ; } for 조건에 대입 연산자 = 대신에 관계 연산자 == 가 설정되어 있어 i 가 10보다 작은 값일 때, 버퍼를 벗어날 위험이 있다. 수정 코드 for ( i = 5 ; src [ i ] && i < 10 ; i ++ ) 도전 4 if ( get_string ( src ) && check_for_overflow ( src ) & copy_string ( dst , src ) ) printf ( "string safely copied\n" ) ; 논리 연산자

Secure-Coding-C-DCL-5

Secure-Coding-C-DCL-5 C 언어 시큐어코딩 - Declare and Initialization 5 올바른 구문을 사용하여 가변 길이 배열 선언 가변 길이 배열은 하나 이상의 멤버가 있는 구조의 마지막 요소가 불완전한 특별한 유형의 배열이다. 즉, 배열의 크기가 구조 내에서 명시적으로 지정되어 있지 않다. 가변 길이 배열 구조 정의 시 몇 가지 제한 사항이 존재한다. 불완전한 배열 유형은 구조 내의 마지막 요소 여야 한다. 가변 길이 배열 멤버가 포함된 구조체 배열은 있을 수 없다. 가변 길이 배열 멤버가 포함된 구조체는 다른 구조체의 멤버로 사용할 수 없다. 구조체에는 가변 길이 배열 멤버 외에 하나 이상의 명명된 멤버가 있어야 한다. 잘못된 코드 예제 C 표준에서 가변 길이 배열을 도입하기 전에 유사한 요소를 얻기 위해 단일 요소 배열을 최종 멤버로 사용하는 구조가 사용되었었다. 예제는 하나의 요소을 가진 배열을 가변 길이 배열과 같이 최종 멤버를 할당하려고 시도한다. 구조체가 인스턴스화되면 malloc() 에 계산된 크기가 동적 배열의 실제 크기를 고려하여 수정된다. # include <stdlib.h> struct flexArrayStruct { int num ; int data [ 1 ] ; } ; void func ( size_t array_size ) { /* Space is allocated for the struct */ struct flexArrayStruct * structP = ( struct flexArrayStruct * ) malloc ( sizeof ( struct flexArrayStruct ) + sizeof ( int ) * ( array_size - 1 ) ) ; if ( structP == NULL ) { /* Handle malloc failure */ }

Secure-Coding-C-DCL-4

Secure-Coding-C-DCL-4 C 언어 시큐어코딩 - Declare and Initialization 4 예약된 식별자를 선언하거나 정의하지 않는다. 밑줄과 대문자 또는 밑줄로 시작하는 모든 식별자는 항상 그 용도가 예약되어 있다. 예약된 식별자를 매크로 이름으로 정의하거나, 예약된 컨텍스트에서 식별자를 선언하거나 정의하지 않는다. 잘못된 코드 예제 (Include Guard) C 표준 라이브러리에 헤더에 구현되어 정의된 이름이 없는 경우에도 컴파일러에 의해 암시적으로 미리 정의된 이름과 충돌할 수 있다. # ifndef _MY_HEADER_H_ # define _MY_HEADER_H_ /* Contents of <my_header.h> */ # endif /* _MY_HEADER_H_ */ 올바른 코드 예제 (Include Guard) 매크로 이름을 밑줄로 시작하지 않는다. # ifndef MY_HEADER_H # define MY_HEADER_H /* Contents of <my_header.h> */ # endif /* MY_HEADER_H */ 잘못된 코드 예제 (File Scope Objects) _max_limit 은 정적이므로 충돌의 영향을 받지 않을 것으로 생각할 수 있으나, size_t를 정의하기 위해 포함된 이름들과 충돌할 가능성이 있으며 컴파일러는 예약된 이름을 암시적으로 선언할 수 있다. _limit 은 런타임 라이브러리에 정의된 동일한 이름의 심볼과 충돌할 수 있다. # include <stddef.h> static const size_t _max_limit = 1024 ; size_t _limit = 100 ; unsigned int getValue ( unsigned int count ) { return count < _limit ? count : _limit

Signed-Numeric-overflow

Signed-Numeric-overflow 부호 있는 정수 경계 오버플로우 부호 있는 정수 취약점 예 char * read_data ( int sockfd ) { char * buf ; int length = network_get_int ( sockfd ) ; if ( ! ( buf = ( char * ) malloc ( MAXCHARS ) ) ) die ( "malloc: %m" ) ; if ( length < 0 || length + 1 >= MAXCHARS ) { free ( buf ) ; die ( "bad length: %d" , value ) ; } if ( read ( sockfd , buf , length ) <= 0 ) { free ( buf ) ; die ( "read: %m" ) ; } buf [ value ] = '\0' ; return buf ; } 먼저 length 가 MAXCHARS 보다 작은지 검사한다. 그리고 2 번째 length 검사에서 length 값에 1을 더한다. 이것이 공격 벡터가 된다. 값이 0x7FFFFFFF라면 이 값은 0보다 크기 때문에 첫 번째 검사를 통과하며, 두 번째 length 검사를 통과한다. (0x7FFFFFFF + 1은 0x80000000이 되고 이는 음수 값이다.) 결과적으로 read() 는 경게가 없는, 즉 음수 값을 가진 length 인자로 호출되고, 이는 잠재적으로 버퍼 오버플로우 상황을 만든다. OpenSSL 0.9.6I에서 정부 부호 경계 취약점 예 BIO(buffered IO) 스트림으로부터 ASN.1 객체를 읽어 들이는 crypto/asn1/a_d2i_fp.c - ASN1_d2i_fp() 함수의 일부분이다.

Unsigned-Numeric-overflow

Unsigned-Numeric-overflow 부호 없는 정수 경계 오버플로우 정수 오버플로우 예 u_char * make_table ( unsigned int width , unsigned int height , u_char * init_row ) { unsigned int n ; int i ; u_char * buf ; n = width * height ; buf = ( char * ) malloc ( n ) ; if ( ! buf ) return ( NULL ) ; for ( i = 0 ; i < height ; i ++ ) memcpy ( & buf [ i * width ] , init_row , width ) ; return buf ; } make_table() 함수의 목적은 너비와 높이, 초기 배열을 받아들여 각 행이 init_row 값과 동일한 값으로 초기화된 테이블을 메모리에 생성하는 것이다. 만약 사용자에게 width 와 height 에 대한 제어권을 갖고 있다면 오버플로우 공격을 일으킬 가능성이 있다. 너비가 1,000,000이고 높이가 3,000 같은 큰 값을 지정한다면 malloc() 함수를 호출하여 3,000,000,000 바이트를 할당하는 과정에서 에러를 감지하고 실패할 확률이 높다. 하지만 결과 값에서 1 비트만 큰 산술 오버플로우를 유도한다면 성공할 가능성이 높다. 실제 초기화는 for 루프에서 이루어지기 때문이다. 사용자가 width 를 0x400으로, height 를 0x1000001로 할당한다면 곱셈 결과 0x400000400이며, 이 값의 모듈로 0x100000000은 0x00000400 또는 1024가 된다. 그러므로 1024바이트가 할당되고 for 루프에서 init_row 를 약 천육백만 번 정도 더 많이 복사하게 된다. OpenSSH 시도 응답 정수 오

memory-corruption-impact

memory-corruption-impact 메모리 오염 영향 평가 취약점의 심각도를 가장 정확하게 파악할 수 있는 방법은 POC 공격을 작성하는 것이지만, 이는 너무 많은 시간을 필요로 한다. 따라서 몇 가지 질문에 대한 답을 찾는 것으로 영향 평가를 수행할 수 있다. 메모리에서 버퍼의 위치 변수는 주로 스택, 힙, 지속 데이터(정적 변수, 전역 변수 포함) 이렇게 세 개의 메모리 영역에 저장되지만 종종 이 세 위치를 분할하거나 새로운 영역을 나누기도 한다. 때로는 초기화된 전역 변수와 초기화 되지 않은 전역 변수를 나누기도 하고, 특별할 위치에 TLS를 두기도 한다. 또한 공유 라이브러리는 라이브러리 코드 바로 다음에 위치하는 프로세스 메모리에 초기화되거나 비초기화된 상태에서 매핑된다. 어디서 메모리 오염이 일어나고 어떤 특별한 고려 사항이 적용되는지 파악할 필요성이 있으며, 운영체제 별 고유 메모리 배치에 대한 이해가 필요하다. 다른 데이터로 덮어쓰기 되는 것 메모리 오염은 공격자가 목표하는 변수에만 국한되지 않을 수 있으며, 다른 변수에 까지 값을 덮어 쓸 수 있다. 예제에서 보는 바와 같이 프로그램 카운터를 덮어쓰기전 지역 변수 값을 같이 덮어쓸 수 잇다. 지역 변수로의 오버플로우 int dostuff ( char * login ) { char * ptr = ( char * ) malloc ( 1024 ) ; char buf [ 1024 ] ; . . . strcpy ( buf , login ) ; . . . free ( ptr ) ; return 0 ; } 여기서 공격자는 프로그램 카운터를 덮어쓸 때 ptr 변수도 같이 덮어쓰게 되는데, ptr 변수는 프로그램이 리턴되기 바로 전에 해제되는 값이다. 이로 인해 유효하지 않은 주소 값으로 ptr 변수를 덮어 쓰게 되면 free() 함수 호출 시 프로그램 크래시가 발생할 수 있다. 이로

Secure-Coding-C-DCL-3

Secure-Coding-C-DCL-3 C 언어 시큐어코딩 - Declare and Initialization 3 상충되는 Linkage 식별자를 선언하지 않는다. Linkage는 동일한 범위 내에서 여러 영역으로 선언되거나 여러 번 선언된 식별자를 동일한 개체나 함수를 참조하도록 만들 수 있다. 식별자는 externally linked, Internally linked 또는 not llinked로 분류된다. 이 세 종류의 분류는 다음과 같은 특징을 갖는다. External linkage: 프로그램에 속한 모든 편집 단위 및 라이브러리에서 동일한 객체 또는 기능을 나타낸다. 식별자는 링커에서 사용할 수 있다. External Internal linkage: 주어진 번역 단위 내에서 동일한 객체 또는 함수를 나타낸다. linker는 내부 링크가 있는 식별자에 대한 정보가 없다. 따라서 이러한 식별자는 번역 단위 내부에 있다. No linkage: 식별자에 연결이 없는 경우 식별자를 사용하는 추가 선언은 새로운 변수 또는 새로운 유형과 같은 새로운 것을 선언한다. 한 번역 내에서 내부 및 외부 링크로 분류된 식별자의 사용은 정의되지 않은 동작이다. 번역의 단위는 헤더와 함께 소스 파일, 전처리 지시어를 통해 포함된 모든 소스 파일을 포함한다. 다음 표는 단일 변환 단위에서 두 번 선언된 객체에 할당된 연결을 나타낸다. 열은 첫 번째 선언, 행은 재 선언을 의미한다. static (second) no linkage (second) extern (second) static (first) internal Undefined Internal no linkage (first) Undefined No linkage External extern (first) Undefined Undefined External 잘못된 코드 예제 이 예제에서 i2 와 i5

Secure-Coding-C-DCL-2

Secure-Coding-C-DCL-2 C 언어 시큐어코딩 - Declare and Initialization 2 식별자를 사용하기 전에 선언한다. C90 표준은 변수와 함수의 암시적 타이핑을 허용하였으나, C11 표준에는 형식 지정자가 필요하며 암시적 함수 선언을 금지하고 있다. 따라서 기존 레거시 코드 중 일부는 암시적 타이핑을 사용하고 있을 확률이 높다. 일부 컴파일러는 계속해서 지원하고 있지만 이는 호환성을 유지하지 위함이므로 새 코드에서는 사용해서는 안된다. 잘못된 코드 예제 C는 더 이상 선언문에 타입 지정자가 없다는 것을 허용하지 않는다. 이 예제는 형식 지정자를 생략한다. extern foo ; 올바른 코드 예제 이 예제는 형식 지정자가 명시적으로 포함되어 있다. extern int foo ; 잘못된 코드 예제 (암시적 함수 선언) 모든 함수는 호출되기 전에 명시적으로 선언되어야 한다. C90에서 명시적 프로토 타입 없이 함수를 호출하면 컴파일러에서 암시적 선언을 제공했다. 함수 호추이 이루어진 지점에서 표시되지 않으면 C90 호환 플랫폼은 extern int identifier(); 를 암시적으로 선언한다. 이 선언은 함수가 int를 반환할 수 있음을 의미한다. 그러나 현재 C 표준을 따르기 위해서는 프로그래머가 모든 함수를 호출하기전에 명시저긍로 프로토 타입을 작성해야 한다. # include <stddef.h> /* #include <stdlib.h> is missing */ int main ( void ) { for ( size_t i = 0 ; i < 100 ; ++ i ) { /* int malloc() assumed */ char * ptr = ( char * ) malloc ( 0x10000000 ) ; * ptr = 'a' ; } return 0 ;