WMI(Windows Management Instrumentation)은 여러모로 쓸모 있는 기능들을 많이 제공합니다. 시스템의 다양한 정보들을 제공할 뿐만 아니라 시스템을 리부팅하거나, 서비스를 시작, 중단 하는 등의 작업도 수행할 수 있지요. 그 뿐인가요? 윈도우 시스템에서 발생하는 다양한 이벤트(레지스트리 변경, 서비스 시작/중단 등)도 친절하게 알려준다. WMI에 익숙하지 않은 독자라면, 필자의
Management & Instrumentation of Your own Application 란 글을 읽어 보기 바랍니다. 오늘은 WMI의 보안에 대해서 간략히(?) 살펴보겠습니다.
WMI Security
오늘의 노가리 거리는 WMI의 보안에 대한 내용이다. 필자도 단순히 WMI 역시 NTFS 나 액티브 디렉터리처럼 네임스페이스 혹은 클래스 별로 보안 설정을 할 것이라는 막연한 생각만 가지고 있었을 뿐, 구체적으로 보안 설정을 어떻게 하는지 알지 못 했다. 그러다 오늘 관련된 테스트를 한 김에 몇 마디 끄적여 볼까 한다.
Episode I : UnauthorizedAccessException
필자가 우연히(?) WMI에 관련된 예제 코드를 작성할 때, 심심풀이로 원격 컴퓨터의 정보를 WMI를 통해 읽도록 작성했었다. 원격 컴퓨터는 필자의 노트북과 동일한 사용자 계정과 비밀번호를 사용했기 때문에 이 코드는 아무런 문제 없이 작동했다(리스트1).
1 using System;
2 using System.Management;
3
4 public class WMIRemoteLogicalDiskSampleApp
5 {
6 [STAThread]
7 public static void Main(string[] args)
8 {
9 // 매개변수로부터 컴퓨터 이름, 아이디, 비밀번호를 받는다.
10 string computerName = null, userid = null, password = null;
11
12 if (args.Length == 1) {
13 computerName = args[0];
14 }
15 else if (args.Length == 3) {
16 computerName = args[0];
17 userid = args[1];
18 password = args[2];
19 }
20 else {
21 Console.WriteLine("usage: List Remote Logical Disks.exe [computername] [userid] [password]");
22 return;
23 }
24 string wmiPath = string.Format(@"\\{0}\root\cimv2", computerName);
25
26 ManagementObjectCollection queryCollection = null;
27
28 // 원격 컴퓨터 접속을 위한 옵션 설정
29 ConnectionOptions opt = new ConnectionOptions();
30 opt.Username = userid;
31 opt.Password = password;
32
33 ManagementScope scope = new ManagementScope(wmiPath, opt);
34
35 // ObjectQuery를 통해 검색 한다.
36 ObjectQuery query = new ObjectQuery("SELECT * From Win32_LogicalDisk ");
37 ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
38
39 try {
40 queryCollection = searcher.Get();
41 foreach(ManagementObject obj in queryCollection) {
42 Console.Write("{0} : [{1}]\t", obj["Name"], obj["Description"]);
43 // removable 디스크와 logical 하드 정보만 표시한다.
44 uint driveType = (uint)obj["DriveType"];
45 if (driveType == 2 || driveType == 3) {
46 Console.WriteLine(" : Total {0,6:0,000} MB, {1,6:0,000} MB Free",
47 (ulong)obj["Size"] / (1024*1024),
48 (ulong)obj["FreeSpace"] / (1024*1024));
49 }
50 else {
51 Console.WriteLine();
52 }
53 }
54 }
55 catch (Exception ex)
56 {
57 Console.WriteLine("WMI 예외 :\n{0}", ex.ToString());
58 }
59 }
60 }
리스트1. 로컬/원격 컴퓨터의 디스크 정보를 표시하는 간단한(?) WMI 예제 코드
여기서 테스트를 멈추었다면 인생이 이렇게 피곤하지 않았을 텐데 말이다. 공연히 다른 Windows 2003 서버에 대해 테스트를 해본답시고 프로그램을 한번 더 돌렸다. 그랬더니 UnauthorizedAccessException 예외가 발생하면서 "액세스가 거부되었습니다" 혹은 본토 말로 "Access Denied" 오류 메시지가 발생한 것 이였다. 갑자기 빡돌아 버린 필자, 원인을 찾기 위해 삽질을 시작하게 된 것이다.
Episode II : Remote WMI Access & DCOM Protocol
원격으로 WMI를 사용할 때는 DCOM (Distributed COM) 프로토콜이 사용된다. 원래 WMI 란 것이 COM을 통해 API를 제공하기 때문에 원격 컴퓨터를 WMI를 통해 액세스 한다면 DCOM이 사용되는 것은 당연하다고 할 수 있겠다. 닷넷의 System.Management 네임스페이스 역시 WMI의 COM 인터페이스에 대한 래퍼(wrapper)일 뿐이다. 언제나 이 놈의 COM에서 벗어날 수 있을까 하는 독자가 있다면, 적어도 몇 년 동안은 COM에 시달려야 할 것은 분명하다. 10여 년 동안 Microsoft의 핵심 기술로서 사용되어 왔고 많은 운영체제 컴포넌트들에 의해 사용되는 COM이 하루 아침에 사라지지 않을 것임은 너무나도 자명하다.
어찌 되었건, WMI를 통해 원격 컴퓨터를 액세스하면 DCOM이 사용되므로, DCOM에 관련된 제반 설정이 정확한가 먼저 살펴야 한다. DCOM 설정 중 가장 먼저 살필 것은 RPC에 대한 것이다. DCOM은 하부 프로토콜로서 RPC (Remote Procedure Call)를 사용하므로 RPC 설정 역시 검토의 대상이다(줴길... 먼 넘의 것이 이렇게 걸리는 게 많은지...). RPC 가 사용하는 포트, 방화벽 등등을 검토해 보아야 할 것이다. RPC가 사용하는 포트가 어떤 것이고, 이것을 바꾸는 방법, 방화벽 설정 그리고 RPC를 테스트하는 구체적인 방법은 지난 RPC 관련 포스트 시리즈들을 살펴보기 바란다. 간단히 요약하면, WMI 클라이언트와 WMI 서버 사이에 방화벽이 있다면 RPC가 제대로 작동하지 않을 확률은 99.999 % 이다. 따라서 RPC가 사용하는 포트를 조정하고 방화벽에서 이들 포트를 열어주어야만 한다.
액세스 하고자 하는 컴퓨터에 대해 RPC 가 작동한다고 끝은 아니다. 해당 원격 컴퓨터에 DCOM이 활성화 되었는가를 또 확인해 보아야 한다. 필자가 테스트한 첫 번째 서버는 이 설정이 잘못되어 있었다. 즉, DCOM을 사용 불가로 해놓았기 때문에 원격 WMI 액세스에 문제가 발생한 것이다. 확인 방법은 "구성 요소 서비스"의 "내 컴퓨터"의 등록 정보 메뉴를 선택하면 나타나는 "DCOM 기본 속성" 대화 상자를 사용하면 된다(화면1).

