4.7. 인간 언어 (로케일) 선택

더욱 많은 사람들이 컴퓨터를 갖게되고 인터넷을 이용할 수 있게 됨에 따라 많은 언어와 문화를 지원하는 프로그램에 대한 요구가 점점 증대되어 왔다. 이러한 언어와 다른 문화적 요소의 결합을 보통 ``로케일 (locale)" 이라고 한다. 다수의 로케일을 지원할 수 있도록 프로그램을 수정하는 과정과 프로그램에 각 로케일의 정보를 제공하는 과정을 각각 ``국제화 (internalization)" (i18n) 와 ``지역화 (localization)" (l10n) 라고 한다.

전반적으로 국제화는 훌륭한 작업이지만 보안 악용에 대한 다른 기회를 제공한다. 잠재적으로 신뢰할 수 없는 사용자가 원하는 로케일에 대한 정보를 제공하기 때문에 로케일 선택은 적절히 보호되지 못한다면 공격에 악용될 수 있는 또다른 입력이 된다.

4.7.1. 로케일 선택 방법

setuid/setgid 프로그램을 포함하여 로컬로 실행되는 프로그램에서 로케일 정보는 환경 변수에 의해 제공된다. 따라서 다른 모든 환경 변수와 같이 이 값은 추출되어 사용전 유효한 패턴에 대해 검사되어야 한다.

웹 애플리케이션의 경우 이 정보는 (Accept-Language request header 를 통해) 웹 브라우저에서 얻을 수 있다. 그러나 모든 웹 브라우저가 이 정보를 적절히 넘겨주지 않기 때문에 (모든 사용자가 적절히 웹 브라우저를 설정하지 못하기 때문에) 생각하는 것보다 그다지 많이 사용되지 않고 있다. 대개 웹 브라우저에서 요청된 언어는 폼 값으로 단순히 넘겨지는데 이 값은 다른 모든 폼 값과 마찬가지로 사용전에 그 타당성이 검사되어야 한다.

각각의 경우에 있어 로케일 정보는 정말로 이전 절에서 논의한 입력의 특별한 경우이다. 그러나 이 입력은 거의 고려되고 있지 않기 때문에 저자는 이를 별도로 논의하는 것이다. 특히 뒤에서 논의할 포맷 문자열과 결합될 때 사용자가 제어하는 문자열은 공격자로 하여금 다른 프로그램으로 임의의 명령 실행, 데이타 파괴 및 다른 부적당한 행위를 할 수 있게 한다.

4.7.2. 로케일 지원 메카니즘

유닉스 계열 시스템에는 로케일 선택 메세지를 지원하는 ``catgets" 와 ``gettext" 라 불리는 두 중요한 라이브러리 인터페이스가 있다. catgets 방법에서 모든 문자열에는 메시지 테이블을 색인하는데 사용될 수 있는 고유 숫자가 할당된다. 반대로 gettext 방법에서는 원래 문자열을 전환시킨 테이블을 검색하는데 문자열 (보통 영어로 된) 이 사용된다. catgets(3) 은 X/Open Portability Guide, Volume 3 과 Single Unix Specificatoin 을 통해 수용된 표준이며 따라서 프로그램이 이를 사용할 수 있다. gettext 인터페이스는 본래 UniForum 제안임에도 불구하고 공식 표준은 아닌데 저자는 이것이 보다 널리 사용되고 있는 인터페이스라고 생각한다 (썬과 본질적으로 모든 GNU 프로그램에 의해 사용되고 있다).

