ASP.NET과 IIS, 그리고 윈도우 2000의 보안 관계

일반입력 :2002/10/09 00:00

유경상

최근 빌 게이츠의 닷넷 관련 발언에 대해 파문이 일었었다. 빌 게이츠가 닷넷의 마이서비스 닷넷(My Service .NET)과 관련하여 보안 문제로 인해 고객들의 사용이 없었으며 이로 인해 닷넷 빌딩 블럭 서비스 중 하나로서 마이서비스 닷넷이 실패했다고 발언한 내용을 국내에서 빌 게이츠가 닷넷이 실패했다고 보도함으로써 일어난 에피소드이다. 빌 게이츠는 ‘닷넷 실패’와 더불어 보안에 대해 집중투자 하겠다는 의사를 밝혔으며 이 말은 조금씩 현실로 나타나고 있다.

필자가 항상 읽어보는 MSDN 매거진의 최신호는 몇몇 고정 컬럼을 제외하고는 모두 보안에 대해 다루고 있다. MSDN 온라인을 찾아봐도 보안 관련 글이 많이 눈에 띈다. 예전에는 찾기 어려웠던 CryptoAPI(암호화, 해시 관련 API)에 대한 글들도 이젠 어렵지 않게 찾을 수 있게 되었다. 본지 9월호의 New! About 코너에서도 보안에 대한 내용이 실렸으며, 이때 ASP.NET의 보안 모델과 CAS(Code Access Security)에 대해서도 언급이 되었다.

이번 컬럼에서는 지난 호에 소개된 ASP.NET의 보안 모델을 보다 깊고 심층적으로 분석해 보고자 한다. 이를 위해윈도우 2000의 기본적인 보안에 대해 언급하지 않을 수 없으므로 간단하게나마 몇 마디 하고난 후에 IIS 5.0 및 ASP의 보안 설정을 살펴볼 것이다. 그런 다음 ASP.NET의 보안 모델을 정리하고자 한다. 지난 호의 New! About과 중복되는 부분은 언급을 피할 것임을 미리 알려두는 바이다. 또한 이 컬럼에서 다루는 보안은 암호화에 관련된 것이 아님을 밝히며 주로 인증과 권한에 대한 것임을 명시하는 바이다. 독자들의 혼동이 없기를 바란다.

윈도우 2000과 IIS의 보안 상식

ASP나 ASP.NET의 보안을 이해하기 위해 필요한 윈도우 2000의 보안 상식 몇 가지를 알아보자. 윈도우 보안을 공부해 본 독자들은 알겠지만 대개 이 개념과 용어에 질려서 중도하차한 적이 몇 번 있을 것이다. 필자도 예외는 아니다. 따라서 이 컬럼에서는 꼭 필요한 개념만 몇 가지를 설명하겠다. 이와 더불어 대개 독자들이 혼동하거나 잘 이해하지 못하고 있는 IIS의 보안 설정 역시 몇 가지 알아보도록 한다.

액세스 토큰

액세스 토큰이라 함은 프로세스 혹은 쓰레드의 보안 문맥(security context)을 기술하는 윈도우의 커널 객체를 말한다. 사용자(사람)가 컴퓨터에 로그온할 때는 계정(account)을 사용하게 된다. 그리고 사용자가 어떤 프로그램을 수행시키면 프로그램은 그 사용자의 계정으로 작동하게 된다. 즉, joe라는 계정을 사용하는 사용자가 notepad.exe를 수행시켰다면 notepad.exe는 joe라는 계정에서 작동한다는 말이다. 이는 joe라는 계정이 갖는 각종 권한을 notepad.exe도 갖는다는 말과 같다. 이때 notepad.exe 프로세스가 어떤 계정에서 수행중이며 어떤 특권을 갖고 있는지, 그리고 특별한 권한 명세가 없을 때 사용되는 디폴트 액세스 권한은 어떤 것인지에 대한 정보를 가지고 있는 것이 바로 액세스 토큰이다.

액세스 토큰은 보안 정보에 대한 일종의 포인터 성격이 강하다. 예를 들어, 현재 프로세스가 어떤 계정에서 수행되는지 알고 싶다면 가장 먼저 할 일은 액세스 토큰을 구하는 일이다. 그런 다음 액세스 토큰에 대한 SID를 구하고 SID로부터 계정 이름을 알아내면 된다. <화면 1>은 윈도우 닷넷 서버의 작업 관리자를 보여주고 있다. 이 화면에서 각 프로세스가 어떤 계정에서 작동하는가를 파악할 수 있다.

액세스 토큰은 다시 프라이머리 토큰(primary token)과 가장 토큰(impersonation token)으로 나눌 수 있다. 프라이머리 토큰은 프로세스가 최초로 수행될 때 생성된 액세스 토큰을 말하며 가장(impersonation)을 수행하지 않은 모든 쓰레드는 프라이머리 토큰에서 작동된다. 가장 토큰은 가장에 대한 이해를 요구하므로 잠시 후에 다루기로 하자.

가장

가장(Impersonation)이란 용어는 비주얼 스튜디오 닷넷에 포함된 한글판 MSDN 라이브러리에서 사용되는 용어이다. 책에 따라 인격화 혹은 흉내내기라는 용어가 사용되기도 한다. 개인적인 의견으로는 어정쩡하고 직관적이지 않는 가장이나 인격화 같은 용어보다는 직관적인 ‘흉내내기’ 용어가 마음에 들지만 MSDN을 자주 참고할 독자들을 위해 ‘가장’ 이라는 용어를 사용하겠다.

가장은 말 그대로 계정을 흉내낸다는 말이다. 대개의 데스크톱 애플리케이션(오피스, 노트패드 등)은 가장을 필요로 하지 않는다. 가장이 필요한 애플리케이션은 대개 서버 측 애플리케이션으로서 클라이언트가 서버에 대해 권한이 있는지를 검사하는 데 사용된다. 즉, 서버가 클라이언트로부터 파일 액세스와 같은 서비스 요청을 받았을 때 클라이언트가 이러한 서비스에 대한 권한을 갖고 있는가를 검사할 때 사용된다는 말이다.

그런데 이러한 권한 검사에서 왜 가장을 사용할까? 이 질문에 대해 필자는 ‘가장을 사용하지 않는 경우에는 어떻게 권한 검사를 하는가’라고 반문을 하고 싶다. 가장을 사용하지 않는다면 서버는 클라이언트의 액세스 토큰이 액세스 하고자 하는 서버 자원(이 경우 파일)에 대한 권한이 있는가를 검사해야 할 것이다. 이러한 권한 검사를 위해 서버는 파일의 ACL(Access Control List)을 읽어야 하고 클라이언트의 액세스 토큰과 ACL을 비교 검사하여 권한이 있고 없음을 결정해야 할 것이다.

하지만 이러한 작업은 이미 윈도우 커널내에서 수행하는 작업 아닌가. 윈도우 커널은 컴퓨터 자원을 액세스할 때 이러한 보안 검사를 수행하도록 되어 있다. 다만 주의할 점은 서버 자원의 ACL과 서버 프로그램의 권한을 비교하는 것이 아니라 서버 자원의 ACL과 클라이언트 계정의 권한을 비교해야 한다는 점에서 다르다. 이때 서버 프로그램이 클라이언트 계정인 것처럼 흉내를 내면 어떨까? 서버 프로그램이 자신을 클라이언트 계정인 것처럼 흉내낸 다음 파일을 액세스한다면 윈도우 커널은 클라이언트 계정의 권한과 파일에 주어진 ACL을 비교하여 보안 검사를 수행하게 될 것이다. 이처럼 가장은 서버 측에서 클라이언트의 권한 검사를 보다 손쉽게 해주는 방법으로 생각하면 될 것이다.

FTP를 예로 들어 보자. FTP에 접속한 사용자는 자신의 계정과 암호를 밝힐 것이다. FTP 서버는 이 계정과 암호를 이용해 가장을 수행한다. 그리고 FTP 클라이언트가 다운로드할 파일을 읽으려고 시도한다. 만약 사용자가 제시한 계정이 해당 파일에 대한 읽기 권한이 없다면 오류가 발생할 것이고 FTP 서버는 이 오류 내용을 클라이언트에게 넘겨주면 된다.

윈도우에서 가장을 수행하는 방법은 여러 가지가 있다. 그중에 많이 사용되는 방법은 LogonUser라는 API를 호출하는 것이다. 이 API 함수는 사용자 계정, 암호, 도메인 등을 매개변수로 취하기 때문에 사용하기 편리하다. LogonUser 함수에 대한 상세한 내용은 MSDN을 참고하기 바란다. LogonUser가 호출되면 이 API를 호출한 쓰레드는 가장 토큰을 받게 된다. 가장 토큰은 프라이머리 토큰과는 별개의 것으로 LogonUser를 호출한 쓰레드만이 가장 토큰의 지배를 받는다. 그리고 가장 토큰을 갖는 쓰레드는 언제든지 RevertToSelf라는 함수를 호출하여 가장을 중지할 수 있다.

IIS의 인증

액세스 토큰과 가장에 대한 내용을 알았으므로 이제 IIS의 보안 설정에 대해 알아보자. IIS의 보안 설정은 크게 인증 설정과 권한 관리로 나눌 수 있는데, 인증은 IIS에 의해 관리되는 반면 권한 설정은 윈도우의 파일 보안을 따르고 있다고 볼 수 있다. 권한 검사를 위해서는 먼저 인증이 수행돼야 하기 때문에 IIS는 인터넷을 통해 접속한 클라이언트(대개의 경우 웹 브라우저일 것이다)를 어떻게 인증할 것인가에 대한 설정을 갖고 있다.

인증 설정은 인터넷 서비스 관리자를 통해 수행할 수 있다. 인터넷 서비스 관리자에서 폴더 혹은 파일을 선택하고 등록 정보 대화상자를 열면 디렉토리 보안 혹은 파일 보안 탭을 찾을 수 있다. 여기서 다시 ‘익명 액세스 및 인증 제어’를 선택하면 <화면 2>와 같은 인증 방법 대화 상자를 볼 수 있다. 네 가지의 인증 방법이 나오는데 각각의 인증 방법에 대한 상세한 설명은 참고자료를 살펴보기 바란다. 필자가 이 대화상자에서 주목하고 싶은 것은 익명 액세스일 때와 인증된 액세스일 때의 차이점이다.