화면1. DCOM 사용 설정
이외에도 DCOM에서 사용하는 보안 설정을 살펴보아야 한다. WMI 서버의 관리자 계정(Administrators 그룹에 포함된 계정)을 사용한다면 문제는 없을 것이다. 이들 관리자 계정들은 기본적으로 DCOM을 원격에서 사용할 수 있도록 설정되어 있다. 만약, 미천한 일반 계정으로 원격 WMI 호출을 해야 한다면 DCOM 보안 설정을 해 주어야 한다. DCOM 보안 설정은 설명할 것이 존나게 많은 관계로 다음 기회로 미루기로 하겠다.
Episode III : WMI Namespace Security
DCOM에 대한 여러 테스트 및 설정 결과, 필자가 사용할 수 있는 다양한 원격 컴퓨터들에 대해 리스트1의 코드는 정상적으로 작동했다. 여기서 만족하고 테스트를 끝냈다면, 이 포스트를 쓸 일도 없었을 것이다. 하지만, 필자의 병적인 호기심이 또 발동한 것이다. 관리자 계정이 아닌 일반 계정으로 원격 WMI 액세스도 가능하겠지? 그래서 테스트 해봤다. 물론 일반 계정으로 리스트1의 코드를 수행해 보면 UnauthorizedAccessException 이 발생한다. DCOM 보안 설정이 문제인 것으로 판단되어 DCOM 보안 설정을 해봤다. 그래도 결과는 마찬가지 였다. 아무리 DCOM 보안 설정을 이리저리 바꾸어도 관리자 계정이 아닌 일반 사용자 계정으로 원격 WMI 액세스는 실패했다. 분노 게이지가 상승한 필자... 간단한 DCOM 서버까지 작성하고 일반 사용자 계정으로 호출해 보니, 잘 작동하는 것이 아닌가?
그렇다. 일반 사용자 계정을 사용하면 DCOM 보안 설정을 통과 하더라도 WMI 보안에 의해 원격 호출은 실패하는 것 이였다. 로컬 컴퓨터를 액세스하는 경우, 일반 사용자 계정을 사용하더라도 리스트1 정도의 '읽기' 작업은 오류를 발생하지 않는다. 하지만, 원격 액세스를 하는 경우에만 일반 사용자 계정에 제한이 있는 것 이였다. 잽싸게 MSDN 라이브러리를 뒤져 보았다. 젠장... 예전에 그냥 WMI 도 별도의 보안이 있을 것이다라고 만 생각하고 까맣게 잊어버린 바로 그 보안 설정이 MSDN에 떡하고 설명되어 있지 않은가? 이래서 닭 대가리는 고달픈가 보다.
WMI는 자체적으로 각 WMI 네임스페이스 별로 액세스 권한을 명시할 수 있도록 되어 있다. 각 네임스페이스에는 __SystemSecurity 라는 WMI 클래스를 갖고 있고 이 클래스의 메쏘드들을 호출하여 WMI 클래스 및 인스턴스에 대한 액세스 권한을 읽거나 설정할 수 있다. 이렇게 WMI 네임스페이스에 설정된 액세스 권한은 해당 네임스페이스의 모든 하위 클래스 및 인스턴스에 대한 액세스 권한을 설정한다. 구체적인 예제 코드를 설명하자면, SID (Security Identifier), ACL (Access Control List) 등을 모두 설명해야 하고, 이들에 대한 설명은 책 한 권 수준이므로 대략 무리다. 코드 말고 권한 설정을 해줄 방법은 없을까?
물론 있다. 필자도 이제서야 알게 된 WMI 컨트롤이란 것을 사용하면 된다. WMI 컨트롤 이라는 녀석은 로컬 컴퓨터의 WMI 보안 설정과 WMI Repositary를 백업하는 등의 관리 기능을 제공하는 다이얼로그를 말한다. WMI 컨트롤을 사용하는 방법은 매우 쉽다. 사실 눈길을 잘 안 줘서 그렇지 우리들이 잘 아는 곳에 위치해 있다. 제어판의 시스템을 선택하거나, "내 컴퓨터"의 관리 메뉴를 선택하면 나타나는 "컴퓨터 관리" MMC에서 "서비스 및 응용 프로그램" 폴더의 하부를 살펴보면, WMI 컨트롤이란 것이 떡 하고 버티고 있을 것이다(화면 2).