이론상 catgets 가 약간 더 빠르지만 오늘날 머신에서는 기껏해야 최소한 (이차적인 문제) 이며 catgets() 에서 고유 식별자를 유효하게 하려는 부기 (bookkeeping) 노력은 gettext() 인터페이스를 더욱 사용하기 쉽게 만들고 있다. 저자는 단지 gettext() 가 사용하기에 더욱 쉽기 때문에 이를 사용하라고 권해왔다. 그러나 저자의 말을 곧이듣지는 마라; 더욱 길고 설명적인 비교를 위해서는 gettext 에 대한 GNU 문서 (info:gettext#catgets) 를 보라.

catget(3) 호출 (과 관련 catopen(3) 호출) 은 환경 변수 NLSPATH 가 국제화된 메시지를 얻는데 사용되는 파일이름을 제어하는데 사용될 수 있기 때문에 특히 보안 문제에 취약하다. GNU C 라이브러리는 setuid/setgid 프로그램에 대해 NLSPATH 를 무시하는데 이는 유용하지만 다른 구현에서 실행되고 있는 프로그램을 보호하지 못하며 또한 이러한 보호가 필요치 않아 보이는 CGI 스크립트와 같은 다른 프로그램도 보호하지 못한다.

널리 사용되고 있는 ``gettext" 인터페이스는 저자가 알기로 악의성있는 NLSPATH 설정에 적어도 공격받을 가능성은 없다. 그러나 저자는 악의성있는 LC_ALL 또는 LC_MESSAGES 설정은 문제를 야기할 수 있을 것같다고 생각한다. 또한 cat-compat.c 파일내 gettext 의 bindtextdomain() 루틴을 사용한다면 이는 NLSPATH 에 의존한다.

4.7.3. 합법적인 값

우선 신뢰할 수 없는 사용자에게 원하는 로케일에 대한 정보 설정을 허용한다면 제공된 국제화 정보가 단지 규칙에 맞는 로케일 이름만을 허용하는 엄밀한 (narrow) 필터를 만족시키는지 확인해라. 사용자 프로그램 (특히 setuid/setgid 프로그램) 의 경우 이러한 값은 NLSPATH, LANGUAGE, LANG, 예전 LINGUAS, LC_ALL 과 다른 LC_* (특히 LC_MESSAGES 및 LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC 와 LC_TIME 를 포함) 값을 통해 들어올 것이다. 웹 애플리케이션의 경우 사용자가 요청한 언어 정보의 설정은 Accept-Language request header 또는 폼 값을 통해 이루어 질 것이다 (애플리케이션은 Content-Language heading 를 통해 반환되는 데이타의 실제 언어 설정을 나타내야 한다). 사용자가 환경 변수를 설정할 수 있거나 (예, setuid/setgid 프로그램) 입력 필터링의 일부분으로 설정할 수 있다면 (예, CGI 스크립트에 대해) 환경 변수 필터링시 이 값을 검사해야 한다. GNU C 라이브러리 glibc 는 setuid/setgid 프로그램에 대해 LANG 에 몇몇 값 (특히 "/" 로 시작하는 모든 값) 을 허용하지 않지만 필터링에서 에러가 발견되어 왔다 (레드햇사는 2000년 9월 1일에 glibc 버그 수정을 하여 갱신판을 배포하였다). 이러한 종류의 필터링이 모든 표준이 필요로 하지는 않는데 따라서 각자 자신이 필터링하는 것이 더욱 안전하다. 저자는 언어 설정 필터링에 대한 어떤 본보기도 발견하지 못했으며 다음은 이 문제에 대한 저자의 연구에 기초한 제안이다.

우선 합법적인 설정 값에 대해 몇마디. 언어 설정은 일반적으로 IETF RFC 1766 에 정의된 표준 태그를 사용하여 설정된다 (기본 태그로 두 문자의 국가 코드를 사용하며 대시기호로 구분되는 선택적인 하부 태그가 따를 수 있다; 저자는 환경 변수 설정이 대신 밑줄을 사용함을 발견했다). 그러나 이것은 그다지 유연하지 않으며 따라서 세 문자의 국가 코드가 곧 사용될 수도 있다. 또한 두개의 주요하지만 그다지 호환되지 않는 확장 포맷인 X/Open 포맷과 CEN 포맷 (European Community Standard) 이 있다; 둘 모두를 허용하고 싶을지도 모른다. 일반적인 값은 ``C" (C 로케일), ``EN" (영어), ``FR_fr" ( 프랑스 영역권의 프랑스어) 를 포함한다. 또한 많은 사람들이 표준이 아닌 이름을 사용하고 있는데 이는 이 문제에 대처하기 위해 ``alias" 시스템을 개발하기 위해 프로그램이 가졌던 이름이다 (GNU gettext 와 X11 경우 각각 /usr/share/locale/locale.alises 와 /usr/lib/X11/locale/locale.aliases 를 보라); 이들은 보통 허용되어야 한다. gettext() 같은 라이브러리는 모든 이러한 변형을 허용해야 하며 가능한 적절한 값을 찾아야 한다. 더욱 자세한 정보는 FSF [1999] 를 참조하는 것이 좋다. 그러나 필요치 않은 문자, 특히 (신뢰할 수 있는 디렉토리에서 벗어날 수 있게 하는) ``/" 과 (상위 디렉토리로 이동할 수 있게 하는) ``.." 문자는 필터가 허용하지 않아야 한다. NLSPATH 에서 다른 위험한 문자는 (대체를 나타내는) ``%" 과 (디렉토리 구분자인) ``:" 이다; 다른 머신에 대한 문서에서는 어떤 구현은 다른 값을 위해 이들을 사용할 수 있다고 제안하지만 이들을 금지하는 것이 가장 안전한 것이다.

4.7.4. 최종 결과

요약하면 저자는 신뢰된 사용자가 값을 제공하지 않도록 한다면 NLSPATH 를 단순히 지우거나 재설정하도록 권한다. HTTP 에서 Accept-Language heading, 로케일을 지정하는 폼 값 및 LANGUAGE, LANG, 예전 LINGUAS, LC_ALL 과 다른 LC_* 값에 대해 null (empty) 값을 허용하거나 또는 다음의 정규 표현식과 전부 일치하는 값들만을 허용하기 위해 신뢰되지 않은 사용자로부터의 로케일을 필터링해라:

 [A-Za-z][A-Za-z0-9_,+@\-\.]*

저자는 이 패턴과 일치하지 않는 어떠한 합법적인 로케일도 발견하지 못했지만, 이 패턴은 로케일 공격에 대해서 보호하는 것처럼 보인다. 물론 요청된 로케일에서 얻을 수 있는 메시지가 있다고 보증할 수는 없지만 그러한 경우 이런 루틴들은 디폴트 메시지 (보통 영어)로 돌아갈 것이며 적어도 이것이 보안 관련 문제가 되지는 않는다.

물론 언어는 성문 (written) 심볼을 나타내는 표준적인 방식으로 지원되어야 하며 이는 문자 인코딩 논의로 이끈다.