6.3. 권한을 최소화해라

앞에서 언급했듯이 프로그램이 그 작업을 하는데 필요한 최소한의 권한을 갖는 것은 중요한 일반적인 원리이다 (이는 "최소한의 권한" 이라고 지칭된다). 이런 방식에서 프로그램이 파괴된다하더라고 그 손상은 제한된다. 가장 극단적인 예는 단순히 보안적인 프로그램을 전혀 작성하지 않는 것이다 -이럴 수 있다면 보통 이렇게 해야 한다. 예를 들어 가능하다면 프로그램을 setuid 또는 setgid 프로그램으로 만들지 마라; 그저 일반적인 프로그램을 만들어 이를 실행시키기 전에 관리자가 그 자격으로 로그인하도록 해라.

리눅스와 유닉스에서 프로세스의 권한을 기본적으로 결정하는 것은 프로세스와 관련된 id 셋이다; 각 프로세스는 사용자와 그룹에 대해 실제 (real), 유효 (effective) 및 유보 (saved) id 를 갖고 있다. 리눅스는 또한 파일시스템 uid 와 gid 를 갖는다. 이러한 값을 조작하는 것은 권한을 최소한으로 유지하는데 있어서 결정적인데 이를 최소화하는 몇가지 방법이 있다 (밑부분에 논의된다). 또한 chroot(2) 를 이용해서 프로그램이 볼 수 있는 파일을 최소화할 수 있다. 리눅스와 유닉스에서는 권한을 결정하는 약간의 다른 값들이 있는데 예를 들어 POSIX 능력 (리눅스 2.2 이상 및 몇몇 다른 유닉스 계열 시스템이 이를 지원한다) 이 있다.

6.3.1. 허가되는 권한을 최소화해라

아마 가장 효과적인 기법은 허가된 가장 높은 권환을 단순히 최소화하는 것이다. 특히 프로그램에게 가능한 루트 권한을 주지 않도록 해라. 프로그램이 단지 작은 파일셋에 접근할 필요만 있다면 프로그램을 setuid root 로 만들지 마라; 여러 기능에 대해 별개의 사용자 또는 그룹 생성을 고려해라.

공통적인 기법은 특별 그룹을 생성해서 파일의 그룹 소유권을 이 그룹으로 변경한 후 프로그램을 이 그룹에 setgid 로 만드는 것이다. 그룹 멤버십이 더욱 적은 권한을 허가하기 때문에 (특히 파일 허가권을 변경할 권한을 주지는 않는다) 프로그램을 setuid 대신 setgid 로 만드는 것이 더욱 좋다.

이는 게임에서 고득점의 경우에 보통 행해진다. 게임은 대개 setgid games 으로 득점 파일은 그룹 games 가 소유하고 프로그램 그 자체와 이들의 설정 파일은 다른 누군가 (루트) 가 소유한다. 따라서 게임에 침입한 공격자가 고득점을 변경할 수는 있지만 게임의 실행 또는 그 설정 파일을 변경할 권한을 얻지는 못한다. 후자는 중요하다; 공격자가 게임의 실행 또는 그 설정 파일 (실행 파일이 무엇을 작동시킬 건지를 제어할 수 있는) 을 변경할 수 있다면 게임을 즐기는 사용자들을 제어할 수도 있다.