익명 액세스라 함은 자신이 누구인가를 밝히지 않는 것을 말한다. 대부분의 경우 웹 브라우저는 익명으로 IIS에 접근하게 된다. 익명 액세스가 요청되면 IIS는 클라이언트를 인터넷 게스트 계정이라 불리는 IUSR_XXX 계정(XXX는 컴퓨터의 이름으로 대체된다)으로 가장한다. 물론 이 가장은 브라우저의 요청을 처리하는 쓰레드에 한한 것으로 전체 프로세스에 적용되는 것은 아니다. IUSR_XXX 계정은 IIS 설치와 더불어 생성되는 로컬 계정(도메인 계정이 아님에 유의한다)으로서 제한된 읽기 권한만을 가지고 있다.

<화면 1>을 살펴보면 inetinfo.exe 프로세스의 계정이 SYSTEM으로 되어 있는 것을 알 수 있다. Inetinfo.exe 프로세스는 IIS의 핵심적인 프로세스로서 HTTP 포트인 80 포트를 리스닝하면서 웹 브라우저의 HTTP 요청을 읽어 파일(HTML, 이미지 등)을 다운로드하거나 ASP, ASP.NET으로 클라이언트의 요청을 포워드해 주는 역할을 담당한다. 따라서 이 프로세스는 다수의 저수준 시스템 자원을 액세스해야 하며 그에 따른 권한을 충분히 갖추고 있어야 한다. 그래서 이 프로세스는 무소불위의 권한을 갖는 SYSTEM 계정을 프로세스 계정(프라이머리 토큰)으로 가지고 있다. 하지만 익명 사용자가 이 SYSTEM 계정에서 작동된다면 보안상의 문제를 야기할 수 있으므로 IIS는 익명 사용자인 경우에 가장을 통해 시스템을 보호하는 것이다.

다시 <화면 2>로 되돌아가 인증된 액세스에 대해 살펴보자. 인증된 액세스는 세 가지의 인증 방법을 사용하는데 이들은 인증 방법이 다를 뿐이지 결과는 모두 동일하다고 볼 수 있다(실제로는 약간 차이가 난다. 상세한 차이점은 참고자료를 살펴보기 바란다). 즉, 인증된 액세스라 함은 사용자가 제시한 인증 정보를 이용해 쓰레드를 가장하겠다는 말이다. 이때 사용자가 제시하는 인증 정보를 서버와 주고받을 때 ‘기본 인증’, ‘다이제스트 인증’, ‘통합(NTLM) 인증’의 방법을 사용하겠다는 뜻이다. 여기서 한 가지 주의할 점은 IIS는 보안 수준이 낮은 쪽을 먼저 적용한다는 말이다. 즉, 어떤 자원을 접근할 때 그 자원이 익명 액세스를 허용한다면 추가적인 인증 절차를 시도조차 하지 않는다. 또 한 가지 알아야 할 사항은 기본 인증, 다이제스트 인증, 통합 인증이 모두 선택되어 있다면 IIS와 브라우저는 어떤 인증 방식을 사용할 것인가를 협상하게 된다. 브라우저에 따라 통합 인증이나 다이제스트 인증을 전혀 지원하지 않을 수도 있기 때문이다.

IIS가 브라우저에게 인증을 요구할 때는 브라우저가 요구한 파일이 익명 액세스를 허용하지 않을 때다. <화면 2>에서는 익명 액세스와 윈도우 통합 인증이 활성화돼 있다. 따라서 브라우저가 AAA.htm 파일을 요구했다면 IIS는 먼저 익명 액세스(IUSR_XXX)로 AAA.htm 파일을 액세스할 것이다. 만일 IUSR_XXX 계정으로 이 파일을 액세스하지 못한다면(AAA.htm 파일의 보안 설정에서 IUSR_XXX가 읽기 권한이 없을 것이다) IIS는 브라우저로 하여금 인증받을 것을 강요한다. 즉, 사용자의 계정, 암호 및 도메인을 입력하도록 요구할 것이며(네트워크 암호 입력 대화상자가 나타난다), 이는 <화면 2>에서 설정된 윈도우 통합 인증을 사용하겠다는 말이다. 일단 사용자가 계정, 암호, 도메인을 입력하면 IIS는 이러한 정보를 이용하여 계정의 유효성을 확인하고 인증 작업이 성공적이라면 이 계정으로 스스로를 가장한다. 여기서도 역시 가장은 브라우저의 요청을 처리하는 쓰레드 레벨에 국한될 뿐이다. 그리고 다시 파일을 액세스하려고 시도할 것이다.

IIS의 웹 애플리케이션 보호와 보안

IIS는 웹 애플리케이션 보호를 지원한다. 웹 애플리케이션 보호라 함은 애플리케이션을 별도의 프로세스 공간에서 수행하도록 함으로써 애플리케이션에서 발생할 수 있는 오류(다운, 무한루프 등)가 다른 웹 애플리케이션에 영향을 주지 못하도록 한다는 것이다. IIS에서 지원하는 웹 애플리케이션 보호는 세 가지 레벨로서 낮음, 보통, 높음이 있다. ‘낮음’이라 함은 웹 애플리케이션이 IIS의 프로세스 공간에서 수행됨을 의미한다. 즉, 웹 애플리케이션은 inetinfo.exe 프로세스 공간에서 수행되며 만약 웹 애플리케이션이 잘못된 동작을 일으켜 프로세스를 다운시킨다면 inetinfo.exe 자체가 다운된다는 말이 된다. 이는 한 애플리케이션의 오작동이 다른 애플리케이션에도 영향을 미치며 심지어 전체 웹 사이트를 다운시킬 수 있음을 의미한다(더이상 80 포트를 리스닝하지 않는다!).

‘높음’이라 함은 웹 애플리케이션이 IIS와는 별도의 프로세스 공간에서 수행됨을 의미한다. 응용 프로그램 보호가 ‘높음’인 웹 애플리케이션은 dllhost.exe라 불리는 프로세스에 의해 호스트되며 dllhost.exe 프로세스는 웹 애플리케이션마다 한 개씩 생성되어 수행된다. Inetinfo.exe가 수신한 브라우저의 요청(HTTP Request)은 명명된 파이프(Named pipe)를 통해 dllhost.exe에 전달되며, 만약 애플리케이션이 오작동을 일으키더라도 영향을 주는 프로세스는 dllhost.exe 하나뿐이므로 inetinfo.exe 프로세스나 다른 dllhost.exe 프로세스는 아무런 영향을 받지 않을 것이다. 하지만 응용 프로그램 보호가 ‘높음’이 되면 ‘낮음’에 비해 성능 손해가 크다. Inetinfo.exe가 수신한 HTTP 요청을 별도의 프로세스인 dllhost.exe로 전송하기 위해서는 추가적인 오버헤드가 발생하기 때문이다. 또한 웹 애플리케이션이 여러 개라면 dllhost.exe가 여러 개 수행되므로 시스템의 자원 소비도 늘어나기 때문이다.

‘보통’이라 함은 낮음과 높음의 중간적인 상태로서 ‘높음’의 단점을 보완해 보고자 윈도우 2000부터 등장한 보호 레벨이다. 응용 프로그램 보호 수준이 ‘보통’인 애플리케이션 역시 별도의 dllhost.exe 프로세스에서 호스팅되는 것은 변하지 않는다. 하지만 이 프로세스는 모든 ‘보통’ 레벨의 웹 애플리케이션을 모두 호스팅한다. 웹 애플리케이션마다 하나씩 생성되는 dllhost.exe 프로세스의 오버헤드를 줄이면서 WWW 서비스의 중요한 프로세스인 inetinfo.exe가 중단되는 것을 막기 위함이다. 물론 이 호스팅 프로세스인 dllhost.exe가 다운되면 ‘보통’ 보호 수준의 웹 애플리케이션은 모두 다운됨은 물론이다. 필자가 낮음, 보통, 높음으로 설명했지만 많은 자료나 MSDN에서는 낮음 수준의 웹 애플리케이션을 in-proc 애플리케이션이라 부르며 보통/높음 수준을 통칭하여 out-of-proc 애플리케이션이라고 하는 것이 일반적이다(<그림 1>).

그렇다면 in-proc과 out-of-proc 애플리케이션이 IIS 보안과 무슨 관계가 있는 것인가? In-proc 애플리케이션은 inetinfo.exe 프로세스에서 호스팅된다. 따라서 프로세스 계정은 inetinfo.exe 프로세스의 계정을 말한다. 그리고 그 계정은 로컬 컴퓨터에 모든 권한을 쥐고 있는 SYSTEM 계정이다. 반면 dllhost.exe에 의해 호스팅되는 out-of-proc 애플리케이션은 디폴트로 dllhost.exe의 프로세스 계정인 IWAM_XXX 계정을 프로세스 계정으로 갖는다. 만약 in-proc 애플리케이션에서 어떤 ASP(ASP.NET이 아님에 유의하자) 페이지가 익명 액세스를 허용한다면 이 페이지는 IUSR_XXX라는 쓰레드 계정과 SYSTEM라는 프로세스 계정에서 수행될 것이다. 반면 out-of-proc 애플리케이션이라면 IUSR_XXX라는 쓰레드 계정과 IWAM_XXX라는 프로세스 계정을 갖는다.

Inetinfo.exe는 HTTP 80 포트를 리스닝한다. 뿐만 아니라 IIS가 수행되기 위해 필요한 각종 시스템 자원(레지스터리, 시스템 파일 등)을 액세스해야 한다. 게다가 이 프로세스는 윈도우 서비스로 등록되어 있다. 따라서 이 프로세스가 왜 SYSTEM 계정에서 수행돼야 하는가를 이해할 수 있을 것이다. 그렇다면 dllhost.exe는? ‘응용 프로그램 보호’ 라는 제목을 생각하면 이 프로세스가 왜 SYSTEM 계정이 아닌가를 이해할 수 있을 것이다. 이 웹 애플리케이션을 수행하는데 필요한 제한된 권한만을 갖고 있는 계정으로서 애플리케이션을 보호하기 위한 방법으로 사용되는 것이다.

