跳转到主要内容
메인 컨텐츠로 가기

한국 developerWorks  >  리눅스  >

Secure programmer: 컴포넌트를 안전하게 호출하기

콜과 리턴을 핸들하는 방법은 어떤 컴포넌트를 호출하는 것 만큼 중요하다!

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.


난이도 : 중급

David A. Wheeler, 연구원, Institute for Defense Analyses

2004 년 12 월 16 일

일반적으로, 애플리케이션 프로그램들은 OS, 데이터베이스 시스템, 재사용 가능한 라이브러리, (DNS 같은) 인터넷 서비스, 웹 서비스 등의 다른 컴포넌트들을 호출한다. 이 글에서는 이러한 호출을 악용하는 사람들을 방지하는 방법을 설명한다.

PDF 파일을 보면서 하이퍼텍스트 링크를 클릭하는 것은 파일을 호출한 뷰어를 신뢰하는 한 보안 문제는 생길 리가 없다. 하지만 xpdf version 0.90의 사용자들은 이러한 추측이 완전히 잘못된 것이라는 것을 발견했다.

xpdf 사용자들이 하이퍼텍스트 링크를 클릭했을 때, xpdf는 뷰어를 시작하고(디폴트는 Netscape) URL을 뷰어에게 보냈다. 여기까지는 좋았다. 하지만 xpdf 개발자들이 system()호출을 사용하여 뷰어를 시작하기로 결정했다. 이것이 문제였다.

유닉스 시스템과는 달리, system()은 명령행 쉘을 호출하는데 이것은 전송된 텍스트를 인터프리팅한다. 다시 말해서, 침입자가 쉘에서 특별히 인터프리팅 된 가짜 URL을 가지고 PDF 파일을 만들 수 있었음을 의미한다. 그런 다음 침입자가 누군가가 하이퍼텍스트 링크를 클릭한다는 것을 확신할 수 있으면 사람들로 하여금 그들이 선택한 아무 프로그램이나 실행할 수 있도록 한다. 모든 파일들을 지우거나, 읽을 수 있는 모든 것을 외부 메일 드랍으로 이메일을 보낸다.

솔루션: 보안 컴포넌트 호출하기.

컴포넌트를 재사용하는 것에 대해서...

애플리케이션 프로그램들은 일반적으로 독립적(self-contained)이지 않다. 대게 다른 컴포넌트들을 호출한다. 컴포넌트로는 다음과 같은 것들이 있다:

  • OS
  • 데이터베이스 시스템
  • 재사용 가능한 라이브러리(동적으로 로딩되는 라이브러리 포함)
  • 인터넷 서비스 (DNS)
  • 웹 서비스

오늘날 대부분의 프로그래밍 언어에는 규모가 큰 빌트인 라이브러리가 포함된다. 이중 어떤 것은 독립적이고 어떤 것은 외부 프로그램이나 서비스들을 호출하도록 설계된다.

재사용 문제는 너무 중요하다-모든 애플리케이션을 위해 처음부터 모든 것을 작성한다는 것은 말이 안된다. 각 컴포넌트를 다시 만들기엔 너무 많은 시간이 걸리기 때문이다. 그리고 재사용 할 때 잠재적인 보안 관련 모험을 감수해야 한다-개발자들은 그들의 컴포넌트가 보안 취약성이 없다는 것을 확인하고 보안 컴포넌트의 혜택을 더 많은 시스템들에 전달하는데 집중할 수 있다. 나중에 취약점이 발견되더라도 한번에 픽스되고 이 컴포넌트를 사용하는 모든 애플리케이션에 이 픽스가 적용될 수 있다.

하지만, 다른 컴포넌트를 호출하는 것에도 단점은 있다: 침입자들이 이 호출을 이용할 수도 있다.

Open Web Application Security Project's (OWASP)의 "Top Ten Most Critical Web Application Vulnerabilities" 는 가장 심각한 취약점 중 하나를 투입 오류(injection flaws)로 구분했다. OWASP는 웹 애플리케이션들은 매개변수를 외부 시스템이나 로컬 OS에 전달하고:

침입자가 악의적인 명령행을 이 매개변수에 삽입할 수 있다면 외부 시스템은 애플리케이션 대신 그 명령어를 실행할 것이다.