새로운 그룹 생성이 충분하지 않다면 일련의 자원들을 관리하는 새로운 준사용자 (실제로 특별한 역할을 하는) 생성을 고려해라. 웹서버가 전형적으로 이런 방식인데 때때로 웹서버는 다른 사용자와 분리될 수 있도록 특별한 사용자 (``nobody") 로 설정된다. 정말로 웹서버는 이점에서 교육적이다: 웹서버는 구동하기 위해 전형적으로 루트 권한을 필요로 하지만 (포트 80 에 부착될 수 있도록) 일단 시작되면 대개 모든 권한을 버리고 ``nobody" 사용자로서 작동한다. 대개 준사용자는 자기가 실행시킨 기본 프로그램을 소유하지 않으며 따라서 그 계정에 침입한다고 해서 프로그램 자체를 변경할 수는 없다. 그 결과로 작동중인 웹서버에 침입하는 것이 일반적으로 전체 시스템의 보안을 자동적으로 파괴하지는 못한다.

프로그램에 루트 권한을 주어야 한다면 프로그램이 이용할 수 있는 권한을 최소화하자마자 POSIX 능력 사용을 고려해라. POSIX 는 리눅스 2.2 이상 및 다른 유닉스 계열 사용할 수 있다. 프로그램 시작후에 즉각적으로 cap_set_proc(3) 또는 리눅스 고유의 capsetp(3) 을 호출함으로써 프로그램의 능력을 영구히 단지 실제 필요로 하는 능력으로 감소시킬 수 있다. 예를 들어 네트워크 시간 데몬 ( ntpd) 는 현재 시간을 수정할 필요가 있기때문에 전통적으로 루트 권한으로 실행된다. 그러나 ntpd 가 단지 하나의 능력 CAP_SYS_TIME 만이 필요하도록 패치가 개발되었으며 따라서 공격자가 ntpd 에 대한 제어를 얻는다 하더라도 프로그램을 악용하는 것은 더욱 어려울 것이다.

다른 조치가 취해지지 않는다면 POSIX 능력을 사용해 권한을 유지하는 것은 프로세스가 루트 사용자 id 를 계속해서 가져야 함을 요구하기 때문에 저자는 ``어느 정도 제한적" 이라고 말하고 싶다. 설정 파일, 바이너리 등의 많은 중요한 파일은 루트가 소유해야 하기 때문에 이러한 제한된 능력을 갖는 프로그램을 제어하는 공격자는 여전히 주요 시스템 파일 수정 및 완전한 루트 수준의 권한을 얻을 수 있다. 리눅스 커널 확장 (버전 2.4.X 및 2.2.19 이상에서 쓸 수 있다) 은 사용가능한 권한을 제한하는 더욱 좋은 방법을 제공한다; 프로그램이 모든 POSIX 능력을 갖는 루트로 실행되어 불필요한 능력은 제거하고 prctl(PR_SET_KEEPCAPS,1) 을 호출한 후 루트가 아닌 프로세스로 변경하기 위해 setuid() 를 사용할 수 있다. PR_SET_KEEPCAPS 설정은 프로세스가 0 이 아닌 값으로 setuid 를 할 때 POSIX 능력이 제거되지 않도록 프로세스에 표시를 하며 이 프로세스 설정은 exec() 에서 제거된다. 그러나 PR_SET_KEEPCAPS 는 더욱 새로운 리눅스 커널 버전에 대한 리눅스 고유의 확장이라는 것에 주의해라.

허가되는 권한의 최소화를 단순하게 하기 위해 사용할 수 있는 리눅스 고유의 한가지 도구는 SuSe 에 의해 개발된 ``compartment" 도구이다. 이는 파일시스템 루트, uid gid 및/또는 capability set 을 설정한후 프로그램을 실행시킨다. 이는 특히 어떤 다른 프로그램을 수정하지 않고 실행시키는데 편리하게 이용할 수 있다. 다음은 0.5 버전의 구문이다:


Syntax: compartment [options] /full/path/to/program

Options:
   --chroot path    path 로 chroot 한다
   --user user      uid 를 user 의 uid 로 변경한다
   --group group    gid 를 group 의 gid 로 변경한다
   --init program   최우선적으로 이 programe 을 실행시킨다
   --cap capset     capset 이름을 설정한다. 여러번 지정할 수 있다
   --verbose        자세한 정보를 보여준다
   --quiet          syslog 에 기록하지 않는다

따라서 anonymous ftp 서버를 더욱 안전하게 구동시킬 수 있다:

  compartment --chroot /home/ftp --cap CAP_NET_BIND_SERVICE anon-ftpd

이 문서 작성 시점에서 도구는 미완성이고 전형적인 리눅스 배포판에서 얻을 수는 없지만 상황은 빠르게 변할 것이다. http://www.suse.de/~marc 에서 프로그램을 다운로드받을 수 있다.

모든 유닉스 계열 시스템이 POSIX 능력을 구현하지는 않으며 PR_SET_KEEPCAPS 은 현재 리눅스에서만 지원함을 주목해라. 따라서 이 접근 방법은 이식성을 한다; 그러나 도움이 되는 경우에만 이를 단지 선택적인 보호수단으로 사용한다면 실제적으로 이식성을 제한하지는 않을 것이다. 또한 리눅스 커널 2.2 버전 이상은 하위 수준 호출을 포함하지만 이들을 사용하기 용이하게 하는 C 수준 라이브러리는 몇몇 리눅스 배포판에는 설치되어 있지 않아 애플리케이션에서 이들의 사용을 약간 복잡하게 하고 있다. 리눅스에서 POSIX 능력 구현에 대해 더욱 자세한 정보는 http://linux.kernel.org/pub/linux/libs/security/linux-privs 를 보라.

FreeBSD 는 권한 제한을 위해 jail() 함수를 갖고 있다: 더욱 자세한 정보는 jail 문서 를 보라. 또한 권한 제한을 위해 많은 전문화된 도구 및 확장들이 있다; 3.10절 을 보라.

6.3.2. 권한이 사용될 수 있는 시간을 최소화해라

가능한 빨리 영구적으로 권한을 포기해라. 리눅스를 포함한 몇몇 유닉스 계열 시스템은 ``이전" ID 값을 저장하고 있는 ``유보된 (saved)" ID 를 구현한다. 가장 간단한 접근 방법은 적절한 경우 (예, setgroups(2) 사용) 모든 추가된 그룹을 재설정한 후 신뢰되지 않은 id 에 다른 id 를 두번 설정하는 것이다. setuid/setgid 프로그램에서는 특히 fork(2) 실행 직후에 실제 uid 와 gid 에 대개 유효 uid 와 gid 를 설정해야 하며 특별한 이유가 없다면 이를 당연히 해야 한다. 루트에서 다른 권한으로 축소시킬 때 gid 를 우선적으로 변경시켜야 하며 이렇지 않은 경우 작용하지 않는다는 것을 주목해라 - 루트 권한을 버린다면 더 이상 변경 시킬 수 없을 것이다.

이러한 최소화를 방해하기 위해 POSIX 능력을 사용하는 잘 알려진 관련 버그가 있음을 언급하는 것은 가치가 있다. 이 버그는 리눅스 커널 2.2.0 에서 2.2.15 및 아마도 POSIX 능력을 갖는 많은 다른 유닉스 계열 시스템에 영향을 미친다. 더욱 자세한 정보는 http://www.securityfocus.com 의 버그트랙 id 1322 를 보라. 다음은 요약이다:

POSIX 능력은 최근 리눅스 커널에 구현되었다. 이 능력은 권한을 갖는 프로세스가 할 수 있는 것을 더욱 명백히 제어할 수 있는 권한 제어의 추가적인 형태로 세가지 비트 필드로 구현되어 있는데 각 비트는 권한을 갖는 프로세스가 수행할 수 있는 특정 동작을 나타낸다. 특정 비트를 설정함으로써 권한을 갖는 프로세스의 동작이 제어될 수 있다 -- 다양한 함수들에 대해 그들을 필요로 하는 프로그램의 특정 부분에 대해서는 접근 허가를 받을 수 있다. 이는 보안 조치이다. 문제는 능력이 fork() execs 와 함께 복사된다는 것인데 이는 능력이 부모 프로세스에 의해 수정된다면 작업이 끝나지 않은 채 능력이 넘겨짐을 의미한다. 이는 세가지 비트 필드 각각에 능력을 0 으로 설정 ( 모든 비트가 off 임을 의미) 한 후 루트 권한으로 실행된 경우 (센드메일이 하는 것과 같이) 위험할 수 있는 코드를 실행시키기 전에 권한을 축소시키려고 하는 setuid 프로그램을 실행시킴으로써 악용될 수 있다. sendmail 은 setuid(getuid()) 를 사용해서 권한을 축소시키려고 할 때 그 비트 필드에 권한 축소에 필요한 능력을 갖지 못하고 반환값 검사없이 실패한다. sendmail 은 슈퍼유저 권한으로 계속 실행되며 루트로서 사용자의 .forward 파일을 실행시켜 완전한 손상을 야기한다.

sendmail 이 사용하는 한가지 접근 방법은 setuid(getuid()) 후에 setuid(0) 를 하려고 하는 것이다; 일반적으로 이는 실패해야 한다. 성공한다면 프로그램은 중지해야 한다. 더욱 자세한 정보는 http://sendmail.net/?feed=000607linuxbug 를 보라. 장기적으로 보았을 때 기본적인 시스템을 갱신하는 것이 더욱 좋은 접근 방법임에도 불구하고 단기적으로 이는 다른 프로그램에서 좋은 개념일 수 있다.

6.3.3. 권한이 필요한 시간을 최소화해라

프로그램이 필요할 때만 유효한 권한을 갖음을 보장하기 위해서 setuid(2), seteuid(2) 와 관련 함수를 사용해라. 위에 언급했듯이 사용자 입력을 해석하는 동안은 권한이 금지되도록 더욱 일반적으로는 권한이 실제 필요할 때만 권한이 유효함을 보장하고 싶을 것이다. 어떤 버퍼 오버플로우 공격은 성공한다면 프로그램으로 하여금 임의의 코드를 실행시키게 할 수 있으며 코드는 일시적으로 축소되었던 권한을 다시 이용할 수 있음을 주목해라. 따라서 가능한 권한을 완전히 축소시키는 것이 늘 더욱 좋다. 더욱이 허가권을 일시적으로 금지하는 것은 프로그램이 파일내에 작성하지 않으려고 했던 것을 작성하도록 납득시키는 기법같은 모든 종류의 공격을 예방한다. 이 기법은 많은 공격을 에방하기 때문에 프로그램의 어떤 부분에서 권한의 완전 축소를 할 수 없는 경우 사용할 가치가 있다.

6.3.4. 권한이 허가된 모듈을 최소화해라

단지 약간의 모듈들에만 권한이 허가된다면 그들이 보안적인지를 결정하는 것은 더욱 쉽다. 이를 행하는 한가지 방법은 하나의 모듈로 하여금 권한을 사용하게 하고 그 후 이를 축소하는 것으로 따라서 추후 호출되는 다른 모듈이 권한을 오용할 수 없다. 다른 접근 방법은 별개 실행 파일에 별개의 명령을 갖는 것이다; 한 명령이 권한을 갖는 사용자 (예, 루트) 를 위해 방대한 양의 태스크를 수행할 수 있는 복잡한 도구이며 한편 다른 도구는 setuid 로 적은 수의 명령 부분집합만을 허용하는 작고 단순한 도구이다. 작고 단순한 도구는 입력이 수용 가능한 다양한 조건을 충족시키는지를 검사하며 따라서 입력이 수용가능하다고 결정하면 데이타를 복잡한 도구로 건네준다. 작고 단순한 도구가 입력 검사와 복잡한 도구로 무엇을 건네줄 것인지를 제한하는 철저한 작업을 해야함을 주목해라 그렇지 않다면 이는 공격당하기 쉬울 수 있다. 이러한 접근 방법은 몇가지 방식으로 계층화 (layered) 될 수 있는데 예를 들어 복잡한 사용자 도구는 다음에 다른 복잡한 신뢰된 도구로 정보를 건네주는 단순한 setuid "wrapping" 프로그램 (입력이 안전한 값인지를 검사한다) 을 호출할 수 있다. 이 접근 방법은 GUI 에 기초한 프로그램에 특히 도움이 된다; GUI 부분을 일반 사용자로 실행시키고 보안 관련 요청을 실제 실행할 수 있는 특별 권한을 갖는 다른 프로그램에 넘겨줘라.

어떤 운영체제는 하나의 프로세스에 다중 신뢰 계층 개념을 갖고 있는데 Multics' rings 이 한예이다. 표준 유닉스와 리눅스는 이와같은 싱글 프로세스내에서 함수를 이용해 다중 신뢰 계층을 분리하는 방법을 갖고 있지 않다; 커널의 호출은 권한을 증가시키지만 그렇지 않으면 주어진 프로세스는 하나의 신뢰 수준을 갖는다. 리눅스와 다른 유닉스 계열 시스템은 프로세스를 각각 다른 권한을 갖는 다중 프로세스로 분기함으로써 이 능력을 때때로 모사할 수 있다. 이렇게 하기 위해 보안적인 통신 채널 (보통 언네임드 파이프 또는 언네임드 소켓이 사용된다) 을 설정한 후 다른 프로세스로 분기하여 각 프로세스 권한을 가능한 많이 축소시켜라. 그리고 단순한 프로토콜을 사용해서 덜 신뢰된 프로세스로 하여금 더욱 신뢰된 프로세스로부터의 동작을 요청하도록하며 더욱 신뢰된 프로세스는 단지 제한된 요청셋을 지원하도록 보장해라.

이는 자바 2 및 Fluke 가 장점을 갖는 분야인데 예를 들어 자바 2 는 단지 특정 파일을 열 수있는 허가권과 같이 미세화된 (fine-grained) 허가권을 지정할 수 있다. 그러나 범용 운영체제는 일반적으로 현재 이러한 능력을 갖고 있지 않다; 이는 가까운 미래에는 변할 수 있다. 자바에 대한 더욱 자세한 정보는 9.6절 를 보라.

6.3.5. 권한을 제한하기 위해 FSUID 사용을 고려해라

각각의 리눅스 프로세스는 파일시스템 사용자 id (fsuid) 와 파일시스템 그룹 id (fsgid) 라는 리눅스 고유의 두개의 상태값을 갖는다. 이 두값은 파일시스템 허가권을 검사할 때 사용되는데 임의 사용자를 위한 파일 서버 (NFS 와 같은) 로 작동하는 프로그램을 작성하려면 이러한 리눅스 확장의 사용을 고려할 수 있다. 이 확장을 사용하기 위해서는 루트 권한을 보유하는 동시에 일반 사용자 대신 파일을 접근하기 전에 단지 fsuid 와 fsgid 를 변경해라. 이 확장은 매우 유용하며 다른 (아마 필요한) 권한을 제거하지 않고서 파일시스템 접근 권한을 제한하는 메카니즘을 제공한다. 단지 fsuid (euid 는 아니다) 를 설정함으로써 지역 사용자는 프로세스에 시그널을 보낼 수 없다. 또한 이러한 상황에서 경쟁 상태는 더욱 쉽게 피해진다. 그러나 이러한 접근 방법의 단점은 이러한 호출이 모든 유닉스 계열 시스템으로 이식할 수 없다는 것이다.

6.3.6. 사용할 수 있는 파일을 최소화하기 위해 Chroot 사용을 고려해라

chroot(2) 를 사용해서 프로그램이 볼 수 있는 파일을 제한할 수 있다. 이는 디렉토리를 주의깊게 설정하고 (choot jail 이라고 불린다) 디렉토리에 정확하게 들어가야 함을 요한다. 이는 프로그램의 보안을 향상시키는 매우 효과적인 기법이다 - 볼 수 없는 파일을 방해하는 것은 어렵다. 그러나 이는 전체적인 가정, 특히 프로그램이 루트 권한이 없어야 한다는 가정에 의존한다. 루트 권한을 획득할 어떠한 방법도 없어야 하며 chroot jail 이 적절히 설정되어야 한다. 저자는 이 방법이 적절한 경우에 chroot(2) 를 사용할 것을 추천하지만 이에만 의존하지만 마라; 대신 이를 계층화된 보호셋의 부분이 되도록 해라. 다음은 chroot(2) 사용에 대한 약간의 주의들이다:

6.3.7. 접근할 수 있는 데이타의 최소화를 고려해라

사용자가 접근할 수 있는 데이타의 양을 최소화하는 것을 고려해라. 예를 들어 사용자가 데이타를 직접적으로 볼 필요가 없다면 CGI 스크립트에서 이 스크립트가 사용하는 모든 데이타를 문서 트리 외부에 놓아라. 어떤 사람은 공개적으로 링크를 제공하지 않음으로써 어떤 누구도 데이타에 접근할 수 없다는 잘못된 생각을 갖고 있는데 이는 간단히 말해서 옳지 않다.

6.3.8. 사용가능한 자원의 최소화를 고려해라

주어진 프로세스가 사용가능한 컴퓨터 자원을 최소화하는 것을 고려해라. 따라서 고장난다고 하더라도 손상을 제한할 수 있다. 이는 서비스 부인 공격을 예방하는 기본적인 방법이다. 네트워크 서버의 경우 공통적인 접근 방법은 각 세션에 대해 별개의 프로세스를 설정하고 각 프로세스에 대해 세션이 사용할 수 있는 총 CPU 시간 등을 제한하는 것이다. 이렇게 함으로써 공격자가 메모리를 소비하거나 모든 CPU 를 사용하는 요청을 하는 경우 한도가 적용되어 그 하나의 세션이 다른 태스크를 방해하지 못하도록 할 것이다. 물론 공격자가 많은 세션을 만들 수 있지만 이는 적어도 공격을 방해할 수 있다. 한도 설정 방법 (예, ulimit(1)) 대한 더욱 자세한 정보는 3.6절 를 보라.