여기서 한 가지 의문을 가져보자. In-proc 애플리케이션은 계정에 의한 응용 프로그램 보호가 안 된다는 말인가? 익명 사용자가 ASP 페이지나 ISAPI 익스텐션(extension)을 사용하면 이 요청을 처리하는 쓰레드는 IUSR_XXX라는 인터넷 게스트 계정에서 작동된다고 하지 않았는가? 그리고 이 계정은 말 그대로 게스트 계정으로 그 권한은 매우 미미하지 않은가? 그렇게 생각하기 쉽지만 사실은 전혀 그렇지 않다. 쓰레드가 IUSR_XXX 계정에서 작동하기 위해서는 가장을 수행해야 하는데 가장을 할 수 있는지 없는지는 쓰레드 계정보다는 프로세스 계정의 영향을 받기 때문이다. 일례로 가장을 중지하기 위해서는 RevertToSelf라는 API를 호출하면 된다. 이 API는 가장을 중단할 권한이 호출자에게 있는가를 검사하는데, 이 검사의 대상이 쓰레드가 아닌 프로세스이다. 즉, 프로세스 계정이 RevertToSelf를 호출할 권한이 있는가를 살피는 것이다. In-proc 서버의 경우 프로세스 계정이 SYSTEM이므로 익명 사용자도 RevertToSelf를 호출할 수 있으며 RevertToSelf 호출 후에 쓰레드는 SYSTEM 계정에서 작동할 것이며 로컬 시스템의 모든 자원을 액세스할 수 있는 권한을 쥐는 것과 같다.

반면 out-of-proc을 고려해 보자. 이 경우도 마찬가지로 익명 사용자라도 RevertToSelf를 호출할 수 있다는 점은 같다. 하지만 RevertToSelf를 호출한 후에 쓰레드가 갖는 계정은 IWAM_XXX 계정일 뿐이다. 앞서 언급했듯 이 계정은 상당히 제한된 권한만을 가지고 있을 뿐이다. 응용 프로그램 보호와 보안에 대해 이제 조금이나마 감을 잡았으리라 생각된다.

요약 그리고 테스트

IIS의 보안 설정을 요약해 보자. IIS의 기본적인 보안 정책은 윈도우 파일 시스템의 보안 설정을 근거로 하고 있으며 이 보안 설정은 윈도우 NTLM 혹은 액티브 디렉토리에 근거한 인증이 필요하다. 기본 인증, 다이제스트 인증, NTLM 인증 중 하나를 거친 웹 사용자는 IIS의 작업 쓰레드로 하여금 사용자를 흉내내도록(impersonate) 하며 사용자를 흉내낸 쓰레드가 파일을 액세스함으로써 주어진 권한을 검사하는 것이다. 만일 클라이언트가 익명 사용자로서 IIS에 접근한다면 작업 쓰레드는 자신을 인터넷 게스트 계정인 IUSR_XXX 계정으로서 가장할 것이며 이 계정에 대해 접근 권한이 있는 파일만을 액세스할 수 있을 것이다.

만약 독자들이 IIS를 사용하면서 원인모를 Access Denied 오류나 웹 브라우저에서 뜬금없이 나타나는 인증 대화 상자를 경험했다면 지금까지 필자가 다뤘던 내용을 세심히 살펴보고 웹 컨텐츠가 포함된 디렉토리나 파일들이 IUSR_XXX 계정에 대해 권한이 있는가를 살펴보는 것이 좋을 것이다.

지금까지 언급한 내용을 ASP를 이용하여 테스트해 보자. ASP.NET으로 테스트하는 것은 잠시만 미루자. 테스트를 위해서는 현재 프로세스의 프라이머리 토큰 혹은 가장 토큰을 읽어야 하고 이를 위해서는 WIN32 API인 OpenThreadToken을 호출해야 한다. 하지만 ASP는 WIN32 API를 호출할 능력이 없다. 따라서 액세스 토큰을 읽거나 가장을 수행하는 COM 컴포넌트를 작성하고 이 COM 컴포넌트를 ASP에서 호출하도록 구성한다. 컬럼 성격상 COM 컴포넌트 작성법이나 액세스 토큰을 읽고 계정 정보를 표시하는 방법에 대한 설명은 생략하겠다. 다만 필자가 작성한 SecurityUtil 컴포넌트의 메쏘드와 프로퍼티를 간단히 설명하겠다. SecurityUtil 컴포넌트의 전체 소스는 ‘이달의 디스켓’을 참고하자.

SecurityUtil 컴포넌트는 두 개의 프로퍼티와 세 개의 메쏘드를 가지고 있다. ProcessUserName 프로퍼티는 프라이머리 액세스 토큰으로부터 계정 이름을 읽어 반환하는 프로퍼티로서 프로세스 계정이 무엇인가를 알려주며 ThreadUserName 프로퍼티는 현재 쓰레드의 액세스 토큰으로부터 계정 이름을 읽어 반환하는 프로퍼티다. 만약 쓰레드가 어떤 가장도 하고 있지 않다면 단순히 프로세스 계정을 반환한다. 따라서 ThreadUserName 프로퍼티는 현재 쓰레드가 어떤 계정에서 수행중인가를 나타낸다고 할 수 있다. RevertToSelf 메쏘드는 쓰레드가 어떤 가장을 수행하고 있다면 이것을 중지하도록 하며, Impersonate 메쏘드는 매개변수로 주어진 계정으로 가장을 수행한다. Refresh 메쏘드는 ProcessUserName 프로퍼티와 ThreadUserName 프로퍼티를 최신 정보로 업데이트해 준다.

이제 테스트에 사용한 코드를 살펴보자. <리스트 1>은 간단한 ASP 페이지로서 SecurityUtil 컴포넌트의 ProcessUserName 프로퍼티와 ThreadUserName 프로퍼티를 사용하여 프로세스 계정과 쓰레드 계정을 보여준다. 이 페이지를 응용 프로그램 보호가 ‘높음’인, 즉 Out-of-proc 애플리케이션으로 구성해 수행해 보자. 수행 결과는 <화면 3>과 같다. 설명한 대로 프로세스 계정은 IWAM_XXXX이며 쓰레드 계정은 익명 사용자인 IUSR_XXXX가 되었다. 필자 컴퓨터의 이름이 SIMPLE이기 때문에 XXX 대신 SIMPLE이 들어갔음에 유의하자. 만약 이 웹 애플리케이션이 In-proc 애플리케이션으로 구성됐다면 프로세스 계정이 IWAM_XXXX가 아닌 SYSTEM이 되었을 것이다.

이제 이 ASP 페이지의 보안 속성에서 익명 사용자 액세스를 제거하자. 그리고 이 페이지를 다시 열어본다. 브라우저는 사용자에게 인증을 요구하는 대화상자를 나타낼 것이다. 로컬 계정의 아이디 그리고 암호를 입력하면 수행 결과는 IUSR_XXXX 대신 사용자가 입력한 계정이 나타날 것이다. 이는 쓰레드가 인증된 사용자로 가장을 수행함을 보여준다. 테스트에서 주의할 점은 로컬 컴퓨터에서 브라우저를 사용하면 인증 대화상자를 볼 수 없는데, 이유는 IIS가 인증을 요구할 때 브라우저는 기본적으로 현재 로그온된 사용자 정보를 IIS에게 보낸다. 이 인증 과정이 실패할 경우에만 인증 대화상자가 나타난다. 따라서 로컬 컴퓨터에서 테스트를 하는 경우 인증 대화상자가 나타나지 않을 수도 있다.

<리스트 1> IIS & ASP 계정 관련 테스트 코드(Security.asp)

<%@ Language=VBScript %>

<%

Dim SecurityUtil

Dim ProcessUser, ThreadUser

On Error Resume NEXT

Set SecurityUtil = CreateObject(SecurityUtil.TokenInfo)

ProcessUser = SecurityUtil.ProcessUserName

ThreadUser = SecurityUtil.ThreadUserName

%>

ASP Examples Home Page

Process/Thread Account Information

Default Account Info

Process Account<% = ProcessUser %>
Thread Account<% = ThreadUser %>

<화면 4>는 간단한 코드를 좀더 추가하여 테스트한 결과다. SecurityUtil 컴포넌트의 RevertToSelf 메쏘드를 호출하여 가장을 중단했으며 그 결과를 표시한 것과 ASP 페이지에서 임의의 계정으로 가장할 수 있음을 보인 것이다. 이 테스트에서 주의할 것은 가장을 수행한 마지막 테스트인데, 가장을 수행하기 위해 프로세스 계정은 ‘운영체제 일부로 활동’이라는 특권을 가져야 한다. 즉, 이 경우에 IWAM_XXXX 계정이 ‘운영체제 일부로 활동’이라는 특권을 가져야 하는 것이다. 하지만 기본 설치 사항으로 IWAM_XXXX 계정은 이 특권을 갖지 않는다. IWAM_XXXX 계정이 이 특권을 갖도록 하기 위해서는 ‘로컬 보안 정책’ MMC에서 IWAM_XXXX 계정에게 이 특권을 줘야 <화면 4>와 같은 결과를 볼 수 있을 것이다. 만약 이 특권이 없다면 가장은 실패하게 된다.

ASP.NET 보안 모델

지금까지 IIS의 보안 모델에 대해 살펴봤다. ASP.NET의 보안 모델은 IIS의 보안 모델과 관계가 깊다. 앞서 자세히 IIS 보안 모델을 살펴본 것은 이러한 연유 때문인데, 이제 ASP.NET의 보안 모델을 살펴보자.

ASP.NET 페이지의 수행 과정

보안 모델에 앞서 한 가지 이해해야 할 사항이 있다. 그것은 ASP.NET의 대표적인 웹 페이지인 aspx 파일이 수행되는 과정에 대한 것이다. IIS가 웹 브라우저로부터 aspx 파일에 대한 요청을 수신했다고 가정해 보자. IIS는 주어진 URL로부터 어떤 웹 애플리케이션에 해당되는지를 먼저 파악한다. 그리고 웹 애플리케이션의 응용 프로그램 맵핑을 살피게 된다. 응용 프로그램 맵핑이란 요청된 파일의 확장자에 의해 적절한 ISAPI 익스텐션이 수행되도록 하는 것을 말한다.

