COM 구성 요소는 아주 유용할 수도 있고, 아주 위험할 수도 있습니다.
Windows®에서 응용 프로그램을 구축하는 일반적인 방법은 계속해서 타사 COM 구성 요소를 구입하거나 아니면 기본 C 기반의 인터페이스를
사용하는 DLL을 구입하여 하나의 프로세서에 통합하는 방법입니다. 이 모듈러 방법을 신중히 사용하면 재사용, 느슨한 연결 및 기타 소프트웨어
개발 과정에 도움이 되는 사항들을 향상시킬 수 있지만 보안상의 허점을 키우는 결과를 가져오기도 합니다.
MSDN® Magazine 2000년 7월호에 실린 Microsoft IIS(Internet
Information Service) 보안을 다룬, 두 부분으로 이루어진 시리즈 기사 중 두
번째 부분? 에서 소프트웨어
프로그램에 대한 가장 일반적이면서도 무서운 공격 중 하나인 버퍼 오버플로에 대해 다루었습니다. 그리 위험하지는 않지만 널리 퍼져있는 한 버그는
DLL 내부 어딘가에 숨어서 공격자가 호스트 프로세서를 파괴할 뿐만 아니라 그 보안 내용을 훔칠 수 있도록 할 수도 있습니다. 쉽게 구할 수
있는 타사 DLL에 프로세스를 결합하면 공격자가 사용되는 개별 DLL에서 공격에 이용할 수 있는 다양한 허점을 발견할 수 있는 시간이 많아지기
때문에 문제가 더 심각해집니다. 즉, 공격자는 안전한 장소에서 충분한 시간 동안 공격을 준비한 다음 사용자의 응용 프로그램을 공격할 수 있게
됩니다.
Authenticode®가 이러한
문제를 해결하는 데 도움이 될 것이라고 생각했습니다. 물론, 아무런 조치도 취하지 않는 것보다는 낫습니다. 하지만 문제는 이 방법은 예방 조치가
아니라 처벌 조치라는 점입니다. 공격자가 사용자의 친한 친구 및 친지 모두에게 사용자의 반송 주소와 함께 음란 웹 사이트 링크 목록을 모두 전자
메일로 보낸 후에는 공격자의 신분을 알아 낸다고 해도 큰 위안이 되지 않습니다. 일반 사용자들이 온라인 경험을 강화시켜준다고 주장하는 DLL을
기꺼이 설치할 준비가 되어 있는 이상 그 작성자가 DLL에 합법적으로 서명을 한다고 해도 어떤 DLL이 실제로 이러한 손상을 입혔는지를
비프로그래머가 어떻게 알아낼 수 있겠습니까? 공격 도중 DLL이 설치된 하드 드라이브가 삭제되거나 손상되면 어떻게 하겠습니까? 그리고 그 증거는
어떻게 되겠습니까? 어떻게 공격자를 법정에 세우겠습니까?
코드, 특히 지금까지 의지해 온
ActiveX® 구성 요소와 같은 이동 코드에 대한 책임뿐만 아니라 액세스 제어도 필요합니다. 관리자가 구성 요소로 이루어진 프로세스를 실행할
때에는 자신의 보안 내용이 나쁜 구성 요소로부터 보호된다는 보장을 받을 수 있어야 합니다. 관리 코드의 확인을 통해 버퍼 오버플로 문제를
제한하는 것이 첫 번째 단계입니다. 그 다음, 코드 액세스 보안은 완벽한 해결책은 아니지만 중요한 두 번째 단계이며 이 기사의 주요 내용이기도
합니다.
이 기사는 일반 언어
런타임(CLR)의 기술 미리 보기를 토대로 작성된 것으로, 최종판이 나오기 전에 변경될 수 있습니다.
개요
먼저, 코드 액세스 보안이 어떻게
이루어지는지에 대해 간단히 설명하겠습니다. 이 부분은 나중에 다룰 자세한 설명에 대한 요약이라고 할 수 있습니다. Java 2의 보안 모델을 잘
알고 있다면 CLR이 유사한 모델을 사용한다는 사실을 알 수 있을 것입니다.
일반적으로, 파일 읽기 또는
쓰기, 환경 변수 변경, 클립보드 액세스, 대화 상자 표시 등 보안이 필수적인 작업을 실행해야 할 때에는 CLR이 미리 패키지된 클래스를
사용하십시오. 이 클래스는 요청된 동작 유형을 시스템에 표시하는 보안 요구가 함께 작성되므로 시스템은 그 요청을 허가 또는 거부할 수 있는
기회를 갖게 됩니다. 이 요청을 거부할 경우 시스템은 예외 유형인 SecurityException을 만듭니다. 그렇다면 시스템은 각 요청에 대한
허가 또는 거부 여부를 어떻게 결정할까요? 바로, 컴퓨터 대 컴퓨터 기반 및 사용자 대 사용자 기반으로 사용자 정의할 수 있는 보안 정책을
조사해서 결정합니다.
CLR의 보안 정책은 개념적
차원에서 보면 아주 간단합니다. 이 보안 정책은 로드 시 어셈블리에 질문을 합니다. 현재는 이 어셈블리가 어디에서 생겨났으며, 누가 이
어셈블리를 작성하였는가라는 두 가지 질문이 일반적으로 사용되고 있습니다. 나중에 알게 되겠지만, 질문 방식은 조금씩 다를 수
있습니다.
보안 정책은 이 질문에 대한
다양한 대답을 특정한 사용 권한 집합으로 매핑할 수 있는 방법입니다. 예를 들면, "https://www.foobar.com/baz에서 가져온
코드는 c:\quux 하위 파일을 읽을 수 있으며 대화 상자는 표시할 수 없습니다"라고 할 수 있습니다. 이 경우에는 첫 번째 질문을 약간
변경하여 "이 어셈블리는 어느 URL에서 가져왔습니까?"라는 질문을 합니다. 다른 각도에서 본다면 이는 CLR 코드 액세스 보안을 요약하고
있지만 조금 더 깊게 들어가 보면 더 흥미롭습니다. 지금 살펴보도록 하겠습니다.
사용 권한 요청
파일에서 단순한 읽기 또는 쓰기를
허용하기 위해 다음 클래스를 작성하고 있다고 상상해 보십시오(구현 생략).
public class MyFileAccessor {
public MyFileAccessor(String path,
bool readOnly) {}
public void Close() {}
public String ReadString() {}
public void WriteString(String stringToWrite) {}
}
다음은 일반적으로 이 클래스를 어떻게 사용하는지를 보여 줍니다.
class Test {
public static void Main() {
MyFileAccessor fa = new
MyFileAccessor("c:\foo.txt", false);
fa.WriteString("Hello ");
fa.WriteString("world");
fa.Close(); // flush the file
}
}
이 사용 모델에서 보안 확인을
지정할 수 있는 가장 단순한 곳은 생성자 내부입니다. 생성자 인수는 정확히 어떤 파일을 액세스해야 하는지, 그리고 이 파일이 읽기 모드로
액세스되는지 아니면 읽기/쓰기 모드로 액세스되는지 알려줍니다. 사용자가 이 정보를 기본 보안 정책에 제시하고 그 결과
SecurityException이 만들어지면 예외가 호출자에게 다시 전달되도록 허용하면 됩니다. 이 경우 생성자는 절대 완료되지 않으므로
호출자는 사용자 클래스의 인스턴스를 거부하며 따라서 다른 비정적인 구성원 기능을 전혀 호출할 수 없게 됩니다. 이는 클래스에 대한 보안 확인을
크게 단순화시켜 주므로 프로그래머 입장에서도 좋지만 보안 측면에서도 좋습니다. 코드에 액세스 확인 논리가 적을수록 실수할 가능성도 더
줄어듭니다.
이 방법의 단점은 이 시나리오에서
볼 수 있습니다. 클라이언트가 생성자의 초기 요청 후 사용자 클래스의 인스턴스를 생성하고 해당 클라이언트가 잠재적으로 다른 어셈블리에 있는 다른
클라이언트와 참조를 공유한다면 새 클라이언트는 사용자의 생성자 중심 코드 액세스 요청에 영향을 받지 않습니다. 이는 현재 Windows
2000에서 커널이 작업을 처리하는 방식과 유사합니다. 여기에서도 성능과 보안 사이의 팽팽한 긴장 관계를 볼 수 있습니다.
실제로는 파일을 액세스하고자 할 경우 MyFileAccessor와 같은 클래스는 작성하지 않을 것입니다. 대신
System.IO.FileStream처럼 시스템이 제공한 클래스를 사용할 것입니다. 이 클래스는 사용자에게 적합한 보안 확인을 실행하도록 미리
디자인되어 있습니다. 가끔은 앞서 설명한 성능 및 보안 구현의 모든 방법을 이용하여 생성자에서 확인 작업을 실행합니다.
Microsoft®.NET 아키텍처처럼 확장성을 위해 FileStream 클래스가 호출하는 보안 확인은 사용자 자체 구성 요소에서 직접 사용할
수도 있습니다. 그림?1은 시스템에 제공된 클래스가 사용자 대신 보안 검사를 수행할 것인지 여부를 모를 경우
MyFileAccessor 생성자에 동일한 보안 확인을 추가하는 방법을 나타낸 것입니다.
그림?1의 코드는 두 단계로 보안 검사를 수행합니다. 먼저, 문제의 사용 권한 즉, 여기서는 파일 액세스를
나타내는 개체를 만듭니다. 그러면 시스템이 호출자의 사용 권한을 살펴보고 호출자가 그 특정 권한을 가지고 있지 않으면 Demand가
SecurityException을 만듭니다. 곧 알게 되겠지만, 실제로 Demand는 이보다는 조금 더 복잡한 검사 작업을
합니다.
그림?2는 현재 런타임이 표시한 다양한 코드 액세스 권한을 요약한 것입니다. 개요 부분에서 언급한 다양한
문제점에 대한 대답을 직접 테스트하는 코드 ID 사용 권한 집합이 있는지 확인하십시오. 하지만 보안 정책 결정을 그 구성 요소에 직접
하드코드하지 않도록 하려면 일반 코드가 코드 ID 사용 권한을 일반적으로 사용해서는 안됩니다. 보안 정책에 대해서는 뒤에서 자세히
다루겠습니다.
코드가 .NET 보안 검사를
성공적으로 끝낸다고 해도 기본 운영 체제가 자체적으로 액세스를 또 다시 확인한다는 것을 말하는 것이 좋을 것 같습니다. 예를 들면,
Windows 2000과 Windows NT®는 액세스 제어 목록을 통해 NTFS 파티션의 파일 액세스를 제한합니다. 따라서 .NET 보안이
로컬 파일 시스템에 대한 무제한 액세스를 한 어셈블리에 허가하고 그 어셈블리의 구성 요소가 Alice라고 실행되는 프로세스에 호스팅되어 있어도
이 구성 요소는 Alice가 기본 운영 체제의 보안 정책에 따라 정상적으로 열 수 있는 파일만 열 수 있을 뿐입니다.
유인 공격
허가할 사용 권한을 결정하기위해
어셈블리에 하는 기본적인 두 가지 질문이 "이 어셈블리는 어디에서 왔습니까?"와 "누가 이 어셈블리를 작성했습니까?"라는 점을 상기해 보십시오.
이 두 질문에 대한 답은 어셈블리에 허가할 사용 권한의 기본 집합을 언급하는 것입니다.
 그림 3 MyFileAccessor
