Wisdom will save you from the ways of wicked men, from men whose words are perverse... | |
Proverbs 2:12 (NIV) |
어떤 입력은 신뢰할 수 없는 사용자로부터 올 수 있는데 이런 입력들은 사용하기 전에 그 정당성을 입증하거나 필터링되어야 한다. 무엇이 합법적인 것인가를 결정해서 그 정의와 일치하는 않는 모든 것을 거절해야 한다. 무엇이 비합법적인 것인가를 식별해서 그러한 경우를 거절하는 코드를 작성하는 것과 같은 반대의 경우는 하지마라. 비합법적인 입력의 중요한 실례를 다루는 것을 잊을 수 있기 때문이다.
그렇지만 비합법적인 값을 식별해야 하는 좋은 이유가 있는데 이는 반드시 확인 (validation) 코드를 철두철미하게 작성하기 위한 일련의 테스트 (보통 머리속에서 실행되는) 와 같다. 저자는 입력 필터를 설정할 때 필터링되지 않을 수 있는 비합법적인 값이 있는지 보기 위해 마음속으로 필터를 공격한다. 다음은 입력에 따라 입력 필터가 예방할 필요가 있는 공통된 비합법적인 값들의 예이다: 빈 문자열, ".", "..", "../", "/" 또는 "." 로 시작하는 모든 것, 안에 "/" 또는 "&" 가 있는 모든 것, 모든 제어 문자 (특히 NIL 과 개행) 와/또는 "high bit" 이 설정되어 있는 모든 문자 (특히 십진수 254 와 255 값). 다시 한번 작성한 코드는 "bad" 값에 대해 검사되지 않아야 한다: 반드시 패턴이 입력 값을 합법적인 값으로 엄격히 제한하도록 이를 마음속으로 검사해야 한다. 패턴이 충분히 엄밀하지 않다면 다른 문제가 있는 지를 살펴 보기 위해 패턴을 주의깊게 재조사해야 한다.
최대 문자 길이 (적절하다면 최소 길이) 를 제한하고 그러한 길이가 초과될 때 반드시 제어가 되도록 해라 (버퍼 오버플로우에 대한 더욱 많은 정보는 5장 을 보라).
다음은 신뢰되지 않은 사용자로부터 온 것들 사용하기 전에 확인해야 하는 약간의 공통적인 데이타 타입과 입력들이다:
문자열에 대해서는 합법적 문자 또는 합법적 패턴 (예, 정규 표현) 을 식별해서 그 형태와 일치하지 않는 모든 것을 거절해라. 문자열이 제어 문자 (특히 개행 또는 NIL) 또는 쉘 메타문자를 포함할 때는 특별한 문제가 있다; 대개 입력을 받자마자 그러한 메타문자가 뜻하지 않게 보내지지 않도록 그러한 메타문자를 이스케이프 ( escape, 의미 해제) 하는 것이 최선이다. CERT 는 더 나아가서 이스케이프를 필요로 하지 않는 문자 목록에 없는 모든 문자를 이스케이프하라고 추천한다 [CERT 1998, CMU 1998]. 호출 (call out) 제한에 대해 더욱 자세한 정보는 7.2절 을 보라.
모든 숫자를 최소 (대개 0) 와 최대 허용 값으로 제한해라.
완전한 이메일 주소 검사기는 실제 매우 복잡하다. 이는 모든 포맷을 지원하려는 경우 확인을 매우 복잡하게 하는 레거시 포맷들이 있기 때문이다; 이러한 검사가 필요하다면 더욱 자세한 정보는 mailaddr(7) 과 IETF RFC 822 [RFC 822] 를 보라. 대개 간단하게 할 수 있는데 단지 ``공통적인" 인터넷 주소 포맷만을 허용해라.
파일 이름도 확인해야 하는데 보통 ".." (상위 디렉토리를 의미한다) 를 신뢰되지 않은 사용자로부터의 합법적인 값으로 포함하길 원하지 않을 것이다. 물론 상황에 따라 변할 수 있다. 특히 문제를 야기할 수 있는 개행 문자를 빠뜨리면서 허용할 문자들만을 리스트하길 원할 수도 있다. 파일 이름에서 예를 들어 일련의 합법적인 문자 집합에 "/" 를 포함하지 않음으로써 디렉토리내의 모든 변경을 금지하는 것이 최선이다. 대개 ``*", ``?", ``[" (matching ``]") 과 ``{" (matching ``}") 들을 사용하여 파일 이름을 전개하는 ``globbing" 을 지원하지 않아야 한다. 예를 들어 ``ls *.png" 명령은 모든 PNG 파일들을 열거하도록 ``*.png" 를 globbing 한다. C 의 fopen(3) 명령은 globbing 하지 않지만 일반적인 쉘은 디폴트로 globbing 을 수행하며 C 에서는 glob(3) 을 사용해 globbing 을 요청할 수 있다. Globbing 을 필요로 하지 않는다면 가능한 globbing 하지 않는 호출 (예, fopen(3)) 만을 사용하거나 이를 금지 (예, 쉘에서 globbing 문자들을 이스케이프해라) 해라. Globbing 을 허용한다면 특별히 주의해라. Globbing 이 유용할 수 있지만 복잡한 glob 는 매우 많은 계산 시간이 걸릴 수 있다. 예를 들어 어떤 ftp 서버에서 이러한 몇개의 요청을 실행시킴으로써 쉽게 전체 머신에 대해 서비스 부인을 야기할 수 있다.
ftp> ls */../*/../*/../*/../*/../*/../*/../*/../*/../*/../*/../*/../* |
Globbing 패턴을 제한하면서 globbing 을 허용하려는 것은 아마도 허사일 것이다. 대신 반드시 그러한 모든 프로그램들을 별개 프로세스로 실행시키고 이들이 소비할 수 있는 CPU 와 다른 자원들의 양을 제한하는 프로세스 한계를 사용해라. 이에 대한 더욱 자세한 정보는 6.3.8절 를 보고 이러한 한계 설정 방법에 대한 더욱 자세한 정보는 3.6절 를 보라.
URL 을 포함하여 URI 는 그 타당성을 검사해야 한다. 웹 서버 또는 웹 서버와 같은 프로그램을 구현하고 있고 URL 이 데이타에 대한 요청인 경우와 같이 직접적으로 URI 에 따라 행동한다면 URI 가 합법적인지 반드시 확인하고 문서 루트 (서버가 응답하고 있는 파일 시스템의 영역) 를 ``이스케이프" 하려고 하는 URI 에 특히 주의해라. 문서 루트를 이스케이프하는 가장 일반적인 방법은 ``.." 또는 심볼릭 링크를 통하는 것인데 따라서 대부분의 서버는 모든 ``.." 디렉토리 자체를 검사하거나 특별히 관리되지 않느다면 심볼릭 링크를 무시한다. 또한 URL 인코딩 또는 UTF-8 인코딩을 통해 모든 인코딩 우선 디코딩하는 것을 기억해라. 그렇지 않으면 인코드된 ``.." 이 슬쩍 빠져나갈 수 있다. URI 는 UTF-8 인코딩을 포함하지 않도록 요구되고 있는데 따라서 가장 안전한 것은 high bit 이 설정되어 있는 문자들을 포함하는 모든 URI 를 거절하는 것이다.
URI/URL 을 데이타로 사용하는 시스템을 구현하고 있다면 전혀 성공하지 못한다. 악의있는 사용자가 다른 사용자들에게 피해를 입힐 수 있는 URI 를 삽입할 수 없도록 보장할 필요가 있다. 더욱 자세한 정보는 4.10.4절 를 보라.
쿠키값을 받아들일 때 사용하고 있는 모든 쿠키에 대한 도메인 값이 예상된 값인지를 반드시 확인해라. 그렇지 않으면 관련된 사이트 (아마도 파괴된 사이트) 가 변조된 쿠키를 삽입할 수도 있다. 다음은 이러한 검사를 하지 않는 것이 어떻게 문제를 야기할 수 있는가에 대한 IETF RFC 2965 로부터의 예이다:
사용자 에이전트가 victim.cracker.edu 에 요청을 하고 쿠키 session_id 1234 를 가져온 후 디폴트 도메인을 victim.cracker.edu 로 설정한다.
사용자 에이전트가 spoof.cracker.edu 에 요청을 하고 도메인이 ".cracker.edu" 로 설정되어 있는 쿠키 session_id 1111 을 가져온다.
사용자 에이전트가 victim.cracker.edu 에 다시 요청을 하고 다음을 건네준다:
Cookie: $Version="1"; session_id="1234", $Version="1"; session_id="1111"; $Domain=".cracker.edu" |
victim.cracker.edu 의 서버는 도메인 속성이 자신의 도메인 속성이 아님을 알아차림으로써 두번째 쿠키가 자신이 생성했던 것이 아님을 탐지해 이를 무시해야 한다.
위에 언급한 사항을 고려하지 않는다면 합법적 문자 패턴에 프로그램 내부 또는 결과로써 생기는 출력에 대해 특별한 의미를 갖는 문자들 또는 일련의 문자들을 포함하지 않아야 한다:
문자 시퀀스 (sequence) 는 프로그램 내부 스토리지 포맷에 대해 특별한 의미를 가질 수도 있다. 예를 들어 범위를 갖는 문자열 (delimited strings) 에 데이타를 (내부적 또는 외부적으로) 저장하려면 분리자 (delimiter) 가 허용된 데이타 값이 아님을 확인해라. 많은 프로그램은 콤마 (,) 또는 콜론 (:) 으로 분리된 텍스트 파일에 데이타를 저장하는데 입력에 분리자를 써넣을 때 프로그램이 이를 고려하지 않는다면 (즉, 분리자를 써넣는 것을 허용하지 않거나 몇몇 방식으로 이를 인코드함으로써) 문제가 생길 수 있다. 문자열을 둘러싸는데 사용되는 단독 및 이중 인용부호와 SGML, XML 과 HTML 에서 태그 시작을 나타내는 "<" (less-than) 들은 종종 이런 문제를 일으키는 문자들이다 (이 포맷으로 데이타를 저장하는 경우 이는 중요하다). 대부분의 데이타 포맷은 이런 경우를 다루기 위해 이스케이프 시퀀스를 갖고 있다; 이를 사용하거나 입력시 이런 데이타를 필터링해라.
문자 시퀀스가 사용자에게 되돌려지는 경우 이는 특별한 의미를 가질 수 있다. 일반적인 예는 추후 다른 구독자에게 게시될 수 있는 데이타 입력에 HTML 태그를 허용하는 경우이다 (예, 게스트북 또는 ``독자 주해" 영역). 그러나 이러한 문제는 더욱 일반적이며 이 주제에 대한 일반적인 논의와 HTML 필터링에 대한 특정한 논의를 보기 위해서는 각각 6.13절 과 4.10절 을 보라.
이러한 테스트는 추후 수정을 위해 타당성 테스트를 쉽게 살펴볼 수 있도록 한 곳에 집중되어 있어야 한다.
타당성 테스트가 실제로 정확함을 확인해라; 이는 다른 프로그램이 사용할 입력 (파일 이름, 이메일 주소 또는 URL 등) 을 검사할 때 특히 문제가 된다. 대개 이러한 테스트는 검사 프로그램과 실제 데이타를 사용하는 프로그램이 다른 가정을 하는 경우 미묘한 에러를 갖는데 소위 "대리 (deputy) 문제" 를 야기한다. 관련된 표준이 있다면 이를 살펴보고 또한 프로그램이 각자가 알아야 필요가 있는 확장을 가지고 있는지 살펴보기 위해 검색해라.
사용자 입력을 해석할 때 일시적으로 모든 권한을 없애거나 더 나아가서 별개의 프로세스를 생성하는 것 (파서의 권한을 영구적으로 없애고 다른 프로세스가 파서 요청에 대해 보안 검사를 수행한다) 은 좋은 생각이다. 이는 파싱 태스크가 복잡하거나 (lex 또는 yacc 와 유사한 도구를 사용하는 경우) 또는 프로그래밍 언어 (예, C 와 C++) 가 버퍼 오버플로우에 대해 보호하지 않는다면 특히 들어맞는다. 권한 최소화에 대해 더욱 자세한 정보를 얻기 위해서는 6.3절 을 보라.
보안 결정 (예, 사용자에게 로그인 허용) 에 데이타를 사용할 때 반드시 신뢰할 수 있는 채널을 사용해라. 예를 들어 공개 인터넷에서 사용자를 인증하는 유일한 방법으로 머신 IP 주소 또는 포트 넘버를 사용하지 마라. 대부분의 환경에서 이 정보는 어쩌면 악의있는 사용자에 의해 설정될 수 있다. 더욱 자세한 정보는 6.9절 를 보라.
다음 하부 절은 다양한 종류의 입력을 논의한다; 입력은 환경 변수, umask 값 등과 같은 프로세스 상태를 포함함을 주목해라. 모든 입력이 신뢰할 수 없는 사용자에 의해 제어될 수는 없는데 따라서 이러한 사용자가 제어할 수 있는 그러한 입력에 대해서만 주의를 기울이면 된다.