컴포넌트를 재사용 할 때 면밀히 계획을 세우기 위해서는 다음 사항들을 고려해야 한다:

  • 안전한 컴포넌트만 안전한 방식으로 사용한다.
  • 유효 데이터만 컴포넌트로 보내고 기대하는 대로 인터프리팅 될 것을 확인한다. 특히, 메타-캐릭터(SQL 투입, 쉘 메타-캐릭터 투입, 포맷 스트링, Perl open() 침입 등의 원인)를 주의한다.
  • 리턴 값을 점검하고 예외를 핸들한다.
  • 애플리케이션과 컴포넌트 사이를 거쳐갈 때, 데이터를 보호한다.

이제부터 좀더 자세히 살펴보자.




위로


안전한 컴포넌트, 안전한 방법

작은 함수라 하더라도 무엇인가를 재사용 하기 전 첫 번째 단계는 문서를 검토하여 보안 경고가 있는지를 확인하는 것이다. 예를 들어, C 표준에는 gets()함수가 포함되지만 이에 관한 문서는 gets()가 위험하다고 경고하고 있다. 문제는 gets()가 버퍼 오버플로우에서 자신을 보호할 수 없다는 것이다. 침입자는 gets()에 전달된 버퍼가 저장할 수 있는 것 보다 더 많은 데이터를 보낸다. 이로서 침입자는 내부 데이터를 제어하게 되고 프로그램을 접수하게 된다.

여기 또 다른 예제가 있다: 모든 언어는 실제로는 예견 가능한 "랜덤(random)" 값을 리턴하는 함수를 갖고 있다. 이 함수들은 시뮬레이션에는 이상적이지만 보안용 비밀 키에 사용한다면 매우 위험하다. 왜냐하면 침입자가 그 키를 결정하기 쉽기 때문이다.

광범위하게 사용되는 컴포넌트를 사용하고 있다면 웹 검색을 수행하여 알려진 보안 이슈가 있는지 또는 가볍게 사용할 일반적인 방법이 있는지를 검토해도 좋다.

휴먼 인터랙션을 위한 전체 프로그램을 재사용하려 한다면 특별히 조심해야 한다. 좋은 규칙은 그 일을 하지 않는 것이다. 사용자 인터랙션 프로그램들은 도움이 되고 사용자들의 의도를 생각한다. "도움"은 침입자들이 프로그램을 오용할 데이터를 만드는 데에도 도움이 된다.

휴먼 인터랙티브 프로그램들은 종종 시간이 흐르면서 미묘한 변화들을 겪는데 이것은 사용자들에게는 좋은 것이지만 이 프로그램을 호출하는 프로그램에 혼란을 줄 수 있다. 또한 이 프로그램들에는 사용자들이 다른 프로그램들을 호출할 수 있도록 하는 함수들을 갖고 있다. 예를 들어, vim (vi), emacs, "ed" 프로그램 같은 텍스트 편집 프로그램들에는 사용자가 OS 명령어 쉘로 호출할 수 있는 함수가 포함되어 있어 침입자들이 악용할 수 있는 손쉬운 목표물이 되고 있다. 유닉스 계열 시스템상에서 텍스트 편집을 할 때 sed, tr, gawk, perl 같은 프로그램에서 사용할 명령어들 중 하나를 사용하는 것이 더 안전하다.

워드 프로세싱, 스프레드시트 프로그램의 경우도 마찬가지다. 일반적으로 휴먼인터랙티브 프로그램들은 다른 프로그램에 의해 호출될 수 있는 컴포넌트들에 구현되도록 작성되어야 한다. 이러한 방식으로, 사용자 인터페이스를 픽스하기 더 쉬워지고 다른 프로그램들이 프로그램의 기능들을 재사용하기 더 쉬워진다.




위로


유효 데이터를 전달하여 메타-캐릭터 문제 피하기

컴포넌트가 재사용된다면 이것은 특정 애플리케이션을 위해 만들어지지 않을 것이다. 대신 재사용된 컴포넌트는 많은 기능들을 갖춘 훌륭한 인터페이스를 이용한다. 침입자들은 당신이 기대하지 않던 것을 수행하는 애플리케이션으로 데이터를 보낼 것이다.