MyFileAccessor는 그림?3처럼 FileStream 개체로 구현되었습니다. 다음은 이
코드의 예입니다.
using System.IO;
public class MyFileAccessor {
private FileStream m_fs;
public MyFileAccessor(String path,
bool readOnly) {
m_fs = new FileStream(path, FileMode.Open,
readOnly ? FileAccess.Read :
FileAccess.ReadWrite);
}
// ...
}
MyFileAccessor를
이러한 방법으로 구현하여 친구 Alice에게 전달했으며, Alice가 이를 자신의 로컬 하드 드라이브에 설치했다고 가정합시다.
MyFileAccessor 어셈블리는 어떤 종류의 코드 액세스 사용 권한을 가지고 있습니까? 제 로컬 보안 정책은 로컬 구성 요소에
FullTrust라는 사용 권한 설정을 허가합니다. 이 권한에는 파일 시스템에 대한 무제한 액세스가 포함되어 있습니다. 기본 운영 체제는 이
권한을 더 제한할 수도 있습니다.
그림?4는 Alice의 로컬 하드 드라이브에 설치된 MyFileAccessor가 다양한 유형의 구성 요소에
사용될 수 있음을 보여 줍니다. 트러스트된 로컬 구성 요소는 MyFileAccessor를 사용하여 파일을 액세스할 수 있습니다. 하지만
Alice가 자신의 브라우저를 불량 웹 사이트로 연결하면 이 사이트에서 다운로드된 .NET 구성 요소가 MyFileAccessor를 나쁜 목적에
사용할 수 있습니다. 이것이 유인 공격의 예입니다. MyFileAccessor를 유인하여 Alice가 공개하고 싶어하지 않는 개인 문서를 열게
할 수 있습니다.
 그림 4 유인 공격의 예