<화면 5>는 IIS 관리자에 설정된 응용 프로그램 맵핑을 보여주고 있다. 확장자가 aspx인 경우 닷넷 프레임워크의 aspnet_isapi.dll에 연결되어 있다. 이제 IIS는 클라이언트로부터 수신한 HTTP Request를 aspnet_isapi.dll로 넘긴다. aspnet_isapi.dll은 그다지 많은 일을 하지 않는다. IIS로부터 넘겨받은 HTTP 요청을 실제적인 처리를 수행할 ASP.NET 작업 프로세스로 넘기는 일을 주로 수행한다. 만약 이 작업 프로세스가 이미 수행중이 아니라면 프로세스를 시작시키는 역할 역시 aspnet_isapi.dll이 맡게 된다. 작업 프로세스는 HTTP 요청을 처리하기 위한 일련의 작업 aspx 파싱, 컴파일, 수행 등의 작업을 수행한다. 수행이 끝나면 최종 결과는 다시 aspnet_isapi.dll로 넘겨지며 이 결과는 다시 IIS를 통해 클라이언트(브라우저)로 넘어간다.

ASP.NET의 작업 프로세스는 aspnet_wp.exe라 불리는 프로세스로서 모든 ASP.NET의 페이지와 웹 서비스는 이 프로세스에서 처리되고 수행된다. 따라서 이 프로세스가 모든 ASP.NET 웹 애플리케이션을 호스팅하는 것이다. 이쯤에서 의문을 가질 법도 한 것은 IIS의 응용 프로그램 보호와 aspnet_wp.exe 프로세스와의 관계에 대한 것이다. 응용 프로그램 보호 수준이 ‘낮음’인 ASP.NET 웹 애플리케이션은 어디에서 수행되는가? Inetinfo.exe인가 아니면 aspnet_wp.exe인가? 혹은 응용 프로그램 보호 수준이 ‘높음’인 ASP.NET 애플리케이션은 dllhost.exe인가 아니면 aspnet_wp.exe인가? 이 질문에 대한 답은 ‘항상 aspnet_wp.exe에서 수행’이다.

그렇다면 IIS의 응용 프로그램 보호 수준과 ASP.NET과의 관계는? ‘아무런 관계없음’이다. 즉, IIS의 응용 프로그램 보호 수준과 아무런 관계 없이 ASP.NET 웹 애플리케이션은 항상 aspnet_wp.exe에서 작동되며 In-proc이니 Out-of-proc이니 하는 것은 ASP.NET 이전의 기술들, 즉 ASP와 ISAPI 익스텐션과만 관계가 있을 뿐이다. ASP에 기반을 둔 개발자들이 ASP.NET에서 혼동하는 첫 번째 요소가 바로 이 점이며 이제는 혼동하지 않기를 바란다.

ASP.NET 작업 프로세스와 보안

앞서 언급한대로 ASP.NET 웹 애플리케이션은 모두 ASP.NET 작업 프로세스에서 호스팅된다고 했다. 그렇다면 이들 여러 웹 애플리케이션이 하나의 프로세스에서 동시에 작동할 때 한 웹 애플리케이션이 다른 애플리케이션에 영향을 미칠 수도 있지 않을까? 전혀 그렇지 않다. 비록 한 프로세스 내에서 호스팅될지라도 이들 웹 애플리케이션은 닷넷의 새로운 개념인 애플리케이션 도메인(Application Domain)에 의해 완전히 독립되어 있으며 한 애플리케이션이 다른 애플리케이션의 메모리를 액세스하거나 직접 코드를 호출할 수 없다. 이것은 IIS의 응용 프로그램 보호에서 ‘보통’ 수준과 비슷하다고 생각하면 될 것이다.

하지만 이 모델은 몇 가지 치명적인 단점을 가지고 있다. 첫째로 어느 웹 애플리케이션의 오작동으로 인해 aspnet_wp.exe 프로세스가 다운되면 전체 ASP.NET 웹 애플리케이션이 모두 다운되고 만다. 둘째로 특정 웹 애플리케이션만을 종료할 방법이 없다. 하나의 프로세스에서 여러 웹 애플리케이션이 호스팅되고 있으므로 특정 웹 애플리케이션만을 선택하여 종료시키기 어려운 것이다. 하지만 이러한 문제점은 윈도우 2000과 윈도우 XP에만 국한된다. 윈도우 닷넷에 포함된 IIS 6.0은 aspnet_wp.exe를 아예 사용하지 않는다. IIS 6.0에 대한 사항은 조금 뒤에 다루기로 하자.

<그림 2>는 IIS 프로세스(inetinfo.exe)와 Out-of-proc 프로세스, 그리고 ASP.NET 작업 프로세스의 관계를 보여주고 있다. 그림에도 나와 있듯 aspnet_wp.exe 프로세스 역시 프로세스 계정을 갖게 된다. 이 프로세스는 디폴트로 ASPNET 계정을 사용한다. 이는 Out-of-proc 프로세스가 IWAM_XXX 계정에서 수행되는 것과 비슷하게 볼 수 있다. 이 작업 프로세스의 계정은 바꿀 수 있는데, 닷넷 프레임워크 폴더(대개 C:WinntMicrosoft.NETFramework버전Config 폴더)의 machine.config 파일의 processModel 노드 값을 수정하면 된다. 대개의 경우, ASPNET 계정으로 대부분의 작업을 수행할 수 있으며 일부 권한이 필요한 작업을 수행해야 한다면 작업 프로세스의 계정을 바꾸는 것보다 웹 애플리케이션 내에서 가장을 수행하는 것이 바람직하다고 할 수 있다.

ASP.NET 인증

이제 기본 사항을 모두 정리했으니 ASP.NET의 보안 모델에 대해 상세히 알아보자. ASP.NET도 ASP나 ISAPI와 마찬가지로 IIS에 의해 작동되므로 IIS의 기본 보안 모델과 관계가 있다. 하지만 ASP.NET만의 고유한 특성 역시 제공하고 있다. ASP.NET이 제공하는 보안 모델 역시 크게 인증 부분과 권한 부분으로 나눌 수 있다. 인증 모델은 윈도우, 폼, 패스포트, 커스텀의 네 가지를 제공하고 권한은 ASP의 권한 검사와 동일한 파일 권한에 의한 보안과 역할 기반 보안(Role-Based Security)으로 다시 나눌 수 있다. 인증 부분부터 차근차근 알아보도록 하자. 앞서 IIS 인증에서 기본적인 내용을 다뤘으므로 이번에는 테스트 중심으로 ASP.NET의 인증을 살펴보도록 하겠다. 여기서 살펴볼 내용은 윈도우 인증과 폼 인증이다. 패스포트 인증은 아직까지 널리 보편화되지 않았고 애플리케이션에서 사용하기 위해서는 라이선스를 받아야 하므로 이 컬럼에서는 다루지 않겠다. 커스텀 인증은 폼 인증과 같은 인증 모듈을 독자적으로 개발하여 적용할 수 있는 방법을 말한다. 커스텀 인증 역시 이 컬럼의 범위를 벗어나므로 여기서 다루지 않을 것이다. 독자들의 넓은 이해를 바란다.

닷넷에서 윈도우 보안 시스템 접근

테스트를 하기 위해서는 닷넷에서 액세스 토큰을 접근하는 방법부터 알아야 한다. 앞서 작성한 SecurityUtil 컴포넌트를 COM Interop를 통해 사용할 수도 있지만 다른 접근 방법을 알아보자. 닷넷이 윈도우를 기반으로 하고 있음에도 불구하고 모든 윈도우 프로그래밍 환경을 커버하고 있지는 않다. 모든 WIN32 API를 닷넷 프레임워크가 대체하고 있지는 않다는 말이다. 이 때문에 닷넷 프레임워크는 WIN32 API나 기존의 COM 컴포넌트를 사용할 수 있도록 PInvoke(Platform Invoke)나 COM Interop를 지원하고 있음은 필자의 컬럼(본지 2002년 6월호)에서 밝힌 바 있다. 닷넷 환경에서 액세스 토큰을 얻기 위한 클래스는 제공되지 않는다. 액세스 토큰이라는 개념은 운영체제에 매우 의존적이다. 예를 들어 프라이머리 액세스 토큰을 읽기 위해 제공되는 OpenProcessToken API는 윈도우 95/98/Me에서는 제공되지 않는다. 하지만 닷넷 환경은 다양한 윈도우뿐만 아니라 FreeBSD와 같은 다른 운영체제에서도 작동한다. 이런 맥락에서 닷넷 프레임워크가 액세스 토큰을 제어하는 메쏘드를 가지고 있지 않다고 생각하면 될 것이다.

만약 프로그램 내에서 액세스 토큰과 같은 윈도우 보안 시스템에 접근하기 위해서는 닷넷 프레임워크가 제공하는 몇몇 클래스와 WIN32 API를 섞어 사용해야 한다. 앞서 예로 든 프라이머리 액세스 토큰을 읽기 위해서는 OpenProcessToken API를 호출하는 PInvoke를 사용해야 하지만 현재 쓰레드의 쓰레드 계정을 알아내기 위해서는 WindowsIdentity 클래스의 정적 메쏘드인 GetCurrent() 메쏘드를 사용하면 된다. 비슷한 예로, WindowsIdentity 클래스는 Impersonate() 메쏘드를 가지고 있지만 WindowsIdentity 클래스의 인스턴스를 만들기 위해서는 액세스 토큰이 필요하고 이 액세스 토큰을 만들기 위해서는 LogonUser 라는 WIN32 API를 호출해야 한다.

필자는 ASP.NET에서 보안 정보를 읽기 위한 몇몇 WIN32 API를 묶어 유틸리티 클래스를 만들었다. 이 클래스 코드는 <리스트 2>에 일부 나타나 있다. 전체 코드는 ‘이달의 디스켓’을 참고하기 바란다. Win32SecurityAPI 클래스는 프로세스 토큰(프라이머리 액세스 토큰)을 읽기 위한 GetProcessToken 메쏘드, 가장을 위한 액세스 토큰 생성과 관련된 LogonUser 메쏘드 등을 가지고 있다. 이들 메쏘드를 사용하여 프로세스 계정을 나타내는 코드는 다음과 같다.

string result;

System.Diagnostics.Process proc =

System.Diagnostics.Process.GetCurrentProcess();