어떤 데이터가 당신이 재사용하고 있는 것으로 보내질 수 있는지를 정확히 알고 유효 데이터를 확실히 보내는 것이 중요하다. 의심 할 것이 전혀 없다면 컴포넌트에 데이터를 보내기 전에 검사해야 한다. 숫자가 0에서 100 사이라면 검사하라. 반드시 컴포넌트가 사용하게 될 것과 같은 데이터타입을 사용한다. 컴포넌트가 서명 된 정수를 기대한다면 데이터를 서명 된 정수로 변환한 다음 검사한다. 특히 컴포넌트가 가장 작은 값인 0을 가진 서명 된 정수를 기대한다면 정확히 그 부분에 대해 검사하라. 거의 모든 언어에서, 서명 되지 않은 정수는 서명 된 정수로 바뀔 때 음수로 변경된다. 서명 비트가 일반적으로 서명 된 값에서는 가장 큰 비트이기 때문이다.

가능한 한 여러분이 보내는 데이터가 침입자들에 의해 제어되지 않도록 하라. 빈번한 심각한 포맷 스트링 침입은 침입자가 데이터를 디스플레이하는데 사용된 포맷을 제어할 수 있다는 개념을 바탕으로 한다. 하지만 포맷이 프로그램에서 연속적이라면 침입자가 제어할 것은 없다. gcc 컴파일러 옵션인 -Wformat-security는 코드가 포맷 스트링 침입에 취약하다는 것을 경고한다.

컴포넌트가 여러분이 보내는 데이터를 여러분이 기대하는 방식으로 인터프리팅 하는 것인지를 확인해야 한다. 침입자가 그 데이터의 값에 영향을 줄 수 있을 경우라도 그렇게 해야 한다. 이렇게 하면 매우 일반적인 문제인 메타-캐릭터 취약성(meta-character vulnerability)문제가 발생한다. 많은 복잡한 재사용 컴포넌트들은 고유의 복잡한 명령어 언어를 구현한다. 침입자는 컴포넌트에 의해 인터프리팅 될 텍스트를 여러분이 의도하지 않았던 어떤 것을 수행하기 위해 삽입을 시도할 수 있다. 이는 유닉스 명령어 쉘(/bin/sh) 과 데이터베이스 시스템에 대한 SQL 명령어에서 가장 빈번히 발생한다.

쉘 취약성

유닉스 계열 시스템의 명령어 쉘, 특히 표준 쉘인 /bin/sh는 매우 유용하다. 다른 프로그램들을 한데로 쉽게 결합시켜 유용한 작동을 한다. 쉘에 대한 한 줄의 텍스트 라인이 백 줄의 프로그램과 같은 작동을 할 수 있다는 것은 놀랄 일이 아니다.