화면2. "컴퓨터 관리" MMC에서의 WMI 컨트롤
WMI 컨트롤의 오른쪽 패널은 별다르게 표시되는 것이 전혀 없으므로 신경을 끄고, WMI 컨트롤의 컨텍스트 메뉴(오른쪽 클릭하면 나타나는 메뉴 있자나...)의 속성을 지긋이 눌러 보자. 그러면 WMI 컨트롤이 나타날 것이다(화면 3). 오 놀라워라... 이런 것이 있었는 줄은 정말 몰랐다... 우~~~워~~~ -_-;

화면3. WMI 컨트롤과 네임스페이스 보안 설정
WMI 컨트롤에서 보안 탭을 살펴보면 WMI Repository 에 저장된 WMI 네임스페이스들이 트리 형태로 나타난다. 그리고 각 네임스페이스를 클릭하고 "보안" 버튼을 누르면 네임스페이스에 설정된 액세스 권한을 보여주는 표준 액세스 권한 대화 상자가 나타날 것이다(화면 3). 대부분의 Windows 보안이 그러하듯이 권한은 상속된다. WMI 역시 Root 네임스페이스에 설정된 권한을 하위 네임스페이스들이 상속 받고 있음을 확인해 볼 수 있다. 그리고...
Everyone 그룹에 설정된 액세스 권한 설정의 찬찬히 살펴보면... "원격으로부터 사용 가능" 이란 권한이 기본적으로 주어져 있지 않음을 발견할 수 있다. 이런 C8, 이런 게 있는지 모르면 백날 방화벽에, RPC 포트에, DCOM 보안 설정해봤자 관리자가 아닌 계정으론 항상 Access Denied 오류가 발생할 것 아닌가... 하긴 몰랐던 필자가 바보지, MS가 이걸 숨긴 건 아니니까... T_T
WMI 액세스 권한의 종류와 의미는 MSDN을 참조해 보면 알 수 있다. 허탈감에 빠진 필자도 상세하게 읽어 보진 못 했다. 대충, "계정 사용" 권한은 해당 계정이 WMI 스키마와 객체의 속성을 읽을 수 있는 권한을 의미하며, "메서드 실행" 권한은 WMI 클래스의 메쏘드를 호출할 수 있는 권한을 말한다. 여기서 주의할 점은 "메서드 실행" 권한이란 것이 WMI 클래스의 메쏘드를 호출할 수 있는 권한만을 얘기하는 것이며 메쏘드 내에서 수행하는 작업에 대한 권한은 아니라는 점이다. 예를 들어, 시스템을 리부팅하는 메쏘드를 호출할 때는 메쏘드를 호출하는 권한은 WMI에 의해 확인되지만, 리부팅 하는 권한은 별도로 확인되어 진다.
"전체 쓰기 권한"은 WMI 클래스의 속성을 쓰거나 인스턴스의 속성에 대한 쓰기 권한을 얘기하는 것 같고, "일부 쓰기" 권한은 WMI 인스턴스의 속성에 대한 쓰기 권한을 의미하는 것 같다. 사실 필자도 정확한 테스트를 안 해봐서 잘 모르겠다. 테스트 하고 싶지도 않고... 나중에 필자가 WMI 권한에 관련되어 다시 분노 게이지가 상승하거나 똥줄 타게 바쁜 일이 생기면 모를까...
기본 설정을 살펴 보면... 관리자 계정은 모든 권한이 주어져 있으며, 관리자가 아닌 일반 계정은 Everyone 그룹에 포함되므로, 로컬 컴퓨터에 대해서는 WMI 개체의 속성을 읽거나 메쏘드를 호출하는 데는 문제가 없다. 하지만 원격에서의 액세스는 제한되어 있다. 관리자가 아닌 일반 사용자 계정을 사용하여 원격에서 WMI를 액세스하고자 한다면, 해당 계정에 대해 "원격으로부터 사용 가능" 권한을 허용해야 한다.
아무 생각 없이 Everyone에 이 권한을 주는 무뇌충 같은 짓은 하지 말기 바란다. Everyone은 말 그대로 모든 계정(익명 사용자 계정 제외)을 포함하므로 권한을 주는데 신중을 기해야 한다. 필자 같으면 별도의 그룹을 만들고(예를 들어 Remote WMI Access Users) 이 그룹에 대해 권한을 주겠다. 그리고 원격 WMI 액세스가 필요한 사용자들을 이 그룹에 포함시키는 방법을 사용할 것이다.
또 한가지 주의할 점은, 필요한 사용자에게 필요한 네임스페이스에 대해서만 권한을 주는 것이다. 앞서도 이야기 했지만, 권한은 상속되므로 Root 네임스페이스에 권한을 주게 되면 하위 모든 네임스페이스에 모두 권한을 주게 된다. 따라서, 가장 많이 사용되는 Win32_XXX 클래스들이 존재하는 root\CIMV2 네임스페이스 정도에만 권한을 주는 것이 좋다.
Episode IV: Conclusion
WMI는 자체의 WMI 클래스, 인스턴스 등에 대한 액세스 권한을 갖고 있으며, 이 액세스 권한은 WMI 네임스페이스 단위로 설정된다. WMI 네임스페이스에 포함된 클래스, 인스턴스 들은 모두 동일한 권한에 의해 지배 받게 된다. 기본적으로 관리자는 모든 권한을 갖고 있으며, 일반 사용자 계정은 로컬 컴퓨터에 대해서는 읽기, 메쏘드 호출 등의 권한을 갖지만, 원격 액세스는 제한되어 있다. WMI 보안 설정을 읽거나 수정하는 방법은 WMI 네임스페이스마다 존재하는 __SystemSecurity 클래스의 메쏘드를 사용하거나 WMI 컨트롤 을 사용하면 된다.
원격으로 WMI를 액세스 할 때는 WMI 보안 뿐만 아니라, WMI 원격 호출에 사용되는 DCOM 프로토콜의 보안 설정, 기타 DCOM 관련 설정, 그리고 DCOM의 하부 프로토콜로 사용되는 RPC 설정을 확인해야 한다. 클라이언트와 WMI 서버(WMI를 통해 액세스하고자 하는 컴퓨터) 사이에 방화벽이 존재하는 경우, RPC 호출에 문제가 발생할 수 있으므로 적절한 방화벽 설정과 RPC 포트 설정이 필요하며, DCOM 보안 설정 역시 살펴보아야 할 사항이다.
대개의 경우, WMI 서버의 관리자 계정을 사용하면 방화벽 문제를 제외하고 다른 문제는 발생하지 않는다. 관리자 계정은 DCOM 보안 설정과 WMI 보안 설정이 모두 적절하게 설정되어 있다. 하지만 관리자 계정이 아닌 일반 사용자 계정을 사용한다면, DCOM 보안 설정과 WMI 보안 설정을 모두 수행해 주어야 한다.