try {

// 프로세스의 액세스 토큰을 읽는다.

IntPtr procToken = Win32SecurityAPI.GetProcessToken(proc.Handle);

// WindowsIdentity 클래스를 이용해 사용자 이름을 알아낸다.

WindowsIdentity procUser = new WindowsIdentity(procToken);

result = procUser.Name;

Win32SecurityAPI.CloseHandle(procToken);

}

catch (Win32Exception e) {

result = 다음 이유로 알 수 없음 : + e.Message;

}

쓰레드 계정을 읽는 방법은 매우 쉽다. WindowsIdentity 클래스의 GetCurrent() 메쏘드를 호출하면 현재 쓰레드의 액세스 토큰으로부터 계정명을 읽을 수 있다. 만일 가장이 수행중이라면 가장 액세스 토큰으로부터 계정명을 읽을 것이며 그렇지 않다면 GetCurrent() 메쏘드가 반환하는 것은 프라이머리 액세스 토큰, 즉 프로세스 계정일 것이다.

string threadUserName = WindowsIdentity.GetCurrent().Name;

이러한 코드를 바탕으로 예제 웹 애플리케이션을 작성하고 ASP.NET의 윈도우 인증, 폼 인증에서 테스트를 수행해 보도록 하겠다.

<리스트 2> 보안 API를 위한 Win32SecurityAPI 클래스

using System;

using System.ComponentModel;

using System.Runtime.InteropServices;

namespace Security

{

///

/// WIN32 토큰과 관련된 액세스 마스크

///

[Flags]

public enum TokenAccess

{

TOKEN_ASSIGN_PRIMARY = 0x0001,

TOKEN_DUPLICATE = 0x0002,

TOKEN_IMPERSONATE = 0x0004,

TOKEN_QUERY = 0x0008,

// 생략……

}

///

/// LogonUser API에서 사용하는 로그온 타입

///

public enum LogonType

{

LOGON32_LOGON_INTERACTIVE = 2,

LOGON32_LOGON_NETWORK = 3,

LOGON32_LOGON_BATCH = 4,

// 생략……

}

// …… 중략 ……

///

/// WIN32 Security 관련 API에 대한 모음

///

public class Win32SecurityAPI

{

[DllImport(advapi32.dll, EntryPoint=LogonUser, SetLastError=true)]

private static extern bool _LogonUser(string lpszUsername,

string lpszDomain, string lpszPassword,

int dwLogonType, int dwLogonProvider, out int phToken);

///

/// 주어진 사용자 ID로 로그온하여 액세스 토큰을 반환한다.

///

/// 사용자 ID

/// 암호

/// 도메인 이름

/// 로그온 종류

/// 로그온 프로바이더

///

public static IntPtr LogonUser(string userName, string password,

string domainName, LogonType logonType,

LogonProvider logonProvider)

{

int token = 0;

bool logonSuccess = _LogonUser(userName, domainName, password,

(int)logonType, (int)logonProvider, out token);

if (logonSuccess)

return new IntPtr(token);

int retval = Marshal.GetLastWin32Error();

throw new Win32Exception(retval);

}

[DllImport(advapi32.dll,

EntryPoint=OpenProcessToken, SetLastError=true)]

private static extern bool _OpenProcessToken(

IntPtr handle, int desigedAccess, out int phToken);

///

/// 프로세스 토큰을 반환한다.

///

/// 토큰을 얻고자 하는 프로세스 핸들

/// 프로세스 토큰

public static IntPtr GetProcessToken(IntPtr handle)

{

int desiredAccess = (int)TokenAccess.TOKEN_QUERY;

return GetProcessToken(handle, desiredAccess);

}

///

/// 프로세스 토큰을 반환한다.

///

/// 토큰을 얻고자 하는 프로세스 핸들

/// 토큰 액세스 목록

/// 프로세스 토큰

public static IntPtr GetProcessToken(IntPtr handle, int desiredAccess)

{

int token = 0;

bool openSuccess = _OpenProcessToken(

handle, desiredAccess, out token);

if (openSuccess)

return new IntPtr(token);

throw new Win32Exception(Marshal.GetLastWin32Error());

}

// …… 생략 ……

}

}

윈도우 인증

ASP.NET에서 제공하는 네 가지 인증 모드 중 디폴트는 윈도우 인증이다. 이 윈도우 인증은 기존 ASP 인증과 다를 바가 없다. 즉, IIS에서 디렉토리나 파일 레벨에서 설정된 인증 사항이 그대로 ASP.NET에도 적용되는 사항이다. 예를 들어 보자. IIS가 Default.aspx 파일에 대한 처리 요청을 수신하면 IIS는 파일의 인증 사항을 메타베이스를 통해 읽는다. 만일 익명 사용자가 이 파일을 액세스할 수 있다면 별다른 처리 없이 aspnet_isapi.dll에게 HTTP 요청을 넘겨주게 될 것이다. 하지만 파일이 익명 액세스를 허용하지 않는다면 IIS 브라우저에게 인증을 요구하게 된다. 인증 방법은 기본 인증, 다이제스트 인증 혹은 NTLM 중 하나를 선택하게 될 것이다.

ASP.NET의 윈도우 인증은 몇 가지 주의할 사항이 있다. 첫째로 ASP.NET 웹 애플리케이션의 web.config 파일에 인증이 명시되지 않으면 machine.config 파일의 인증 설정을 따른다. 그리고 이 기본 인증 설정은 다음과 같이 Windows로 설정되어 있다.

이러한 기본 설정은 우리가 ASP에서 테스트해 보았던 대로 작동하지 않는다. ‘이달의 디스켓’에 포함된 Default.aspx는 앞서 설명한 Win32SecurityAPI 클래스를 이용하여 프로세스 계정과 쓰레드 계정을 표시해 준다. 그리고 이 Default.aspx에 대한 IIS의 보안 설정이 익명 액세스를 허용하도록 돼 있다고 가정해 보자. ASP에서의 테스트를 기억해 보면 프로세스 계정은 ASPNET이 될 것이고(<그림 2> 참조), 쓰레드 계정은 IUSR_XXXX가 돼야 할 것이다. 하지만 수행 결과는 전혀 그렇지 않다. 수행 결과인 <화면 6>을 보면 프로세스 계정과 쓰레드 계정이 모두 ASPNET으로 돼 있다. 왜일까?

ASP.NET은 설정 파일(configuration)의 태그에 따라 클라이언트를 가장할 것인가를 결정하도록 돼 있고 machine.config 파일의 기본 설정은 다음과 같이 impersonate 값이 false로 돼 있다. 그래서 쓰레드 계정이 IUSR_XXXX가 아닌 프로세스 계정으로 설정되어 있는 것이다.

익명 액세스를 허용하지 않고 인증을 받게 되면 어떻게 될까? IIS 설정에서 Default.aspx의 익명 액세스를 disable 하고 이 페이지를 다시 시도하면 인증 대화 상자가 나타날 것이다(나타나지 않을 수도 있다). 인증을 성공적으로 마치더라도 결과는 <화면 6>과 전혀 다르지 않다.

그렇다면 기존의 ASP나 ISAPI와 호환이 되도록 하는 방법은 무엇인가? 그것은 웹 애플리케이션의 web.config 파일에 태그를 과 같이 추가하는 것이다. 그런 다음 다시 Default.aspx를 접근하면 <화면 7>과 같은 결과를 얻게 될 것이다.

<화면 7>에서는 프로세스 계정을 읽는 것이 실패했다. 그 이유는 현재 쓰레드 계정인 IUSR_XXXX 계정이 OpenProcessToken API를 호출할 권한이 없기 때문이다. 대개 프로세스 토큰을 얻기 위해서는 가장을 중단하는 것이 좋다. 가장을 수행한 쓰레드가 프로세스 토큰을 액세스할 권한이 없을 수도 있기 때문이다. 따라서 현재 쓰레드 토큰을 읽어 저장해 두고, 가장을 중단한 후에 프로세스 토큰을 읽는다. 그리고 나서 저장해둔 쓰레드 토큰으로 다시 가장을 하는 것이 일반적인 방법이다. 앞서 테스트에 사용한 SecurityUtil 컴포넌트는 이러한 방식으로 코딩이 돼 있지만 ASP.NET 코드는 그렇지 못했기 때문에 문제가 발생한 것이다. 오류가 발생하지 않도록 하는 것은 Default.aspx.cs에 단 세 줄을 추가하는 것이므로 독자들에게 숙제로 남기겠다.

<화면 7>은 익명 액세스를 허용한 경우이다. 만일 익명 액세스를 허용하지 않는다면 IIS는 브라우저에게 인증을 요구할 것이고 사용자가 제공한 인증 정보를 통해 가장이 수행될 것이다. 결과적으로 쓰레드 계정은 IUSR_XXXX가 아닌 사용자가 제공한 계정이 될 것이다. 이것은 ASP나 ISAPI와 동일하게 적용된다.

ASP.NET에서는 지금까지 설명한 내용 외에도 추가적인 액세스 토큰 설정 방법이 있다. 이 방법은 인증 방법에 관계없이 쓰레드가 하나의 계정에서 작동하도록 하는 방법이다. 즉, 익명 액세스를 사용하건 아니면 NTLM 인증을 사용하건 쓰레드 계정을 단일화한다는 것이다. 이와 같이 사용하기 위해서는 web.config의 설정에 가장을 수행할 구체적인 계정 이름과 암호를 설정하면 된다.

web.config에 이와 같은 설정을 추가하고 페이지를 보려고 하면 윈도우 2000에서는 오류가 발생할 것이다. 그 이유는 ASP.NET이 가장을 하기 위해 호출하는 LogonUser API 때문이다. 앞서 언급한 대로 이 API를 호출하기 위해서는 프로세스 계정(현재 쓰레드 계정이 아니다!)이 ‘운영체제의 일부로 활동’이라는 특권을 가지고 있어야 하는데 ASP.NET 작업 프로세스의 계정인 ASPNET은 이 특권을 가지고 있지 않다. 이 때문에 액세스 토큰을 만들 수 없다는 오류가 발생한다. 반면 윈도우 XP와 윈도우 닷넷은 LogonUser를 호출하기 위해 이 특권을 요구하지 않는다.