표준 쉘은 다른 프로그램들을 통합하기 쉽도록 설계된 많은 빌트인 기능들을 갖춘 완전한 프로그래밍 언어이다. 많은 프로그래밍 언어에는 빌트인 기능들이 포함되어 있어 표준 쉘인 C의 system(3)popen(3)을 호출하고 backtick (`) 연산자는 표준 쉘을 호출한다. Windows와 MS-DOS의 사용자들만 유닉스 계열 시스템에서 쉘이 빈번히 사용된다는 데에 놀란다. COMMAND.COM은 매우 제한적이고 같은 기능을 할 수 없기 때문이다.

특히 많은 문자들이 쉘에 대해 특별한 의미를 갖고 있어서 일반 침입 트릭은 이러한 특별한 문자들을 호출 될 경우 쉘로 재전송하기 위해 프로그램을 얻으려 시도하는 것이다.

최상의 솔루션은 프로그램 내부에서 직접 쉘을 호출하지 않는 것이다. 실수하기 쉽기 때문에 단지 그렇게 하지 않으면 된다. 다시 말해서, 보안 프로그램에는 system()popen()을 사용하지 않는 것이 좋다. 특히 이러한 호출들로 보내지고 있는 것이 일정한 것이 아닐 때 더욱 그렇다. setuid/setgid 쉘 스크립트를 작성하지 말라; 리눅스 기반 시스템에서는 전혀 작동하지 않으며 다른 유닉스 계열 시스템상에서의 보안 취약성이 있다.

주의를 기울인다면 setuid/setgid가 아닌 쉘 스크립트를 작성할 수 있다. 쉘 스크립트는 다른 쉘의 사용 만큼 나쁘지는 않다. 스크립트 자체는 파일 내에서 정적이기 때문이다. 하지만 이 쉘을 사용해도 단점은 있다. 침입자들이 프로그램을 제어할 수 있게 쉘 안에서 무엇인가를 수행하기 쉽기 때문이다.

쉘 스크립트를 작성하고 있다면 각 프로그램의 전체 경로명을 써서 침입자가 PATH를 조작하더라도 침입하기 어렵게 만든다. 이것을 호출하는 setuid/setgid 프로그램이 있다면 환경을 우선 정리해야 한다. (합당한 PATH 값 설정 포함).

프로그램에서 직접 쉘을 호출할 것이라면 환경이 안전하게 설정되었는지를 확인한다. 침입자들은 문제를 일으키는 PATH, IFS, 기타 환경 변수들을 가지고 장난친다. 전체 경로명으로 명령어를 호출한다.

쉘을 직접 호출할 때 가장 큰 문제는 믿을 수 없는 데이터에서 나온 쉘 데이터를 보내는 경우이다. 침입자가 특별한 의미를 가진 문자를 쉘에 보낼 수 있다면 문제가 생길 것이다. 그와 같은 문자들이 많이 있다.

쉘에서 더블 쿼트 (")는 또 다른 더블 쿼트로 끝난다. 세미 콜론(;)은 명령어를 끝내고 새로운 것을 시작한다. 예를 들어 다음 코드를 가진 C++ 프로그램을 생각해 보자:


Listing 1. 취약한 C++ 코드


 #include <string>
 using namespace std;
 ...
  string command = "md5sum < " + filename + " > ./results";
  system(command.c_str());
 }

침입자가 변수 파일이름의 값을 제어할 수 있는지를 묻고있다. 침입자가 할 수 있다면 문제인 것이다.

이와 같은 코드가 어떻게 이용되는지를 예제를 통해 보자. SLOCCount는 프로그램에서 코드의 소스 라인들의 수를 세는 프로그램으로서 내가 직접 작성한 것이다.(참고자료) 어떤 사람들은 이전 코드와 같아 보이는 SLOCCount에 코드를 추가한 것이라고 생각한다. 하지만 어떤 사람들은 SLOCCount를 사용하여 그들이 작성하지 않은 프로그램들을 측정한다는 것을 깨닫는 것은 중요하다. 사실 그들이 측정하고 있는 프로그램은 침입자가 작성한 것일 수도 있다. SLOCCount가 침입자들에 의해 작성된 프로그램으로 보내져서 SLOCCount 사용자들에게 해를 끼칠 수 있기 때문에 SLOCCount는 침입자로부터 사용자들을 보호해야 한다.

침입자가 그들이 가진 파일들 중 하나를 x ; ;rm -fr ~로 이름을 정했다고 가정해보자. 이 파일 이름들은 가능하다. 다시 말해서 이 시스템 명령어가 쉘에 다음의 명령어를 보낸다는 것을 의미한다:

md5sum < x ; rm -fr ~ > ./my-output

이것은 SLOCCount를 실행하는 사람의 홈 디렉토리 전체를 지울 수 있다. 이 프로그램이 네트워크 인터페이스를 갖고 있지 않더라도 보안에 대해 걱정할 필요가 없다. 침입자들이 일부 인풋을 제공할 수 있기 때문이다.

이 코드에는 다른 잠재적 문제가 있다. 결과를 저장하는 방식 때문이다. my-output에 작성하는 한 개 이상의 프로그램이 있다면? 침입자가 파일, 디렉토리, 조상 디렉토리를 조작할 수 있겠는가?

메타-캐릭터 문제에 대한 간단한 솔루션은 인풋을 메타-캐릭터가 아닌 문자(A-Z, a-z, 0-9)로만 인풋을 제한하는 것이다. 가끔씩 이렇게 할 수 있다. .

그러나, 쉘을 사용해야 하고 들어오는 데이터를 제한할 수 없다면 명령어에 있는 것을 쉘로 보내기 전에 모든 잠재적인 메타-캐릭터들을 취소해야 한다. 쉘의 경우 의심이 가는 모든 문자 앞에 "\" 문자를 삽입하여 명령어의 일부로서 쉘에 보낸다. NUL 문자는 절대 허용하지 말라. 어떤 쉘은 이것을 핸들하지 않는다.

SQL 투입

SQL 투입은 쉘 메타-캐릭터와 같은 문제이다. 다만 쉘 대신 SQL 인터프리터 문제이다.

SQL 투입 침입에서 프로그램은 SQL 명령어를 만들고 이를 SQL 인터프리터로 보낸다. 프로그램은 침입자가 SQL 명령어의 의미를 변경하는 문자를 추가할 수 있도록 한다. 침입의 결과는 광범위하다. 기밀로 유지하고 싶은 데이터를 드러낼 수도 있고 임의의 데이터 변경일 수도 있으며 허용해서는 안될 곳의 인증을 하락할 수도 있고 데이터베이스를 묶어 시스템을 사용할 수 없게 만들 수도 있다.

SQL 투입 침입은 고가의 사이트를 해칠 수 있는 취약성이다. 사이트가 SQL 데이터베이스의 사용을 정렬할 수 있을 만한 데이터를 갖고 있다면 그 데이터는 누군가에 의해 악용될 충분한 소지가 있다. 쉘로의 호출을 재작성 하기 쉬워서 쉘은 개입하지 않아도 SQL 요청을 피할 수 없다. 일반적으로 프로그램의 전체 포인트는 SQL 데이터베이스에 저장된 데이터로 작업한다.

취약한 코드는 무엇을 찾고 있는지 일단 알고 있을 경우 찾기 쉽다. SQL을 호출하는 그릇된 방식은 스트링 연결을 수행하여 극도로 좁은 스크리닝을 경험하지 않은 데이터를 사용하는 SQL 명령어를 만드는 것이다. 다음의 Perl 코드는 그 문제의 예이다: $cmd = "SELECT salary,lastname,firstname FROM employee WHERE eid=" . $eid; 뒤에는 SQL 명령어인 prepare()execute() 에 대한 명령어가 뒤따른다. 여기에서는 간단한 스트링 연결을 갖고 있다. ("."는 Perl에서 연결 연산이다.) $eid 변수가 단순한 숫자를 갖고 있다면 기대하는 대로 작동한다. 하지만 침입자가 $eid를 조정하여 "5 OR 1=1" 값을 갖도록 한다면 $result 는 전체 테이블에서 선택된 칼럼을 포함하는 것으로 끝난다.

다른 트릭은 ";"를 삽입하여 다른 명령어를 실행하면서 여분의 더블 쿼트(")를 삽입하여 쿼트 밖의 스트링을 일찍이 취소하는 것이다. 이러한 종류의 침입에는 무한한 변이들이 있다. 기본적으로 간단한 스트링 연결이나 스트링 대체를 사용하여 SQL 쿼리를 만든다면 문제를 버는 것이다.

이러한 잠재적 문제를 확인하는 두 가지 침입 유형이 있다:

  • 받아들이기 전에 아무 데이터나 검사한다.
  • 취약하지 않은 루틴을 사용한다.

데이터를 받아들이기 전에 원하는 포맷을 설명하는 정규식을 정의하고 이 포맷에 맞지 않는 모든 것을 거절한다. 가능하다면 포맷이 SQL에서 통어적 의미를 갖고 있는 문자를 받아들이지 않는지를 확인한다. 문자와 숫자에 대한 값을 제한하라. Legal 리스트에 공백 문자와 구두점을 포함하지 않음으로서 조작의 잠재적 위험성을 방지할 수 있다.

신중하게 핸들하지 않는다면 문제를 일으킬 수 있는 데이터를 받아들여야 한다. 이 같은 것을 염두하고 텍스트를 연결하여 SQL 명령어를 결코 만들지 말라. 대신 라이브러리 루틴들을 검사하여 그와 같은 실수에서 자동으로 보호되는 고급 루틴들을 찾도록 하라.

다른 언어들은 "bound" 또는 "placeholder" 또는 "prepared" 또는 "parameterized" SQL 문장 같은 다른 것들을 호출한다. 예를 들어, PHP는 사용자 데이터를 정확히 대체하는 bind_param 메소드를 갖고 있다. 이 루틴들은 사용자가 제공한 데이터가 기대했던 포맷으로 있는지를 확인해야 한다. (숫자라면 루틴은 어떤 것이든 받아들인다.); 그런 다음 적절한 escaping를 가진 데이터를 삽입하여 SQL 인터프리터에 의해 잘못 번역되지 않도록 한다.

이 레벨의 기능이 여러분이 선택한 언어에서 사용할 수 없다면 루틴을 만들거나 적어도 쿼리를 만들기 위해 다른 스트링과 이들을 결합하기 전에 기대한 포맷과 값이 매치하는지 검사하는 특별한 루틴을 만든다. 루틴을 직접 작성해야 한다면 어떤 데이터든 정확히 쿼트(")를 달도록 한다.

다음은 취약성을 설명하는 일반적인 예제이다.

불행히도 침입자로부터 그 데이터를 인터프리팅하는 라이브러리로 데이터를 옮기는 것이 일반적이다. C에서 일반적인 실수는 침입자 데이터를 포맷 스트링 매개변수(이를 테면 printf(3)의 첫 번째 매개변수)로 전달하는 것이다. printif 포맷 스트링은 데이터를 작성할 수 있고 %n 명령어를 사용함), 임의의 데이터를 드러내면서 이를 심각하게 취약한 것으로 만든다. 다음은 이러한 실수의 예제이다:

printf(bad); /* DON'T DO THIS if attacker can control 'bad' */.

포맷 스트링 침입은 일반적으로 프로그램을 목표로 하지만 다른 언어들도 포맷 스트링을 갖고 있다. 이 언어들의 경우 침입자들이 이들을 제어하지 못하도록 해야 한다. 예를 들어, Python은 빌트인 "%" 연산자를 갖고 있는데 이것은 포맷팅을 수행한다. 이를 상수로 만들어서 침입자가 이 포맷을 제어하지 못하도록 한다.

Perl에서 일반적인 실수는 침입자들에게 시스템에 대한 완전한 제어권을 주는 방식으로 open()함수를 사용하는 것이다. Perl의 open()함수는 보안을 요구하기에는 너무 느슨하다. 예를 들어 침입자가 파일 이름에 대해 정렬하여 pipe 심볼로 시작하거나 공간으로 시작하고 끝나도록 한다면 open()이 특정 문자만 특별히 인터프리팅 하기 때문에 문제가 생긴다.

Perl의 빌트인 함수는 시작 공백과 끝 공백을 없앤다. 일반적으로 Perl에서는 open() 대신 sysopen()을 사용하는 것이 더 낫다. (perlopentutperlfunc 참조).

명령행 프로그램을 호출할 때 주의하라. 대부분의 유닉스 계열 프로그램들은 대시(-)를 선행시켜 옵션을 나타낸다. Windows 프로그램들은 일반적으로 슬래시(/)를 사용하지만 가끔은 대시를 사용하여 옵션임을 나타낸다. 침입자가 선행하는 대시 또는 슬래시를 명령행 프로그램으로 전달될 어떤 것 앞에 둔다면 옵션처럼 잘못 인터프리팅 된다.

C (C++ 포함)이외의 대부분의 언어들은 삽입된 NUL 문자로 스트링을 쉽게 저장할 수 있다. 반면 C 언어는 NUL 문자를 사용하여 스트링의 끝을 표시한다. 대부분의 C 루틴들은 중간에서 NUL 문자를 가진 스트링을 핸들 할 수 없다. 이러한 차이는 미미한 것 처럼 보이지만 다음을 생각해보자: 많은 라이브러리들(OS 시스템 호출 포함)은 C 규약을 사용한다. 실질적으로 무엇이든 C 규약을 사용할 수 있다. 따라서 삽입된 NUL 문자를 가진 스트링이 C 규약을 사용하여 라이브러리로 전달된다면 스트링은 갑자기 첫 번째 NUL에서 중단된다. 침입자들은 이것 또는 이와 비슷한 트릭을 사용하여 애플리케이션 프로그램이 보는 것과 호출된 루틴이 보는 것이 다르다는 것을 확인한다.




위로


리턴 값과 예외

데이터를 정확히 보내는 것으로는 충분하지 않다. 들어오는 어떤 것이든 정확히 핸들해야 한다.

응답을 정확히 핸들링하는 것은 C에서 보안 코드를 작성할 때 가장 큰 문제들 중 하나이다. (C는 버퍼 오버플로우를 피하기 힘들다.) C는 예외를 처리하는 빌트인 기능이 포함되어 있지 않기 때문에 기본적으로 어떤 함수 리턴이든 무시한다. 에러를 리턴할 수 있는 함수를 호출할 때 마다 요청이 기대한 결과를 만들어 냈는지 신중하게 검사해야 한다. (그렇지 않을 경우 에러를 처리해야 한다.) 침입자가 이를 야기시킬 수 있으므로 이 경우도 대비해야 한다.

예를 들어, read(2)는 요청된 바이트 숫자 이하를 읽을 수 있고 write(2)는 요청된 바이트 숫자 이하를 작성할 수 있다. 어떤 것은 그렇지 않지만 gcc 사용자들은 -Wreturn-type을 (-Wall의 일부), 사용하기 바란다. "(void)" 를 선행시킴으로서 값을 없앤다. 하지만 신중해야 한다.

이것은 우선 고통이 될 수 있다. 어떤 프로그래머들은 printf() 와 다른 많은 함수들이 실제로 값을 리턴한다는 것을 깨닫지 못하고 있다. 보안에 대해 염려한다면 이것을 깨달아야 한다. C에서 정말로 안전한 프로그램은 "정상" 프로세싱을 핸들하는 작은 양의 코드를 가진 에러 핸들링 코드이다.

감사하게도 대부분의 다른 프로그래밍 언어들에는 예외 핸들링이 포함되어 있어서 에러를 처리하기 더 쉽다. 하지만 너무 기뻐하지는 말라. 어떤 호출에서 어떤 예외가 던져졌으며 적절히 처리하는 방법을 알아야 한다. 특히 반드시 잠금을 해제하고 침입자들이 보낸 데이터로 인한 전채 애플리케이션의 충돌을 피해야 한다.

침입자가 리턴 된 데이터에 영향을 끼칠 수 있다면 조심하라. 예를 들어, DNS resolver를 호출하여 정보를 얻는다면 이 데이터는 침입자가 직접 제공한 데이터임을 기억하라. 이를 조심스럽게 다루고 포맷 또는 크기도 가정하지 말고 믿을 수 있을 때까지 믿어서도 안된다.

인풋인 것처럼 보이는 임의의 데이터를 핸들 할 때 조심하라. (진짜 인풋일수도 있다.) NUL 문자, 무효 문자, 문제를 일으킬 것들을 포함했을 수도 있다.




위로


애플리케이션과 컴포넌트 간 데이터 보호하기

침입자가 애플리케이션과 호출하고 있는 컴포넌트 간 흐르는 데이터와 인터페이싱 또는 읽을 수 없도록 한다. 기저의 OS에 시스템 호출할 할 때 이는 문제가 되지 않는다. (적어도 직접적으로는...)

특히 setuid/setgid 프로그램을 실행한다면 사용자들이 라이브러리 호출을 리다이렉트 하도록 하는 방식은 불가능하고 앞서 언급했듯이, setuid/setgid 프로그램은 환경변수들을 지워 호출하는 것이 무엇이든 보호 받도록 해야 한다.

그러나 그러한 방식으로는 인터넷이라는 광야를 감당할 수 없다. 웹 서비스로의 call을 호출한다면 웹 서비스를 신뢰한다 하더라도 침입자가 애플리케이션과 사용하고 있는 서비스 간 통신과 인터페이싱 할 수 없도록 해야 한다.

기밀성과 무결성을 유지하는 전형적인 솔루션은 이미 존재하고 있는 보안 프로토콜과 암호 알고리즘을 사용하는 것이다. 프로토콜과 알고리즘을 다시 만들지 말라. 일반적인 보안 프로토콜에는 TLS/SSL, OpenSSH, IPSec 등이 있다. RSA, SHA-1, AES, Triple-DES 같은 암호 알고리즘을 적용할 것이다.

암호화하고 인증하는 서비스로의 보안 연결을 만드는 것 만으로도 서비스로의 연에 기밀성과 무결성을 확보할 수 있다. 첫 번째 단계는 이 프로토콜과 알고리즘들이 필요하다는 것을 인식하는 것이다.




위로


이제는 안전하다!

불안전한 방식으로 다른 컴포넌트에 의존한다면 안전한 프로그램을 가질 수 없다. 이 글에서 침입의 일반적인 종류들을 언급했다. 다른 컴포넌트들을 안전하게 호출하더라도 아웃풋을 다시 보내는 방식이 모든 일을 그르칠 수 있다.

침입자들은 프로그램 아웃풋을 이용할 줄 안다. 놀라운 일이 아니다. 다음 글에서는 그와 같은 종류의 침임을 어떻게 다루는지를 설명하겠다.




위로


참고자료




위로


필자소개

David A. Wheeler

David A. Wheeler는 컴퓨터 보안 전문가이다.





위로


기사에 대한 평가

매우 불만족 (1)
불만족 (2)
보통 (3)
만족 (4)
매우 만족 (5)




위로



    IBM 소개개인정보 보호정책문의