4.2. 환경 변수

디폴트로 환경 변수는 부모 프로세스로부터 상속된다. 그러나 프로그램이 다른 프로그램을 실행시킬 때 호출하는 프로그램이 환경 변수를 임의의 값으로 설정할 수 있다. 이는 setuid/setgid 프로그램의 경우 이들의 호출자가 주어진 환경 변수들을 완벽히 제어할 수 있기 때문에 위험하다. 환경 변수는 보통 상속되기 때문에 이는 또한 과도적으로 적용되는데 보안적인 프로그램이 어떤 다른 프로그램을 호출할 수도 있으며 특별한 조치가 없다면 잠재적으로 위험한 환경 변수를 자신이 호출한 프로그램에 넘겨줄 수 있다. 다음 하부 절은 환경 변수 및 이를 갖고 하는 일을 논의한다.

4.2.1. 몇몇 환경 변수는 위험하다

몇몇 환경 변수들은 모호한, 미묘한 또는 비공인된 (undocumented) 방식으로 많은 라이브러리와 프로그램을 제어하기 때문에 위험하다. 예를 들어 IFS 변수는 어떤 문자가 명령 행 인수들을 구분하는 지를 결정하기 위해 shbash 쉘에 의해 사용된다. 쉘은 몇가지 하위 수준 호출 (C 에서 system(3) 과 popen(3) 또는 펄에서 back-tick 연산자) 에 의해 호출되기 때문에 IFS 변수를 예외적인 값으로 설정한다면 명백히 안전한 호출을 파괴할 것이다. 이 동작은 sh 와 bash 에 문서화되어 있지만 그 의미는 모호하다; 많은 오래된 사용자들만이 실제로 의도된 목적에 사용하기 때문이 아니라 보안을 깨뜨리는데 IFS 변수를 사용하기 때문에 이에 대해 알고 있다. 다욱 바람직하지 않은 것은 모든 환경 변수가 문서화되어 있지 않으며 있다고 하더라도 다른 프로그램이 위험한 환경 변수를 변경 및 추가할 수도 있다는 것이다. 따라서 이 문제에 대한 유일한 실제 해결책 (밑부분에 기술되듯이) 은 필요한 환경 변수만 선택하고 나머지 모든 변수는 버리는 것이다.

4.2.2. 환경 변수 저장 포맷은 위험하다

일반적으로 프로그램은 환경 변수에 접근하기 위해 표준 접근 루틴을 사용하는데 예를 들어, C 에서는 getenv(3) 을 이용하여 환경 변수를 얻고 POSIX 표준 루틴 putenv(3) 또는 BSD 확장 setenv(3) 를 이용하여 이를 설정하며 unsetenv(3) 을 이용하여 이를 제거한다. setenv(3) 는 리눅스에서도 구현되어 있음을 저자는 언급한다.

그러나 크랙커가 그렇게 영리할 필요는 없으며 크랙커는 execve(2) 를 사용하여 프로그램에 넘겨지는 환경 변수 데이타 영역을 직접적으로 제어할 수 있다. 이는 다소의 다루기 힘든 공격을 허용하며 환경 변수가 실제로 어떻게 작동하는지 알아야만 이해할 수 있다. 리눅스에서 환경 변수의 실제 작동 방법에 대한 요약은 environ(5) 을 볼 수 있다. 요약하면 환경 변수는 내부적으로 문자에 대한 포인터 배열의 포인터로 저장되는데 이 배열은 순서적으로 저장되며 NULL 포인터 (이를 통해 배열이 언제 끝나는지를 알 수 있다) 로 끝난다. 문자에 대한 포인터들은 차례로 ``NAME=value" 형태의 NIL 로 끝나는 문자열 값을 가리킨다. 이는 몇가지 함축된 의미을 갖는데 예를 들어 환경 변수 이름은 = 기호 를 포함할 수 없으며 이름과 값은 NIL 문자를 내장할 수 없다. 그러나 이 포맷의 더욱 위험한 함축된 의미는 동일 변수 이름을 갖으나 다른 값을 갖는 다중 엔트리를 허용한다는 것이다 (예, SHELL 에 대해 한가지 이상의 값). 일반적인 명령 쉘은 이를 금지하는 반면 지역적으로 작업을 하는 크랙커는 execve(2) 를 이용하여 이런 상황을 만들 수 있다.