이 오류를 해결하는 방법은 두 가지가 있다. machine.config 파일을 수정하여 작업 프로세스가 ASPNET이 아닌 SYSTEM이나 administrator와 같이 특권을 갖도록 하는 방법과 ASPNET 계정에 ‘운영체제의 일부로 활동’ 특권을 주는 방법이다. 보다 안전한 웹 사이트를 위해서는 후자의 방법을 택하는 것이 좋다. ‘운영체제의 일부로 활동’ 특권을 주기 위해서는 로컬 보안 설정 관리자에서 사용자 권한 할당을 사용하면 된다. 특권을 할당했으면 IIS를 재시작해야만 한다. iisreset 커맨드나 서비스 관리자로 IIS를 재시작한 후 다시 시도하면 <화면 9>와 같은 결과를 얻을 것이다.

<화면 9>는 익명 액세스를 사용하거나 NTLM 인증을 사용하더라도 항상 동일하게 나타나는 결과 화면이다. ASP.NET은 쓰레드 계정을 항상 에 명시된 계정으로 가장을 수행할 것이다. <화면 9>에서 프로세스 계정 정보가 잘 나타나는 이유는 에 명시된 계정 ksyu가 프로세스 토큰을 읽을 권한이 있기 때문이다. 만약 권한이 없는 사용자가 에 명시됐다면 프로세스 계정 정보는 <화면 7>과 같이 나타나지 않을 것이다.

ASP.NET의 윈도우 인증은 IIS 인증과 호환되면서 추가적인 기능이 포함되어 있다. 필요에 따라 적절하게 설정을 수행하면 인트라넷에서 액티브 디렉토리와 더불어 싱글사인온을 구현하는데 매우 편리한 방법일 것이다. 하지만 인터넷 환경이나 액티브 디렉토리를 사용하지 않는 인트라넷에서는 그다지 많이 사용되지 않는다. 하지만 ASP.NET 애플리케이션을 개발하다가 종종 등장하는 ‘권한 없음’ 오류에 대처하는데 지금까지 설명한 프로세스 계정, 쓰레드 계정 지식을 적용한다면 쉽게 해결할 수 있는 문제가 많이 있을 것이라고 필자는 장담할 수 있다.

폼 인증

ASP.NET 애플리케이션에서 가장 유용하고 많이 사용될 수 있는 인증 방식이 바로 폼 인증이다. web.config 파일의 태그의 mode 애트리뷰트를 Forms로 설정하면 폼 인증이 활성화된다. 폼 인증 방식은 인증 티켓을 브라우저 쿠키에 저장하는 방식을 사용한다. 인증 티켓은 FormsAuthenticationTicket 클래스에 의해 표현되며 이 티켓의 여러 속성은 web.config 파일의 설정에 따라 초기화된다(물론 코드에 의해 제어도 가능하다). 인증 티켓은 web.config 설정에 따라 적절히 암호화되어 브라우저 쿠키로 저장되며, 폼 인증 검사 모듈은 브라우저의 요청에 유효한 인증 티켓이 있는가를 검사한다. 만약 인증 티켓이 존재하지 않는다면 ASP.NET은 사용자를 web.config에 명시된 로그인 페이지로 리다이렉트를 수행하며 이 페이지에서 사용자에게 ID 및 암호 등 인증에 필요한 정보를 입력받게 된다. 입력된 인증 정보를 통해 애플리케이션은 사용자를 인증하고 인증이 성공하면 FormsAuthentication 클래스를 통해 인증 티켓을 만들고 이것을 쿠키로서 브라우저에 내려 보낸다.

폼 인증은 ASP.NET의 중요한 특징 중 하나이며 많은 자료나 서적에서 사용법을 설명하기 때문에 구체적인 사용법이나 코딩 방법을 여기서 굳이 다시 설명하지 않겠다. 다만 폼 인증이 사용될 때 웹 애플리케이션의 프로세스 계정이나 쓰레드 계정은 어떻게 되는지, 폼 인증과 IIS의 보안 설정은 어떤 관계가 있는지를 테스트를 통해 살펴보기로 하자.

테스트를 위해 web.config 파일에서 폼 인증 설정을 다음과 같이 수행한다. 이 설정은 폼 인증을 사용하며 인증되지 않은 사용자를 거부할 것을 명시하고 있다. 인증되지 않은 사용자는 디폴트 설정에 의해 Login.aspx로 리다이렉트될 것이며 이 페이지에서 인증받도록 할 것이다. Login.aspx가 아닌 다른 페이지로 설정하는 것은 MSDN을 참조하기 바란다.

그리고 Login.aspx 페이지를 만들자. 이 페이지는 단순한 테스트를 위한 것이므로 로그인 버튼 하나만을 둘 것이다. 이 버튼이 눌려지면 FormsAuthentication.RedirectFromLoginPage 메쏘드를 호출하여 인증 티켓을 만든다. 인증에 사용된 ID는 하드 코드된 값으로 ‘formsUser’가 사용됐다. 다음 코드는 Login.aspx.cs의 일부이며 전체 코드는 ‘이달의 디스켓’을 참고하기 바란다.

private void btnLogin_Click(object sender, System.EventArgs e)

{

// 간단한 테스트를 위해 임의로 인증을 수행한다.

FormsAuthentication.RedirectFromLoginPage(formsUser, false);

}

Login.aspx와 Default.aspx의 IIS 보안 설정은 디폴트(익명 액세스와 NTLM 인증 선택. <화면 2> 참조) 설정대로 둔다.

이제 브라우저가 Default.aspx를 접근할 때 어떤 일이 일어나는지 차근차근 알아보자. 브라우저가 Default.aspx를 요청하면 이 요청은 IIS에 의해 수신될 것이다. IIS는 메타베이스의 Default.aspx 보안 설정을 확인하고 이것이 익명 액세스를 허용하므로 곧바로 aspnet_isapi.dll를 통해 작업 프로세스인 aspnet_wp.exe로 브라우저의 요청을 넘긴다. ASP.NET 엔진은 web.config 파일에 의해 폼 인증이 수행중이라는 것을 알게 될 것이고 폼 인증을 수행한다. 인증 모듈은 인증 티켓을 검사하고 인증 티켓이 없으므로 브라우저에게 Login.aspx로 리다이렉트할 것을 지시한다. 그럼 브라우저는 Login.aspx로 리다이렉트돼 인증을 수행하고 다시 Default.aspx로 리다이렉트될 것이다. 이 리다이렉트 과정에서 브라우저는 인증 티켓을 쿠키로서 보관하게 된다는 점은 중요하다(왜 Transfer가 아닌 Redirect인가에 대한 답이 될 것이다). 이제 인증 모듈은 인증 티켓을 브라우저가 제시했으므로 인증 티켓의 유효성을 검사하고 Default.aspx를 수행할 것이다.

이러한 폼 인증 과정에서 프로세스 계정과 쓰레드 계정은 어떻게 될까? 폼 인증이라 할지라도 프로세스 계정은 윈도우 인증과 다를 바가 없을 것이다. 즉, ASP.NET의 인증 방법에 관계없이 프로세스 계정은 여전히 ASPNET 계정, 혹은 machine.config의 설정 사항을 따를 것이라는 것에는 의심이 없다. 그렇다면 폼 인증시 쓰레드 계정은 어떻게 될까? 결론부터 이야기하면 윈도우 인증과 크게 다르지 않다는 점이다. 먼저 우리가 테스트 환경에서 web.config의 태그의 impersonate를 명시하지 않았으므로 ASP.NET은 가장을 하지 않는다. 따라서 쓰레드의 액세스 토큰은 프로세스의 액세스 토큰이 되며 쓰레드 계정은 프로세스 계정과 같다. 따라서 이 테스트의 결과는 <화면 6>과 같다. 만약 web.config에 를 삽입해 넣는다면 프로세스 계정은 ASPNET이고 쓰레드 계정은 IUSR_XXX가 될 것이다(<화면 7>). 이는 Default.aspx가 익명 액세스를 허용했을 때의 결과이다.

만일 Default.aspx가 익명 액세스를 허용하지 않고 NTLM 인증만을 사용한다면 어떻게 될까? 이 경우, 웹 페이지를 액세스하기 위해서는 두 가지 인증이 모두 사용된다. IIS 인증과 ASP.NET의 폼 인증이 순서대로 일어난다. IIS가 브라우저의 요청을 먼저 처리하기 때문에 NTLM 인증을 위해 네트워크 암호 입력 대화상자가 나타나고(다시 한 번 이야기하지만 로컬 컴퓨터에서 테스트하면 나타나지 않을 수도 있다) ASP.NET의 폼 인증에 의해 login.aspx로 리다이렉트될 것이다. 즉, 이중 인증이 필요하다는 의미다. 이 때문에 폼 인증을 사용하는 경우에는 익명 액세스를 사용하는 것이 일반적이고, IUSR_XXX 계정이 아닌 계정에서 쓰레드가 작업을 해야 한다면 web.config의 <

태그에서 사용할 구체적인 계정을 적어 주는 것이 좋다. 이때 ASPNET 계정이 aspnet_wp.exe 프로세스의 계정으로 사용된다면 ‘운영체제 일부로 활동’ 특권이 주어져야 함은 물론이다(윈도우 2000의 경우에만).

또 하나의 도전, CLR 쓰레드 계정

폼 인증과 더불어 한 가지 새로운 개념을 이해해야 할 필요가 있다. 소위 CLR(Common Language Runtime) 쓰레드라는 것이 그것인데, 명칭상 쓰레드를 지칭하는 것 같다. 그렇다면 CLR 쓰레드는 무엇을 의미할까? CLR 쓰레드는 지금까지 필자가 언급해온 쓰레드와는 약간 다른 개념이다. 구분을 위해 지금까지 단순히 쓰레드라고 지칭했던 것을 WIN32 쓰레드로 구분하기로 한다. CLR 쓰레드는 닷넷 CLR에만 존재하는 쓰레드로서 몇 개의 CLR 쓰레드가 하나의 WIN32 쓰레드에서 수행될 수 있음을 의미한다. 실제로 이러한 작업은 CLR에 의해 수행되며, 어떤 CLR 쓰레드가 WIN32 쓰레드에서 수행되도록 할 것인가에 대한 제어는 프로그래머에 의해 조정될 수 없다. 간단히 말해 CLR 쓰레드는 닷넷 CLR에만 존재하며 오직 CLR에 의해서만 제어되는 소프트웨어적인 쓰레드라 할 수 있다. 99.9%의 경우 개발자는 CLR 쓰레드에 대해 인지할 필요가 없으며 WIN32 쓰레드와 동등 혹은 동일한 것으로 파악해도 아무런 무리가 없다. 실제로 MSDN을 뒤져봐도 CLR 쓰레드에 대한 문서는 전혀 없으며 MSDN 매거진 같은 잡지에 극히 일부만 언급되는 내용이 CLR 쓰레드이다.