중개
구성 요소가 자체적으로 액세스를 확인하여 이러한 일이 발생하지 않도록 하지 않고 CLR은 단순히 호출 체인의 모든 호출자가 FileStream이
요청하는 사용 권한을 가지고 있는지 확인합니다. 그림?4에서 NotepadEx라는 로컬 구성
요소가 MyFileAccessor를 사용하여 파일을 열면 이 로컬 구성 요소는 무제한 액세스를 허가받습니다. 모든 호출 체인이 로컬로 설치된
어셈블리에서 만들어지기 때문입니다. 하지만 RogueComponent가 MyFileAccessor를 사용하여 파일을 열려고 하고
FileStream이 Demand를 호출하면 CLR이 스택을 검색하여 이 호출 체인의 호출자 중 하나가 필수 사용 권한을 가지고 있지 않다는
것을 확인하게 됩니다. 그러면 Demand가 SecurityException을 만듭니다.
암시적 사용 권한
이쯤에서, 어떤 사용 권한은
허가될 경우 다른 의미도 암시한다는 것을 집고 넘어가야 할 것 같습니다. 예를 들어, c:\temp 디렉터리에 대한 모든 액세스를 허가 받았다면
하위의 모든 디렉터리에 대한 액세스도 암시적으로 허가받은 것입니다. 모든 코드 액세스 사용 권한 개체에 있는 IsSubsetOf 메서드를 통해
이를 확인할 수 있습니다. 예를 들어, 그림?5의 코드를 실행하면 다음 결과를 얻게 됩니다.
p2 is subset of p1
암시적 사용 권한은 관리를 상당히
간편하게 만들어 주지만 사용 권한을 요청할 때에는 가능한 한 구체적으로 해야 한다는 것을 기억하십시오. CLR은 사용자 요청을 자동으로 비교하여
허가받은 사용 권한의 하위 집합인지 확인합니다.
자체 보호
어셈블리는 로드 시 기본 사용
권한을 부여 받습니다. CLR과 해당 호스트는 어셈블리에 대해 질문을 하고 보안 정책에 답을 제공하여 이 사용 권한을 확인하며 보안 정책은 답을
사용 권한으로 변환합니다. 하지만 정책을 기반으로 한 어셈블리가 기본 사용 권한 집합을 허가받았다고 해서 런타임 시 이 사용 권한을 모두
사용해서 요청에 응할 수 있다는 것은 아닙니다. 이에 대해 설명을 하겠습니다.
한 어셈블리가 로컬로 설치되어
있다면 이 어셈블리는 적어도 코드 액세스 보안에 관한 한 거의 무제한의 범위에 걸친 사용 권한을 갖게 될 것입니다. 이렇게 큰 신뢰를 받는
어셈블리 중 하나가 사용자가 제공하는 스크립트를 호출한다고 생각해 봅시다. 이 스크립트가 어떤 실행에 관한 것이냐에 따라 스크립트를 호출하는
어셈블리는 호출하기 전에 유효한 사용 권한을 제한하고자 할 것입니다(그림?6 참조).
이 코드는 현재 스택 프레임에
추가 제한 사항을 제공합니다. 이에 따르면, Calculate가 환경 변수를 액세스하거나 클립보드의 내용을 몰래 들여다 보거나 이 코드에 지정된
중요한 두 디렉터리 아래에 있는 파일 시스템의 파일들을 돌아다니려고 하는 경우, 또는 Calculate가 사용한 구성 요소가 내부적으로 이러한
작업을 하려고 하면 CLR이 해당 스택을 검색하여 액세스를 확인하며 스택 프레임이 이러한 사용 권한을 명시적으로 거부한다는 것을 확인하면
CLR은 요청을 거부합니다. 이 경우 몇 가지 사용 권한을 하나의 PermissionSet으로 모은 다음 사용 권한 전체를 거부했습니다. 각
스택 프레임이 많아야 하나의 사용 권한 집합을 거부에 사용할 수 있으며 Deny 함수를 호출하면 현재 스택 프레임에 맞게 이전 집합이 교체되기
때문입니다. 이는 다음 코드가 사용자가 기대하는 작업을 하지 않는다는 것을 뜻합니다.
FileIOPermission p1 = new FileIOPermission(
FileIOPermissionAccess.AllAccess,
"c:\\sensitiveStuff");
FileIOPermission p2 = new FileIOPermission(
FileIOPermissionAccess.AllAccess,
"c:\\moreSensitiveStuff");
p1.Deny(); // p1 is denied
p2.Deny(); // now p2 is denied (not p1)
이 코드에서 두 번째 Deny
호출은 첫 번째 호출을 효과적으로 덮어 쓰므로 이 경우에는 p2만 거부됩니다. 사용 권한 설정을 사용하면 둘 이상의 사용 권한을 동시에 거부할
수 있습니다. CodeAccessPermission 클래스에서 정적 RevertDeny 함수를 호출하면 현재 스택 프레임에 대한 거부 권한
집합이 비워집니다.
개별 사용 권한을 여러 개
거부하는 경우에는 다른 방법을 사용하는 것이 더 쉬울 수 있으며 Deny와 RevertDeny 대신 PermitOnly 및
RevertPermitOnly를 사용할 수 있습니다. 이 방법은 허용하고자 하는 사용 권한을 정확히 알고 있는 경우 좋습니다.
자신의 사용 권한 어설션
스택 검색 메커니즘은
FileStream 및 MyFileAccessor와 같은 일반 클래스를 보호합니다. 이러한 클래스는 다른 여러 방법으로 사용할 수 있습니다.
예를 들면, FileStream은 사용자 하드 드라이브의 잘 정의된 디렉터리에 있는 잘 정의된 로그 파일에 오류를 로그합니다. 한편,
FileStream을 악의적으로 사용하면 로컬 보안 정책 파일의 내용을 손상시킬 수 있습니다. 스택 검색 메커니즘은 FileStream 개체가
인스턴스로 생성되기 전에 얼마나 많은 중간 어셈블리가 통과했는지에 관계 없이 모든 어셈블리가 파일 시스템 액세스 요청을 만족시켜야 한다는 사실을
보장하도록 만들어 졌습니다. 이 방법을 사용하면 앞서 설명한 유인 공격을 피할 수 있습니다.
하지만 스택 검색 메커니즘은 너무
많은 장점을 제공하기 때문에 때로는 방해가 되기도 합니다. 그림?7은 앞에서 언급한 ErrorLogger라는 클래스로 잘 정의된 오류 기록 서비스를
제공합니다.
ErrorLogger 클래스가
Alice의 로컬 하드 드라이브에 설치되어 있고 Alice 컴퓨터의 코드 액세스 보안 정책이 파일 시스템에 대한 전체 액세스 권한을 이
어셈블리에 허용했다고 생각해 봅시다. 현재, 로컬 구성 요소에는 기본 보안 정책에 따라 파일 시스템을 액세스할 수 있는 무제한 액세스가
허용됩니다. 하지만 이 클래스가 다른 어셈블리에 서비스를 제공하도록 디자인되어 있고 해당 어셈블리 중 일부에는 로컬 파일 시스템에 대한 쓰기
권한이 허용되지 않았다면 어떨까요?
분명, 임의 구성 요소가
사용하기에는 클라이언트가 지정하는 모든 파일에 액세스를 허용하는 MyFileAccessor보다는 ErrorLogger가 훨씬 안전합니다.
ErrorLogger는 문자열을 잘 정의된 하나의 파일에 첨부하는 데에만 사용할 수 있는 단순한 클래스입니다. 하지만 스택 검색 메커니즘은 이를
모르므로 FileStream 생성자가 호출자의 사용 권한을 요청하는 경우 체인의 모든 호출자가 FileIOPermission을 요청하지 않았다면
이 요청은 실패합니다. 이것이 방해가 된다면 ErrorLogger가 이 로그 파일에 대한 쓰기 권한을 어설션하도록 하여 제거할 수 있습니다.
그림?8은 새로운 구현을 나타낸 것입니다.
로컬 구성 요소로 설치된
ErrorLogger 클래스의 새 버전에도 파일 시스템에 대한 전체 액세스가 허용됩니다. 이 경우 FileStream을 사용하여 실제로 파일을
열기 전에 이 클래스는 이 파일 IO 사용 권한을 어설션합니다. 단, 사용자는 사용자 어셈블리가 실제로 허가받은 사용 권한만 어설션할 수
있습니다.
개별 스택 프레임은 어설션되는
사용 권한 집합을 가지고 있을 수 있으며 스택 검색이 이 스택 프레임에 도달하면 어설션된 사용 권한을 만족시켰다고 간주합니다. 어설션되는 사용
권한 집합이 만족시키지 않는 다른 사용 권한이 요청되지 않는 한 스택 검색은 계속되지 않습니다. 일부러 RevertAssert를 호출할 필요가
없다는 점에 주목하십시오. 이 경우에는 RevertAssert가 필요하지 않습니다. Log 호출이 리턴되어 어설션된 사용 권한 설정을 포함한
스택 프레임이 삭제되기 전까지는 어설션이 그대로 유지될 수 있으므로 이 경우에는 RevertAssert가 필요하지 않습니다. 이는 Deny와
PermitOnly에도 마찬가지입니다.
어설션 대상
사용 권한 어설션 기능은 남용될
수 있습니다. 예를 들면, 완전한 신뢰를 받는 로컬로 설치된 구성 요소는 모든 사용 권한을 어설션할 수 있으며, 이 클라이언트에 관계 없이
원하는 모든 권한을 사용할 수 있습니다. 이는 생각만 해도 끔찍한 일입니다. 그렇다면, 컴퓨터에 설치된 구성 요소가 그러한 작업을 하지 않을
것이라는 것을 어떻게 알 수 있을까요? 어설션은 남용의 가능성이 있는 아주 강력한 기능이므로 사용 또한 SecurityPermission이라는
사용 권한 클래스에 의해 관리됩니다. 이 클래스는 실제로 보안 관련 클래스 및 정책 사용을 관리하는 몇 가지 다른 사용 권한을 나타냅니다.
이러한 사용 권한의 대부분을 메타 사용 권한으로 생각하면 됩니다.
그림?8에 제시된 ErrorLogger2 클래스를 생각해 봅시다. 한 고유 파일에 대한 자체 쓰기 권한을
어설션하면 이 시스템의 보안 정책이 손상되는 걸까요? 어떤 공격이 가능할까요? 불량 구성 요소는 가짜 오류 메시지를 삽입하여 사용자에게 혼동을
줄 수 있습니다. 또, 아주 큰 문자열을 전송하여 사용자의 하드 드라이브를 가득 채우는 방법을 시도할 수도 있습니다. 따라서 그 자체 권한을
어설션할 경우 ErrorLogger2가 MyFileAccessor와 같은 일반 클래스보다 더 안전하기는 하지만 어설션 그 자체 때문에 발생할 수
있는 공격이 여전히 있습니다.
이러한 문제 때문에 Assert
사용을 피해야 할까요? 보안에 대한 대부분의 질문과 마찬가지로 이 질문에 대해서도 단정적으로 대답할 수가 없습니다. 이러한 문제는 확실히 보안
모델을 복잡하게 만들므로 가장 좋은 방법은 동료들에게 이러한 기능 사용을 평가해 주도록 부탁하는 방법일 것입니다. 많은 관리자들이 강력하게
신뢰할 수 있는 로컬 구성 요소를 제외하고는 어설션을 허락하지 않기 때문에 보안 정책상 어설션이 거부될 수 있다는 점도 참조하십시오. 사용자가
코드 전체적으로 어설션에 의지한다면 응용 프로그램이 중단될 수 있습니다. Assert 호출로 만들어진 SecurityException만 제한하여
알린 다음 어설션이 허용되지 않을 때 전체 스택 검색이 이루어지는 상태에서 작업을 시도하는 것이 좋을 것입니다.
어설션이 꼭 필요한 경우는 관리 코드에서 비관리 코드로 이동하는 경우입니다. 시스템 정의 FileStream 클래스를 예로 들어
봅시다. 분명, 이 클래스는 실제로 파일을 열고, 닫고, 읽고, 쓰려면 기본 운영 체제에 호출을 해야 하며 이 호출은 비관리 코드에서
실행됩니다. 이러한 호출이 이루어지면 인터로프 계층은 특히 UnmanagedCode 사용 권한을 요구하는 SecurityPermission을
요청합니다. 이 요청이 스택으로 확산되면 비관리 코드에 대한 호출 권한이 허용되지 않는 경우 어떤 코드도 파일을 열 수
없습니다.
FileStream 클래스는 이
평범한 요청을 보다 세부적인 요청 즉, FileIOPermission 요청으로 변환합니다. 이때, 생성자 내에서 FileIOPermission을
요청하는 방법을 사용합니다. 이 요청이 성공하면 FileStream 개체는 실제로 운영 체제를 호출하기 전에 UnmanagedCode 사용
권한을 어설션합니다. FileStream에서 한 관리되지 않은 호출은 비관리 코드에 대한 임의 호출이 아니며, 생성자의 이전 요청이 표시한 특정
목적을 위해 특정 파일을 여는 호출입니다. FileStream 및 기타 신뢰되는 구성 요소를 호스트하는 mscorlib 어셈블리는 이러한 정책
변환 실행을 신뢰할 수 있다고 간주하므로 Assertion 권한을 허가 받습니다. Assertion 권한이 있는 다른 어셈블리를 신뢰하려면 먼저
이 어셈블리가 사용자의 보안 정책을 실행하도록 도울 만큼 높은 수준의 신뢰도를 사용자가 갖추고 있어야 합니다.
선언적 속성
구성 요소에 Deny,
PermitOnly 또는 assert를 사용하려고 한다면 이러한 실행이 모두 체계적일 뿐만 아니라 선언적으로도 이루어질 수 있다는 것을 알아
두십시오. 예를 들면, 그림?9는 FileIOPermissionAttribute라는 선언적 속성을 사용하는 오류 로거의 세 번째 구현을
보여 줍니다. C#에서는 속성을 선언할 때 간략하게 속성 접미사를 생략할 수 있습니다.
이 방법을 사용하면 몇 가지
장점이 있습니다. 먼저, 입력이 조금 더 쉽습니다. 그리고 보다 중요한 점은, 선언적 속성이 구성 요소를 위한 메타데이터의 일부가 되며 반사를
통해 쉽게 발견할 수 있다는 것입니다. 이렇게 되면 도구를 사용하여 어셈블리를 스캔하고, 어설션을 사용하는지, 그리고 다양한 사용 권한을
어설션하는 방법과 클래스를 나열할 수 있는지 등을 알아낼 수 있습니다. 이 도구는 보안 정책을 사용해 잠재적인 충돌을 발견할 수도 있습니다.
하지만, 로컬 하드 드라이브에 이 구성 요소가 설치되어 있지 않은 경우 어설션이 허가되지 않습니다.
이 방법의 커다란
단점은 어설션 요청이 거부될 경우 이 메서드가 예외를 알리지 못한다는 것입니다. 이 단점이 어설션 권한에 적용됩니다. 선언적 속성을 사용해 사용
권한을 제한하기만 하면 이러한 문제는 발생하지 않습니다.
SecurityAction
열거는 선언적 사용 권한 속성과 함께 사용되며 로드 또는 런타임 시 클라이언트의 요청 권한뿐 아니라 코드에 사용할 수 있는 사용 권한을 조정하는
데 사용할 수 있는 몇 가지 옵션이 포함되어 있습니다. 그림?10은 .NET 프레임워크 SDK 설명서에서 가져온 것으로, 이러한 옵션이 종류별로 분류되어 있습니다.
예를 들면, 다음 두 속성 선언을 비교해 봅시다.
[SecurityPermission(SecurityAction.Demand,
UnmanagedCode = true)]
[SecurityPermission(SecurityAction.LinkDemand,
UnmanagedCode = true)]
첫 번째 선언을 메서드에 적용하면
런타임 시 이 메서드에 대한 호출이 있을 때마다 일반 스택 검색이 이루어집니다. 한편, 두 번째 선언을 사용하면 보호되는 메서드 참조 시 확인이
한 번씩만 이루어집니다. 이러한 검사는 JIT(Just-in-time) 컴파일 시간에 발생합니다. 두 번째 선언은 이 선언으로 링크되는 코드의
사용 권한만을 요청하며 LinkDemand에 대한 전체 스택 검색은 실행되지 않습니다. 이 목록에 제시된 다른 속성에 대해서는 나중에 보안
정책에 대해 다루면서 설명하겠습니다.
코드 액세스 보안에 대한 공격
앞으로 코드 액세스 보안 인프라를
시험할 기회가 많이 있으므로 다른 흥미로운 공격을 발견할 수 있을 것으로 기대됩니다. 현재, 피해를 입기 쉬운 몇 가지 공격은 Assertion
및 UnmanagedCode 보안 권한의 악용입니다. 어설션의 위험에 대해서는 이미 설명했으며 비관리 코드 호출 또한 복잡한
문제입니다.
한 어셈블리가 비관리 코드 호출을
허가받으면 이 어셈블리는 실제로 모든 코드 액세스 보안을 무시할 수 있습니다. 예를 들어, 한 어셈블리가 로컬 파일 시스템에 대한 사용 권한은
허가받지 못했지만 비관리 코드로의 호출은 허가받은 경우 이 어셈블리는 Win32® 파일 시스템 API를 직접 호출하여 나쁜 작업을 실행할 수
있습니다. 앞서 언급한 것처럼 이러한 호출은 운영 체제 보안 확인이 무엇을 실행하는지에 따라 영향을 받지만 공격자의 코드가 SYSTEM 로그온
세션에서 실행되는 데몬 프로세스나 관리자 브라우저 등 권한을 가진 환경으로 로드되면 이러한 보안 검사는 거의 믿을 수
없습니다.
관리자의 로그온 세션에서
Win32 파일 API를 사용하는 공격자가 로컬 컴퓨터의 보안 정책을 다시 작성하는 것은 쉽게 상상해 볼 수 있습니다. 이 보안 정책은 관리자가
작성할 수 있는 XML 파일에 저장되어 있기 때문입니다. 아니면 공격자는 동일한 Win32 파일 API를 사용하여 CLR 실행 엔진을 교체할
수도 있습니다. 공격자가 관리 권한을 사용하여 비관리 코드를 실행하면 방법이 전혀 없습니다. 하지만, 이러한 공격은 보안 정책을 신중히 관리하면
분명히 막을 수 있으며 이에 대해서는 나중에 설명하겠습니다.
크게 두드러지는 또 다른 공격은
위치 변경을 이용한 사용 권한 에스컬레이션과 관련이 있습니다. 인터넷 URL에서 한 어셈블리를 사용하는 경우 이 어셈블리는 일반적으로 로컬로
설치될 때보다 훨씬 적은 사용 권한을 갖습니다. 공격자의 첫 번째 목표는 희생자를 설득하여 그의 로컬 하드 드라이브에 이 어셈블리 사본을 설치한
다음 즉시 사용 권한을 단계적으로 확대하는 것입니다. 너무도 많은 사용자들이 별 생각 없이 기꺼이 인터넷 사이트에서 ActiveX 컨트롤을
설치하려고 하고 있으므로 이는 심각한 문제가 될 수 있습니다. 잠재적인 공격과 예방 방법에 대한 자세한 내용은 제 웹 사이트(http://www.develop.com/kbrown/? )를 참조하십시오.
보안 정책
이 문서 처음부터 내내 코드
액세스 사용 권한 할당에 관한 보안 정책이 있다는 사실을 암시했습니다. 이 정책은 다소 복잡할 수 있지만 일단 근본 원리만 파악하면 쉽게 이해할
수 있습니다. 무엇보다도, 사용 권한이 어셈블리 단위로 할당된다는 사실을 파악하는 것이 중요합니다. 이러한 사용 권한을 파악하는 과정을 다음과
같이 기본적인 세 단계로 구분했습니다.
- 증거를 수집합니다.
- 보안 정책에 증거를 제시하고 할당된 사용 권한 집합을 찾아냅니다.
- 어셈블리 요구 사항을 기초로 사용 권한 집합을 조정합니다.
증거
처음 CLR 시험을 시작했을 때,
증거라는 단어가 좀 이상하게 여겨졌습니다. 마치, 컴퓨터 과학자가 아니라 일련의 법률가들이 이 보안 인프라를 디자인한 것처럼 들렸기
때문이었습니다. 하지만 CLR을 어느 정도 시험하고 나자 증거라는 용어가 정말 적절하다는 것을 알게 되었습니다. 법정에서 증거는 "살인 무기는
무엇이었는가?"라거나 "누가 이 계약서에 서명했는가?"처럼 배심원의 질문에 대한 답을 찾는 데 도움이 되는 정보를
제공합니다.
CLR에서 증거란 보안 정책이
하는 질문에 대한 일련의 답이라고 할 수 있습니다. 이 답을 기초로 보안 정책은 자동으로 코드에 사용 권한을 허가할 수 있습니다. 다음은 이
문서를 작성하는 현재 보안 정책이 하는 질문입니다.
- 어느 사이트에서 이 어셈블리를 얻었습니까?
- 어느 URL에서 이 어셈블리를 얻었습니까?
- 어느 구역에서 이 어셈블리를 얻었습니까?
- 이 어셈블리의 강력한 이름은 무엇입니까?
- 누가 이 어셈블리에 서명했습니까?
처음
세 질문은 어셈블리를 가져온 원래 위치를 묻는 질문이고, 나머지 두 질문은 어셈블리 작성자에 중점을 둔 질문입니다.
법정에서는 일방이 증거를 제출하지만 상대방이 그에 대해 이의를 제기할 수 있고 배심원들이 증거가 근거가 있는지를 판단합니다.
한편, CLR의 경우에는 두 개체 즉, CLR과 응용 프로그램 도메인의 호스트가 증거를 수집할 수 있습니다. CLR은 자동화된 시스템이므로
배심원은 없으며 정책 평가를 받도록 증거를 제출하는 개체는 거짓 정보를 제시하지 않는다는 신뢰를 받는 개체여야 합니다. 이것이 바로 특별 보안
권한인 ControlEvidence가 있는 이유입니다. 보안 정책을 실행하려면 이미 CLR을 신뢰해야 하므로 CLR은 당연히 증거를 제공할 수
있을 만큼의 신뢰를 받습니다. 따라서 ControlEvidence 보안 권한이 호스트에 적용됩니다. 지금 현재로서는 세 개의 호스트 즉,
Microsoft Internet Explorer, ASP.NET, 셸에서 CLR 응용 프로그램을 시작하는 셸 호스트가 기본적으로
제공됩니다.
좀더 구체적으로 들어가서
System.AppDomain 클래스에서 볼 수 있는 다음 함수를 살펴봅시다.
public int ExecuteAssembly(
string fileName,
Evidence assemblySecurity,
);
브라우저가 이미 어셈블리를 로컬
파일 시스템의 캐시로 다운로드했을 수도 있지만 두 번째 매개 변수를 통해 어셈블리의 실제 근원에 대한 증거를 제공해야 합니다.
보안 정책 평가
일단 호스트와 CLR이 모든
증거를 수집한 다음 이 증거를 Evidence 유형의 하나의 컬렉션 개체에 캡슐화하여 개체 집합으로 보안 정책에 제출합니다. 이 컬렉션의 개별
개체 유형은 개체가 나타내는 증거의 유형을 표시하며 앞서 나열한 개별 질문을 나타내는 증거 클래스가 있습니다.
Site
Url
ApplicationDirectory
Zone
StrongName
Publisher
보안 정책은 세 가지 다른
수준으로 구성되며 각 수준은 일련의 개체 모음입니다. 이 개별 개체를 코드 그룹이라고 하며, 어셈블리에 제시된 질문과 함께 증거가 질문을
만족시킬 경우 만들어지는 사용 권한 집합에 대한 참조를 나타냅니다. 이 질문은 전문적으로 그룹 등록 조건이라고 하며 사용 권한 집합은 관리자가
다시 사용할 수 있도록 명명됩니다. 그림?11은 그룹 등록 조건 및 이에 맞게 명명된 사용 권한
집합입니다.
 그림 11 그룹 등록 조건을 갖춘 코드
그룹
"코드 그룹"이라는 용어가 약간의
혼돈을 일으킬 수 있다고 항상 생각해 왔지만 아직 보다 나은 용어가 떠오르지 않은 관계로 항상 코드 그룹을 보안 정책 수준을 만드는 그래프의
노드라고만 생각합니다. 그림?12는 코드 그룹 또는 노드 집합이 어떻게 루트가 하나인 계층을
형성하는지 보여 줍니다. 이 정책 수준의 각 노드는 그룹 등록 조건 및 사용 권한 집합에 대한 참조를 나타내므로 CLR은 수집된 증거를 조사하고
증거를 계층의 노드에 일치시켜 정책 수준이 허용하는 사용 권한을 나타내는 사용 권한 집합이 됩니다. 루트 노드는 실제로 순회의 시작 지점에
불과하므로 모든 코드에 일치하며, 기본적으로는 Nothing이라는 사용 권한 집합을 참조합니다. Nothing에는 추측하는 바처럼 아무런 사용
권한이 들어 있지 않습니다.
 그림 12 보안 정책 수준 그래프
그래프의 실제 순환은 몇 가지 규칙에 의해 관리됩니다. 먼저, 부모 노드가 일치하지 않으면 자식 노드는 일치 여부를 전혀
테스트하지 않습니다. 즉, 이 그래프는 AND 및 OR 논리 연산자와 비슷한 무언가를 나타냅니다. 둘째, 각 노드에는 순환을 관리하는 속성이
있을 수 있습니다. 여기에 적용되는 속성은 Exclusive로, 이 Exclusive 속성이 있는 노드가 일치하면 특정 노드에 대한 사용 권한
집합만 사용됩니다. 물론, 한 정책 수준에서 일치하는 두 노드가 이 속성을 가지고 있는 것은 아무런 의미가 없으며 따라서 이는 오류로
간주됩니다. 이러한 일이 발생하지 않도록 하는 것은 시스템 관리자의 몫이지만 실제로 발생하게 되면 시스템이 PoicyException을 실행하며
어셈블리는 로드되지 않습니다.
 그림 13 정책 수준 교환
그림?13은 ACME Corporation이 서명한
https://q.com/downloads/foobar.dll에서 다운로드한 어셈블리의 예입니다. 그래프의 네 노드가 어떻게 일치하는지 보고
교환 중 게시자 노드 중 하나만 일치합니다. 이 그래프의 왼쪽 반은 ACME Corporation에서 나오는 코드에 대한 한 쌍의 논리적 AND
관계를 설명합니다. 여기에는 "ACME Corporation이 게시하고 AND 인터넷에서 다운로드된 코드는 사용 권한 집합 bar와 baz를
갖지만 ACME Corporation이 게시하고 AND 로컬로 설치된 코드는 foo 및 gimp라는 사용 권한 집합을 갖습니다"라고 쓰여
있습니다.
제가 왜 계속해서 정책 수준에
대해 얘기하는지 의아해 하실 것입니다. 그 이유는, 실제로 가능한 정책 수준이 세 가지 있으며 각각에는 그림?12에서 볼 수 있는 노드 그래프가 들어 있기 때문입니다. 이 세 정책 수준은 각각 컴퓨터 정책
수준, 사용자 정책 수준, 응용 프로그램 도메인 정책 수준이며 이 순서로 평가됩니다. 이 결과 만들어지는 사용 권한 집합은 이 세 가지 정책
수준에서 그래프 교환 시 발견되는 사용 권한 집합이 교차하는 지점입니다.
 그림 14 세 가지 정책 수준
이
응용 프로그램 도메인 정책 수준은 원칙적으로 옵션이며 호스트가 동적으로 제공합니다. 이 기능의 가장 두드러진 예는 웹 브라우저로, 응용 프로그램
도메인에 대해 보다 제한적인 정책의 옵션을 원할 것입니다. 그림?14는 제가 정책 수준에 대해
어떻게 생각하는지를 보여 줍니다. 노드의 다른 속성(LevelFinal)을 사용하여 정책 수준의 교환을 중지할 수 있습니다. 일치하는 노드에서
이 속성이 발견되면 더 이상의 정책 수준에 대한 교환은 이루어지지 않습니다. 예를 들면, 이는 도메인 관리자가 사용자 수준 정책을 편집하여 개별
사용자가 변경할 수 없는 컴퓨터 정책 수준에서 명령을 내리도록 만듭니다.
사용 권한 설정 조정
CLR이 세 정책 수준에서 사용
권한 집합을 모으면 최종 단계는 어셈블리가 스스로 입장을 취하도록 합니다. 코드를 회수하면 사용 권한을 거부하거나 어설션하여 런타임 시 이용
가능한 사용 권한 집합을 체계적으로 또는 선언적으로 조정할 수 있습니다. SecurityAction 열거의 세 요소를 신중히 사용하면 어셈블리는
정책상 허용된 사용 권한을 조정할 수 있습니다(그림?10에도 제시됨).
SecurityAction.RequestMinimum
SecurityAction.RequestOptional
SecurityAction.RequestRefuse
이 요소의 이름은 그 기능을 잘
보여 줍니다. 어셈블리가 요청하는 최소의 사용 권한 집합이 정책상 허가되지 않으면 이 어셈블리는 실행되지 않습니다. 이 특정 기능을 조금만
사용하면 사용자는 환경에 대해 가정을 하고 프로그래밍을 조금 더 쉽게 만들 수 있습니다. 하지만 너무 많이 사용하면 이 기능은 사용자에게 나쁜
경험을 남길 수 있습니다. 예를 들어, 어셈블리가 필요로 할 수도 있는 모든 사용 권한을 요청하기 위해 RequestMinimum을 사용하면
필요한 것 이상의 상황에서는 로드 오류가 발생하게 됩니다. 이렇게 되면 관리자가 자신의 보안 정책을 필요한 것 보다 조금 더 완화하여 구성
요소가 실행되도록 할 수도 있습니다.
RequestRefuse는 최소한
초기 단계에서는 자유롭게 사용하기에 유용한 도구처럼 보입니다. 이 도구를 사용해 사용자는 정책상 허가 받은 사용 권한을 거부할 수도 있습니다.
사용자 어셈블리가 필요로 하지 않는 사용 권한 집합은 늘 거부하십시오. 안전을 기해서 손해볼 것은 없습니다.
마지막으로, RequestOptional을 사용하면 없어도 관계 없지만, 가능할 경우 이용할 수도 있는 옵션 사용 권한을 지정할
수 있습니다. 사용자 어셈블리가 몇몇 추가 사용 권한을 필요로 하는 옵션 기능을 표시할 경우 유용합니다.
다음은 정책에서
파생된 사용 권한 집합과 어셈블리의 최소, 옵션 및 거부된 사용 권한 집합을 가정할 때 한 어셈블리에 대해 허가되는 사용 권한을 결정하는 CLR
문서에 제시된 공식입니다.
G = M + (O ?P) - R
여기에서 G = 허가된 사용
권한, M = 최소 요청, O = 옵션 요청, P = 정책 파생 사용 권한, R = 거부된 사용 권한입니다.
보안 정책 보기 및 편집
보안 정책을 찾아다니고 싶다면
코드 액세스 보안 정책 도구인 CASPOL.EXE를 확인하십시오. 다음은 시작을 지정할 때 제가 즐겨 사용하는 몇 가지
명령줄입니다.
caspol -a -listgroups
caspol -a -resolvegroup c:\inetpub\wwwroot\bar.dll
caspol -a -resolveperm c:\inetpub\wwwroot\bar.dll
첫 번째 예는 컴퓨터와 사용자
정책 수준 모드에 해당되는 코드 그룹을 나열합니다. 자세히 들여다보면 노드의 계층을 볼 수 있으며 개별 노드마다 그룹 구성 조건이 있고 그 뒤에
사용 권한 집합 이름이 옵니다. 두 번째 예는 특정 어셈블리에 대해 일치하는 코드 그룹 목록을 요청하는 것이고, 세 번째 예는 이 어셈블리에
대한 사용 권한을 실제로 확인하는 것입니다.
HTTP를 통해 동일한 어셈블리를
참조할 때 어떻게 변하는지 다음 예에서 살펴보십시오.
caspol -a -resolvegroup http://localhost/foo.dll
CASPOL.EXE를 보안 정책 편집에 사용할 수 있지만 아주 단순한 작업을 하는 것이 아니라면
EMACS를 가져와서 직접 정책 파일을 편집하는 쪽을 선호합니다. XML 문서이기 때문입니다. 이 방법을 사용하려고 할 경우에는 원본 파일을
백업해 두십시오. 현재 이 문서를 작성할 경우에는 %SYSTEMROOT%\ComPlus\v2000.14.1812\security.cfg에 컴퓨터
정책 파일이 있습니다. 사용자 버전 번호는 제 번호와 다르겠지만 이해는 되실 겁니다. 사용자 보안 정책은 동일한 경로의 사용자 프로필 디렉터리에
저장되어 있습니다. 현재, 기본 사용자 정책은 모든 코드에 FullTrust를 허용하며 이는 컴퓨터 정책이 보안 정책을 완벽하게 조정한다는 것을
뜻합니다.
결론
코드 액세스 보안은 CLR의 코드
확인과 결합하면서 이전 플랫폼 생성 시 취한 무간섭주의 방법에서는 크게 벋어나는 단계를 제공합니다. 이전에는 통합된 커다란 응용 프로그램 구축과
비교해 DLL이 아주 현대적이라고 여겨졌습니다. 물론, 통합된 방법이 훨씬 더 안전하기는 했을 테지만 말입니다.
코드
액세스 보안은 최신 응용 프로그램이 구성 요소로 이루어 졌다는 사실을 알고 있습니다. 또한, 이러한 구성 요소의 원본은 처벌이 아니라 예방적인
보안 정책을 결정하며 일반적으로 새로이 생겨나는 많은 모바일 코드 응용 프로그램 클래스의 안전을 강화합니다.
코드
액세스 보안은 분명 완벽한 처방은 아닙니다. 호스트 전체를 복잡하게 만들기는 하지만 관리상의 문제는 전혀 없습니다. 지식이 풍부하며 시간을 들여
이 기능을 충분히 파악하고자 하는 의지가 있는 관리자가 없다면 그 뒤에 많은 새로운 공격들이 도사리고 있을 것입니다. 수 년간 모바일 코드를
처리해 왔으며 현재 커다란 성공을 거두고 있는 Java 보안의 다양한 기록을 살펴보고 이 새 아키텍처를 평가해 보는 것이 좋을 것입니다. 제 웹
사이트를 방문하시면 제가 모아 둔 .NET 보안 뉴스 및 샘플 코드와 모바일 코드 보안을 기초로 한 기존 작업 참조를 보실 수 있습니다.
마지막으로, CLR 보안 아키텍처에 대한 많은 의견을 보내 주시기 바랍니다.
Keith Brown은 보안에 대한 프로그래머들의 인식을 조사, 저술,
교육, 촉진하는 개발 조언자입니다. 저서로는 Programming Windows Security(Addison-Wesley,
2000)가 있으며, Essential COM을 공동 집필했고 현재 .NET 보안에 대한 자료를 만드는
중입니다.
? 최종수정일: 2001년 3월 12일
|