이런 스토리지 포맷(과 설정되는 방식) 과 관련된 문제는 프로그램이 이 값을이 유효한지 보기 위해 이러한 값들 중 하나를 검사할 수 있지만 실제로는 다른 값을 사용할 수도 있다는 것이다. 리눅스에서 GNU glibc 는 이로부터 프로그램을 보호하려고 하는데 glibc 2.1 에서 getenv 은 늘 처음 일치하는 엔트리를 얻고 setenv 와 putenv 는 늘 처음 일치하는 엔트리를 설정하며 unsetenv 는 실제로 모든 일치하는 엔트리의 설정을 해제할 것이다 (이런 방식으로 unsetenv 를 구현한 GNU glibc 구현자에게 축하!). 그러나 몇몇 프로그램은 직접적으로 환경 변수로 가서 모든 환경 변수에 대해 반복 적용하는데 이런 경우 처음이 아닌 마지막으로 일치하는 엔트리를 사용할 수도 있다. 따라서 처음 일치하는 엔트리에 대해서는 크래커로부터 보호할 수 있지만 실제 사용된 값이 마지막으로 일치하는 엔트리라면 크랙커는 이를 이용해 보호 루틴을 피할 것이다.

4.2.3. 해결방안 - 추출 및 제거

보안적인 setuid/setgid 프로그램에 대해 입력될 필요가 있는 환경 변수의 간단한 리스트가 주의깊게 추출되어야 한다. 그 후 전체 환경 변수를 지우고 필요한 약간의 환경 변수들을 안전한 값으로 재설정해야 한다. 하위 프로그램을 호출한다면 실제로 더욱 좋은 방법은 없다: 모든 위험한 값을 열거할 수 있는 실제적인 방법은 없다. 직접적 또는 간접적으로 호출할 모든 프로그램의 소스 코드를 검토한다고 하더라도 코드를 작성한 후 누군가가 새로운 비공인된 환경 변수를 추가할 수 있으며 이들 중 하나를 공격에 이용할 수도 있다.

C/C++ 에서 환경을 지우는 간단한 방법은 전역 변수 environ 를 NULL 로 설정하는 것이다. 전역 변수 environ 은 <unistd.h> 파일에 정의되어 있는데 C/C++ 사용자는 이 헤더 파일을#include 를 써서 이용할 수 있다. 쓰레드를 생성하기 전에 environ 을 조작할 필요가 있지만 프로그램 실행 초기 (보통 쓰레드가 생성되기 전) 에 이를 조작하기 때문에 거의 문제가 되지 않는다.

전역 변수 environ 의 정의는 여러가지 표준에 정의되어 있다; 공식적 표준이 이 값의 직접적 변경을 묵과하는 지는 명확하지 않으며 저자는 이를 변경하는데 있어 문제가 있었던 유닉스 계열 시스템은 잘 모르고 있다. 저자는 보통 environ 을 직접적으로 수정한다; 이러한 하위 수준 컴포턴트를 조작하는 것은 아마도 이식가능하지 않지만 이는 깨끗한 (안전한) 환경을 얻을 수 있게 보증한다. 추후 전체 변수 집합에 접근할 필요가 있는 드문 경우에는 environ 변수 값을 어디든 저장할 수 있으나 이는 거의 필요하지 않다; 거의 모든 프로그램은 이들중 단지 일부 값만을 필요로 하며 나머지는 버린다.