프로그래밍적인 관점에서 System.Threading.Thread 클래스는 CLR 쓰레드를 나타내고 있다고 봐야 한다. 하지만 99.99%의 경우 이 클래스를 WIN32 쓰레드로 봐도 무방하다. 하지만 폼 인증과 앞으로 설명할 역할 기반의 권한 검사에서 CLR 쓰레드의 개념을 이해하는 것이 많은 도움이 된다.

필자가 전혀 몰라도 무방할 CLR 쓰레드에 대해 언급하는 이유는 이렇다. 윈도우 인증 혹은 폼 인증을 사용할 때 Page.User 프로퍼티가 나타내는 계정 때문이다. Page.User 프로퍼티는 현재 페이지를 액세스하는 사용자의 인증 정보(사용자 계정)를 나타내는 프로퍼티인데, 이 프로퍼티의 값을 해석함에 있어 혼동이 올 소지가 다분하다. 백문이 불여일견이고 백견(白見)이 불여일Run이다. 실제로 테스트를 해보자. 윈도우 인증에서 impersonate가 true로 설정되어 있다고 가정해 보자. 그리고 페이지에 익명 액세스가 허용되어 있다. 이 때 프로세스 계정은 ASPNET이고 쓰레드 계정(정확하게 WIN32 쓰레드 계정)은 IUSR_XXX이다. 그렇다면 Page.User 프로퍼티는 IUSR_XXX 계정을 지칭하고 있을까? 다음과 같이 코드를 추가하여 Page.User 프로퍼티의 정보를 출력해 보자.

if (Page.User.Identity.IsAuthenticated) {

lblAuthUserName.Text = Page.User.Identity.Name;

}

else {

lblAuthUserName.Text = 인증되지 않음;

}

출력 결과는 <화면 10>과 같다. 프로세스 계정은 권한 문제 때문에 표시할 수 없고 쓰레드 계정은 IUSR_XXX이다. 하지만 Page.User가 나타내는 계정은 ‘없음’이다. 이것이 무슨 이슈인지 의아해 할지도 모르겠지만 Page.User 프로퍼티는 실제로는 System.Threading.Thread.CurrentPrincipal 프로퍼티와 같은 객체이기 때문이다. 이는 곧 현재 쓰레드의 계정이 ‘없음’에 해당한다. 하지만 쓰레드의 액세스 토큰은 IUSR_SIMPLE이라고 표시되지 않았는가? 액세스 토큰은 WIN32 쓰레드에 해당되는 이야기다. CLR 쓰레드의 계정(Principal)은 액세스 토큰과 무관하다. 비록 WIN32 쓰레드가 IUSR_XXX의 액세스 토큰에서 수행된다 할지라도 CLR 쓰레드는 인증되지 않은 상태로도 있을 수 있다는 얘기다.

<화면 10>의 결과는 논리적인 의미와 매우 일치되는 결과이다. Default.aspx가 익명 액세스를 허용하기 때문에 CLR 쓰레드는 익명의, 즉 인증되지 않은 사용자임을 나타낸다. 하지만 Default.aspx 파일에 접근하기 위해 WIN32 쓰레드는 반드시 계정을 필요로 하므로 익명 사용자에 걸맞는 제한된 권한만을 갖는 IUSR_XXXX 계정으로 가장을 하는 것이다. 만약 익명 액세스를 허용하지 않고 NTLM 인증을 수행하면 어떻게 될까? 이렇게 되면 CLR 쓰레드 계정과 WIN32 쓰레드의 계정은 일치하게 된다(<화면 11>).

폼 인증이 사용되면 CLR 쓰레드의 계정은 더욱 극명하게 나타난다. <화면 12>는 폼 인증을 사용하고 impersonate가 false이며 익명 액세스를 허용한 경우의 결과이다. 익명 액세스가 허용됐으나 web.config에서 impersonate를 허용하지 않았으므로 WIN32 쓰레드는 프로세스 계정인 ASPNET 계정에서 수행된다. 하지만 CLR 쓰레드의 계정은 폼 인증의 결과안 formUser임에 주의해야 한다. <화면 12> 같은 경우 Default.aspx에서 웹 서버 의 어떤 파일을 액세스하고자 한다면 그 파일은 ASPNET 계정에 대해 권한이 있어야만 한다. 파일 액세스는 WIN32 쓰레드 계정과 관계가 있기 때문이다. 하지만 조금 있다 설명할 URL 권한 검사나 역할 기반 권한 검사에서는 CLR 쓰레드의 계정이 사용됨에 유의해야 할 것이다.

CLR 쓰레드의 계정 설정은 System.Threading.Thread.CurrentPrincipal 프로퍼티를 통해 수행될 수 있다. ASP.NET은 웹 애플리케이션에서 설정된 인증 방법에 따라 이 프로퍼티에 Principal 객체를 만들어 준다. 특이한 점은 이 프로퍼티 값을 코드에 의해 임의로 수정할 수 있다는 점이다.

CLR 쓰레드의 등장으로 독자들이 혼란에 빠졌을지도 모르겠다. 이제 차분히 정리를 해보자. WIN32 쓰레드는 운영체제 레벨에서 제공되는 쓰레드로서 파일 액세스나 기타 운영체제에서 제공하는 자원에 접근하기 위해서는 WIN32 쓰레드가 어떤 액세스 토큰(쓰레드 계정)을 갖고 있는가가 중요하다. 반면 CLR 쓰레드는 소프트웨어적이며 개념적인 쓰레드로서 운영체제가 아닌 닷넷 수준에서 제공하는 보안 처리를 하는데 CLR 쓰레드의 계정이 사용된다. 일례로 윈도우 98/Me는 멀티 쓰레딩을 제공하지만(WIN32 쓰레드가 존재하지만) 각 쓰레드의 액세스 토큰은 존재하지 않는다. 반면 윈도우 98/Me에서도 닷넷 CLR은 작동하며 CLR 쓰레드는 계정 정보를 가질 수 있다.

ASP.NET 권한 검사

지금까지 많은 지면을 할애하여 인증에 대해 살펴봤다. 운영체제 입장에서 프로세스 계정과 WIN32 쓰레드 계정을 그리고 CLR 쓰레드의 계정 설정을 살펴봤다. 이렇게 설정된 인증 정보를 통해 ASP.NET은 세 가지 수준의 권한 검사를 수행할 수 있다. 첫째는 파일 수준의 권한 검사이며 두 번째는 URL 권한 검사, 세 번째는 역할 기반 권한 검사이다.

파일 수준의 권한 검사는 ASP.NET만의 고유 기능이라기보다는 IIS와 연계되는 서버 측 기술들(ASP, ISAPI)이 공통적으로 제공하는 권한 검사라고 볼 수 있다. 즉, 특정 페이지를 브라우저가 요청했을 때 그 페이지 파일에 대한 권한이 있는지를 검사하는 것으로서 ASP.NET이 수행하는 가장과 WIN32 쓰레드 계정과 밀접하게 연관된다. 예를 들어 폼 인증을 사용하고 impersonate가 true로 설정되어 있을 때 aaa.aspx를 브라우저가 접근하한다면 두 가지 사항이 점검 대상이 된다. 첫째로 IIS의 메타베이스 설정과 aaa.aspx에 설정된 윈도우 권한이다. IIS 보안 설정이 이 파일에 대해 익명 액세스를 허용하더라도 IUSR_XXXX 계정이 aaa.aspx 파일에 읽기 권한이 없다면 오류가 발생할 것이다. 하지만 폼 인증에서 수행한 인증 정보는 aaa.aspx를 접근할 수 있는가 와는 전혀 무관한 설정이다. 폼 인증에서 제공한 계정은 WIN32 쓰레드 계정이 아닌 CLR 쓰레드 계정과 관계가 있기 때문이다.

따라서 폼 인증을 사용할 때 원인 모를 액세스 거부 오류가 발생하거나 뜬금없이 네트워크 암호 대화상자가 브라우저에 나타난다면 ASPNET 계정이나 IUSR_XXXX 계정이 오류를 발생시킨 aspx 파일 혹은 디렉토리에 읽기 권한이 있는가를 검사해야 한다. 또, ASP.NET 웹 페이지 내에서 System.IO 네임스페이스의 클래스를 이용해 파일을 읽거나 쓰려고 할 때 역시 그 파일에 대해 ASPNET 혹은 IUSR_XXXX 계정이 적절한 권한이 있는지 살펴야 할 것이다.

독자들이 필자에게 가끔씩 하는 질문 중 하나가 COM+ 컴포넌트를 ASP.NET에서 사용할 때 레지스트리 관련 오류가 발생한다는 것이었다. 이 오류는 닷넷으로 작성한 COM+ 컴포넌트가 최초로 액세스될 때 등록되지 않은 COM+가 발견되면 스스로 COM+ 카탈로그에 등록하려고 하는데 이때 ASPNET 계정이나 IUSR_XXXX 계정이 레지스트리에 권한이 없기 때문에 발생하는 오류이다. 이 오류를 해결하기 위해서는 web.config 파일의 태그에서 권한이 있는 사용자로 가장을 하거나, machine.config 파일을 수정하여 aspnet_wp.exe 프로세스가 ASPNET이 아닌 다른 계정으로 수행되도록 하던가, 아니면 자동 등록이 발생하지 않도록 미리 수동으로 등록을 해야 한다.

URL 권한 검사는 브라우저가 요청한 URL에 대해 사용자별로 권한을 주거나 주지 않을 수 있음을 말한다. URL 권한 검사 설정은 web.config 파일의 태그를 통해 권한을 설정할 URL을 명시하고 태그를 통해 어떤 사용자 계정 혹은 역할(role)에게 액세스를 허용할 것인지를 결정한다. 이 때 권한 검사에 사용되는 계정은 CLR 쓰레드의 계정임에 유의하자. URL 권한 검사를 위한 설정이나 구체적인 적용 방법은 여러 ASP.NET 관련 서적에서 상세히 다루고 있고 MSDN에도 충분한 설명이 있으므로 더이상 다루지 않겠다.

역할 기반 권한 검사는 닷넷 CLR에서 제공하는 닷넷만의 독특한 보안 기능으로서 ASP.NET뿐만 아니라 WinForm 애플리케이션, 콘솔 애플리케이션 등 모든 닷넷 애플리케이션에서 적용할 수 있는 보안 메커니즘이다. 역할 기반 권한 검사는 어떤 메쏘드 혹은 코드 블럭을 수행하는데 필요한 권한을 역할에 따라 결정하겠다는 말이다. 예를 들어 데이터베이스에서 데이터를 읽어 오는 작업을 수행하는 메쏘드가 있다고 가정해 보자. 이 메쏘드는 관리자만이 호출할 수 있으며 일반 사용자는 접근을 허용하지 않아야 한다. 이 요구 사항을 만족시키기 위해 흔히 사용하는 접근 방법은 메쏘드의 도입부에서 메쏘드를 호출한 사용자 정보를 읽고 이 정보를 바탕으로 액세스 허용 여부를 결정하는 것이다. 이러한 권한 검사 코드는 필연적으로 if와 같은 조건문을 사용하기 마련이고 권한 설정이 복잡하거나 다양한 역할에 대해 권한 검사를 해야 한다면 이러한 조건문의 개수는 기하급수적으로 늘어나기 마련이다. 하지만 닷넷에서는 이러한 역할 기반 권한 검사에 대한 인프라를 갖추고 있다. 다음 코드가 이러한 역할 기반의 권한 검사를 해준다.

[PrincipalPermission(SecurityAction.Demand, Role=administrators)]

static void DoDatabaseAccess()

{

// 데이터베이스 조회 코드

// administrators 역할을 가진 사용자(principal)만이 이 함수를 호출할 수 있다.

}

앞 코드에서 주의깊게 볼 부분은 PrincipalPermission 특성이다. 이 특성은 선언적(declarative)인 보안 검사를 할 수 있도록 해주는 것으로서 호출자가 administrators 역할을 갖지 않으면 DoDatabaseAccess 메쏘드 호출은 SecurityException 예외를 발생시킬 것이다. 역할 기반 권한 검사는 이 컬럼에서 그 내용을 모두 다룰 수 없으므로 상세한 내용은 MSDN 도움말을 참고하기 바란다.

역할 기반 권한 검사에서 우리가 주목해야 할 것은 권한 검사를 수행할 대상이 무엇이냐는 것이다. PrincipalPermission 특성의 이름이 암시하듯 검사 대상은 CLR 쓰레드의 계정이 된다. 즉, 역할 기반 권한 검사는 WIN32 쓰레드의 액세스 토큰과는 전혀 무관하며 오로지 Thread 클래스의 CurrentPrincipal 프로퍼티가 반환하는 계정 정보에 의해서만 역할 기반 권한 검사가 수행된다. 이 점을 독자들은 주지해야 할 것이다.

ASP.NET이 제공하는 보안 모델에서 권한 검사는 다양하고 풍부하지만 여전히 실제 애플리케이션에서 적용하기에는 어려운 점이 있다. 예를 들어 보자. 어떤 웹 페이지는 데이터를 조회/추가/수정/삭제를 할 수 있다. 그러나 이 페이지는 사용자에 따라 서로 다른 권한을 주고자 한다. 관리자는 조회/추가/수정/삭제를 할 수 있으며 파워 유저는 조회/수정을 그리고 일반 사용자는 조회만이 가능하게 하고 싶다. 이 문제를 해결하는 방법으로 파일 기반 권한 검사나 URL 기반 권한 검사는 적절하지 않다. 모든 사용자가 이 웹 페이지를 열 권한은 있어야 하기 때문이다.

따라서 역할 기반의 권한 검사가 적절할 것처럼 보이지만 역할 기반 권한 검사는 약간의 한계가 있다.

첫째로 호출하기 전에는 권한이 있는지 없는지 알기 어렵다. 이 경우 일반 사용자가 수정 버튼을 클릭해 보고서야 자신이 권한이 없다는 것을 알게 된다. 일반 사용자가 이 페이지를 열었을 때 아예 추가/수정/삭제 버튼이 Disable하도록 하기 어렵다는 점이다. 물론 이 기능이 불가능하다는 것은 아니다. 하지만 앞서 본 코드처럼 선언적으로 보안 검사를 하면 소스 내에 하드 코드가 되기 때문에 수정이나 관리가 어려워진다. 프레임워크에서 제공하는 다른 클래스를 이용하면 프로그램적으로 검사가 가능하지만 이 역시 개발자가 많은 코드를 작성해야 한다. 대부분의 경우 사용자가 어떤 웹 페이지에 어떤 권한이 있는가는 데이터베이스에 별도로 저장되기 마련이고 권한 검사가 필요할 때 이 데이터를 읽어서 처리하는 것이 대부분이다. 이런 점에서 볼 때 닷넷에서 제공하는 역할 기반 권한 검사 보다는 애플리케이션 레벨에서 권한 검사 코드를 작성하는 것이 일반적이라 할 수 있겠다.

IIS 6.0과 보안

윈도우 2000 서버 제품군의 다음 버전인 윈도우 닷넷의 RC(Release Candidate) 버전이 출시됐다. 윈도우 닷넷 서버는 닷넷 프레임워크를 기본으로 탑재하고 있으며 IIS 6.0과 COM+ 1.5이 제공되고 윈도우 XP와 같은 UI를 갖고 있다. 윈도우 닷넷에 포함된 IIS 6.0은 기존 버전에 비해 다양한 기능이 추가됐으며 이 기능은 ASP.NET과 IIS의 통합, 보안 강화, 웹 서비스의 안정성 증대, 성능 개선 등으로 축약할 수 있다. 여기서 이들에 대한 모든 내용을 살펴볼 수 없으므로 보안에 관련된 부분 몇 가지만 언급하도록 하겠다.

IIS 6.0에서 크게 바뀐 점은 IIS 6.0이 제공하는 보안 기능이다. IIS 6.0은 설치되면 ASP, ISAPI, ASP.NET 등 서버 측에서 수행(execution)을 요구하는 어떤 스크립트나 코드도 수행되지 않도록 돼 있다. 서버 측 수행이 필요하다면 관리자는 이들을 명시적으로 Enable시켜야 한다. 이는 사용하지 않더라도 윈도우와 함께 디폴트로 설치된 IIS가 코드레드 류의 바이러스에게 공격을 받는 것을 방지하기 위함이다. 어찌됐건 IIS 6.0이 설치되면 디폴트로 제공되는 서비스는 정적인 htm 파일이나 이미지 파일뿐이다.

IIS 6.0은 HTTP 80 포트를 inetinfo.exe가 리스닝하지 않는다. 이제 80 포트는 윈도우 커널(HTTP.SYS)이 리스닝하게 된다. inetinfo.exe 프로세스는 메타베이스를 관리하는 작업만을 수행하며 inetinfo.exe 프로세스에서 서비스되는 웹 애플리케이션은 더이상 존재하지 않는다. 모든 웹 애플리케이션은 IIS 작업 프로세스 w3wp.exe에서 작동되며 작업 프로세스는 설정에 따라 두 개 이상 수행될 수 있다. 이는 ASP.NET 웹 애플리케이션에도 해당되며 더 이상 aspnet_wp.exe는 존재하지 않는다. 결과적으로 In-proc이나 Out-of-Proc이니 하는 용어도 사라져 버렸다. 다만 어떤 웹 애플리케이션을 어떤 IIS 작업 프로세스(w3wp.exe)에서 수행시킬 것인가를 결정하는 애플리케이션 풀(application pool) 개념이 등장했다.

애플리케이션 풀은 하나 혹은 그 이상의 웹 애플리케이션을 호스팅할 수 있으며 애플리케이션 풀은 두 개 이상의 작업 프로세스에 부하를 분산시킬 수도 있다(WebGarden). 이러한 변화 때문에 윈도우 닷넷 서버에서는 aspnet_isapi.dll이 존재하지 않으며 aspnet_wp.exe 프로세스도 ASPNET 계정도 존재하지 않는다. ASP.NET 애플리케이션이 IIS 작업 프로세스에 의해 호스팅되므로 애플리케이션 프로세스 계정은 이 프로세스의 계정을 따르게 된다. IIS 작업 프로세스 계정은 미리 정의된 SYSTEM, NETWORK_SERVICE, LOCAL_SERVICE 계정이나 임의의 계정으로 설정될 수 있다(<화면 13>).

간략하게나마 IIS 6.0의 보안 사항을 이 컬럼에서 다룬 내용을 위주로 살펴봤다. WIN32 쓰레드 계정이나 CLR 쓰레드 계정은 윈도우 2000과 동일하다. IIS 6.0은 ASP.NET을 통합함으로서 진정한 닷넷을 위한 웹 서버의 역할을 한다고 할 수 있겠다. IIS 6.0의 새로운 기능에 대해서는 다음 기회에 상세히 다룰 것을 약속하는 바이다.

보안 오류를 자신있게 해결하자

지금까지 ASP.NET의 보안에 관련된 여러 가지 사항을 살펴봤다. 일반적인 내용보다는 그 뒤에 숨겨진 원리나 작동 방식에 주안점을 두었고 이러한 내용이 실제 문제를 해결하는데 도움이 되리라 생각한다. IIS에 기반한 많은 웹 개발자들이 전혀 예상하지 못한 보안 오류에 부딪히곤 하는데 대부분의 문제들은 IIS가 어떤 식으로 작동하는 지를 정확히 이해하지 못했거나 ASP.NET의 다양한, 아니 다양하다 못해 혼동스럽고 복잡하기까지 한 보안 사항을 상세히 이해하지 못했기 때문이다. 이 컬럼의 내용이 다른 ASP.NET 관련 서적과 함께 보안 오류를 해결하고 더 나아가 보다 안전한 웹 애플리케이션을 계획하고 설계하는데 도움이 되었으면 하는 바람이다. @