환경을 지우는 또 다른 방법은 비공인된 clearenv() 함수를 사용하는 것이다. clearenv() 함수는 별다른 역사를 갖고 있는데 이는 POSIX 1 에 정의되어 있다고 알려져 있지만 어떤 연유인지 전혀 이 표준에 포함되지 않았다. 그러나 clearenv() 는 POSIX 9 (포트란 77 의 POSIX 바인딩) 에 정의되어 있으며 따라서 이에 대해 준 공식 상태로 있다. 리눅스에서 clearenv() 는 <stdlib.h> 파일에 정의되어 있지만 #include 를 써서 포함하기 전에 __USE_MISC 가 #define 에 의해 정의되어 있는지 확인해야 한다. 약간 더욱더 공식적인 접근 방법은 __USE_MISC 를 정의해서 _SVID_SOURCE 또는 _BSD_SOURCE 가 #define 에 의해 정의되게 하여 <features.h> 파일을 #include 에 의해 포함하는 것이다 - 이는 공식적인 특징을 갖는 테스트 매크로이다.

거의 확실히 재추가해야 하는 환경 변수는 프로그램 실행을 위해 검색되는 디렉토리 목록인 PATH 이다; PATH 는 현재 디렉토리를 포함하지 않아야 하며 보통 ``/bin:/usr/bin" 과 같이 간단한 형태이다. 일반적으로 IFS (디폴트는 공백이 첫 문자인 `` \t\n") 와 TZ (timezone, 시간대) 를 설정할 수 있다. 리눅스가 IFS 또는 TZ 가 설정되지 않았다고 해서 작동하지 않는 것은 아니지만 어떤 System V 에 기초한 시스템은 TZ 값을 설정하지 않는 경우 문제의 소지가 된다. 어떤 쉘은 IFS 값이 설정되어야 한다는 루머도 있다. 리눅스에서 설정할 수 있는 공통된 환경 변수 목록을 얻기 위해서는 environ(5) 를 보라.

실제로 사용자가 제공한 값들이 필요하다면 그 값이 합법적인 값에 대한 패턴과 일치하는지 어떤 합당한 최대 길이내에 있는지를 보증하기 위해 우선적으로 검사해라. 원칙적으로는 /etc 디렉토리에 표준적인 안전한 환경 변수 값에 대한 정보를 갖고 있는 신뢰할 수 있는 표준 파일이 있을 수도 있지만 현재 이러한 목적을 위해 정의된 표준 파일은 없다. 비슷한 이유로 PAM 모듈을 갖고 있는 시스템에 대해 pam_env 를 조사할 수도 있다.

프로그래밍 언어로 쉘을 사용하려 한다면 ``-" 옵션을 사용해 ``/usr/bin/env" 프로그램을 사용할 수 있다 (실행되는 프로그램의 모든 환경 변수를 지운다). 기본적으로 /usr/bin/env 를 호출하여 이에 ``-" 옵션을 주고 설정하려고 하는 변수와 값 (name=value 형태로) 을 쓴 후 실행시킬 파일 이름과 그 인수를 설정한다. 대부분은 완전한 경로 이름 (/usr/bin/env) 을 사용하여 프로그램을 호출하길 원하는데 ``env" 와는 달리 사용자가 위험한 PATH 값을 만들 수 있다. GNU env 는 ``-i" 와 ``--ignore-environment" 옵션 (시작하는 프로그램의 환경을 지운다) 을 동일하게 허용함을 주목해라. 그러나 이를 다른 버전의 env 에 이식할 수는 없다.

환경을 직접적으로 재설정할 수 없도록 하는 언어로 setuid/setgid 프로그램을 작성한다면 다른 접근 방법은 ``wrapper" 프로그램을 생성하는 것이다. wrapper 는 환경 프로그램을 안전한 값으로 설정한 후 다른 프로그램을 호출한다. 주의: wrapper 가 실제로 의도하는 프로그램을 호출할 것인지를 확인해라; 인터프리티드 프로그램이라면 인터프리터로 하여금 특별한 setuid/setgid 권한이 주어진 프로그램외에 다른 프로그램을 적재할 수 있게 하는 가능한 경쟁 상태가 없음을 확인해라.