연습: 부분 신뢰 시나리오에서 코드 내보내기
업데이트: 2007년 11월
리플렉션 내보내기는 완전 또는 부분 신뢰에서 동일한 API 집합을 사용하지만 부분 신뢰 코드에서는 특별한 권한이 있어야 일부 기능을 사용할 수 있습니다. 또한 리플렉션 내보내기는 보안 투명 어셈블리에서 부분 신뢰 수준으로 사용되도록 설계된 익명으로 호스팅된 동적 메서드라는 기능이 있습니다.
참고: |
---|
.NET Framework 버전 3.5 이전에는 코드를 내보내려면 ReflectionPermissionFlag.ReflectionEmit 플래그가 설정된 ReflectionPermission이 필요했습니다. 이 권한은 FullTrust 및 Intranet 명명된 권한 집합에는 기본적으로 포함되어 있지만 Internet 권한 집합에는 포함되어 있지 않습니다. 따라서 SecurityCriticalAttribute 특성이 있고 ReflectionEmit에 대해 Assert 메서드를 실행한 경우에만 부분 신뢰 영역에서 라이브러리를 사용할 수 있었습니다. 코드 오류가 있으면 보안에 문제가 생길 수 있으므로 이러한 라이브러리를 보안 측면에서 자세히 검토해야 합니다. .NET Framework 3.5의 경우에는 기본적으로 코드를 생성하는 데 권한이 필요하지 않으므로 부분 신뢰 시나리오에서 보안 요구 사항 없이 코드를 내보낼 수 있습니다. 즉, 생성된 코드에는 이를 내보내는 어셈블리보다 많은 권한이 부여되지 않습니다. 따라서 코드를 내보내는 라이브러리에 대한 보안이 문제가 되지 않고 ReflectionEmit을 어설션할 필요가 없으므로 보안에 크게 신경쓰지 않고도 안전한 라이브러리를 작성할 수 있습니다. |
이 연습에서는 다음 작업을 수행합니다.
코드를 테스트하기 위한 부분 신뢰 환경 설정.
부분 신뢰 응용 프로그램 도메인에서 코드 실행.
부분 신뢰에서 익명으로 호스팅된 동적 메서드를 사용하여 코드 내보내기 및 실행
부분 신뢰 시나리오에서 코드를 내보내는 방법에 대한 자세한 내용은 리플렉션 내보내기의 보안 문제점을 참조하십시오.
이러한 절차에 나오는 코드의 전체 목록을 보려면 이 연습의 끝 부분에 있는 예제 단원을 참조하십시오.
부분 신뢰 위치 설정
다음 절차에서는 부분 신뢰 수준으로 코드가 실행될 수 있는 위치를 설정하는 방법을 보여 줍니다.
첫 번째 절차에서는 인터넷 신뢰 수준에서 코드가 실행되는 샌드박싱된 응용 프로그램 도메인을 만드는 방법을 보여 주고 일반적인 문제에 대해서도 설명합니다.
두 번째 절차에서는 부분 신뢰 응용 프로그램 도메인에 ReflectionPermissionFlag.RestrictedMemberAccess 플래그가 설정된 ReflectionPermission을 추가하여 같거나 낮은 신뢰 수준의 어셈블리에서 전용 데이터에 액세스할 수 있도록 하는 방법을 보여 줍니다.
세 번째 절차에서는 신뢰 수준을 폴더에 연결하는 코드 그룹을 만들어 해당 폴더에 있는 모든 어셈블리가 부분 신뢰 수준으로 실행되도록 하는 방법을 보여 줍니다.
이러한 절차 외에도 간단한 시나리오에서는 다음과 같은 어셈블리 특성을 사용하여 SkipVerification 권한 없이도 어셈블리를 실행할 수 있습니다. 비슷한 방법으로 특성을 사용하여 MemberAccess 권한을 거부할 수도 있습니다.
<Assembly:SecurityPermissionAttribute(SecurityAction.RequestRefuse, Flags:=SecurityPermissionFlag.SkipVerification)>
[assembly:SecurityPermissionAttribute(SecurityAction.RequestRefuse, Flags=SecurityPermissionFlag.SkipVerification)]
샌드박싱된 응용 프로그램 도메인 만들기
어셈블리가 부분 신뢰 수준으로 실행되는 응용 프로그램 도메인을 만들려면 AppDomain.CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, array<StrongName[]) 메서드 오버로드를 사용하여 어셈블리에 부여할 권한 집합을 지정해야 합니다. 권한 부여 설정을 지정하는 가장 쉬운 방법은 보안 정책에서 명명된 권한 집합을 검색하는 것입니다.
주의: |
---|
증명 정보를 지정하는 것만으로는 샌드박싱된 응용 프로그램 도메인을 만들 수 없습니다. 권한 부여 설정이나 응용 프로그램 도메인 정책 수준을 지정해야 합니다. 이 항목에서는 응용 프로그램 도메인 정책 수준 설정에 대해 설명하지 않습니다. 예를 들어, 인터넷 증명 정보와 함께 CreateDomain(String, Evidence) 메서드 오버로드를 사용하는 경우 응용 프로그램 도메인 경계에만 권한이 적용됩니다. 응용 프로그램 도메인 내의 어셈블리에는 표준 보안 정책에 따라 권한이 부여됩니다. 사용자 컴퓨터에 있는 콘솔 응용 프로그램의 경우 완전히 신뢰될 수 있습니다. |
다음 절차에서는 부분 신뢰 수준으로 코드를 실행하는 샌드박싱된 응용 프로그램 도메인을 만들어 내보낸 코드가 공용 형식의 공용 멤버에만 액세스할 수 있는 시나리오를 테스트합니다. 이후 절차에서는 내보낸 코드가 같거나 낮은 권한이 부여된 어셈블리에 있는 비공용 형식과 멤버에 액세스할 수 있는 시나리오를 테스트하기 위해 RestrictedMemberAccess를 추가하는 방법을 보여 줍니다.
부분 신뢰가 부여된 응용 프로그램 도메인을 만들려면
다음 도우미 함수를 사용하여 보안 정책에서 명명된 권한을 가져옵니다.
Private Shared Function GetNamedPermissionSet(ByVal name As String) As PermissionSet If (String.IsNullOrEmpty(name)) Then Throw New ArgumentException("name", "Cannot search for a permission set without a name.") End If Dim foundName As Boolean = False Dim setIntersection As New PermissionSet(PermissionState.Unrestricted) ' Search all policy levels. Dim levelEnumerator As IEnumerator = SecurityManager.PolicyHierarchy() While (levelEnumerator.MoveNext()) Dim level As PolicyLevel = levelEnumerator.Current Debug.Assert(level IsNot Nothing) ' If this policy level has a named permission set with the ' specified name, intersect it with previous levels. Dim levelSet As PermissionSet = level.GetNamedPermissionSet(name) If (levelSet IsNot Nothing) Then foundName = True setIntersection = setIntersection.Intersect(levelSet) ' Intersect() returns null for an empty set. If this occurs ' at any point, the resulting permission set is empty. If (setIntersection Is Nothing) Then Return New PermissionSet(PermissionState.None) End If End If End While If Not foundName Then setIntersection = New PermissionSet(PermissionState.None) End If Return setIntersection End Function
private static PermissionSet GetNamedPermissionSet(string name) { if (String.IsNullOrEmpty(name)) throw new ArgumentException("name", "Cannot search for a permission set without a name."); bool foundName = false; PermissionSet setIntersection = new PermissionSet(PermissionState.Unrestricted); // Search all policy levels. IEnumerator levelEnumerator = SecurityManager.PolicyHierarchy(); while (levelEnumerator.MoveNext()) { PolicyLevel level = levelEnumerator.Current as PolicyLevel; Debug.Assert(level != null); // If this policy level has a named permission set with the // specified name, intersect it with previous levels. PermissionSet levelSet = level.GetNamedPermissionSet(name); if (levelSet != null) { foundName = true; setIntersection = setIntersection.Intersect(levelSet); // Intersect() returns null for an empty set. If this occurs // at any point, the resulting permission set is empty. if (setIntersection == null) return new PermissionSet(PermissionState.None); } } if (!foundName) setIntersection = new PermissionSet(PermissionState.None); return setIntersection; }
권한 부여 설정은 모든 정책 수준에 부여된 권한 집합의 교집합입니다. 즉, 권한 부여 설정이 모든 정책 수준에 부여되지 않으면 특정 권한도 부여되지 않습니다. 따라서 도우미 함수는 완전 신뢰에 대한 권한 부여 설정을 사용하여 시작되고 정책 계층 구조의 수준을 열거하며, 이때 각 수준에 대해 정의된 권한 집합과 해당 권한 부여 설정의 교집합을 사용합니다.
참고: PermissionSet 클래스를 사용하면 원하는 권한 조합이 포함된 권한 부여 설정을 만들 수 있습니다.
도우미 함수를 사용하는 방법은 이 절차의 뒷부분에서 보여 줍니다.
보안 영역을 사용하여 부분 신뢰 위치에 대한 증명 정보를 만듭니다. 이 경우 인터넷 영역이 사용됩니다.
Dim zoneEvidence() As Object = { New Zone(SecurityZone.Internet) } Dim internetZone As New Evidence(zoneEvidence, zoneEvidence)
Object[] zoneEvidence = { new Zone(SecurityZone.Internet) }; Evidence internetZone = new Evidence(zoneEvidence, zoneEvidence);
AppDomainSetup 개체를 만들어 응용 프로그램 경로로 응용 프로그램 도메인을 초기화합니다. 이 코드 예제에서는 현재 폴더를 사용합니다.
Dim adSetup As New AppDomainSetup() adSetup.ApplicationBase = "."
AppDomainSetup adSetup = new AppDomainSetup(); adSetup.ApplicationBase = ".";
도우미 함수를 사용하여 시스템 정책에서 명명된 집합을 검색합니다.
Dim internetSet As PermissionSet = GetNamedPermissionSet("Internet")
PermissionSet internetSet = GetNamedPermissionSet("Internet");
증명 정보, 응용 프로그램 도메인 설치 정보 및 권한 부여 설정을 지정하여 응용 프로그램 도메인을 만듭니다.
Dim ad As AppDomain = AppDomain.CreateDomain("ChildDomain1", _ internetZone, _ adSetup, _ internetSet, _ Nothing)
AppDomain ad = AppDomain.CreateDomain("ChildDomain1", internetZone, adSetup, internetSet, null);
AppDomain.CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, array<StrongName[]) 메서드 오버로드의 마지막 매개 변수를 사용하면 응용 프로그램 도메인의 권한 부여 설정 대신 완전 신뢰를 부여해야 하는 어셈블리 집합을 지정할 수 있습니다. 응용 프로그램이 사용하는 .NET Framework 어셈블리는 전역 어셈블리 캐시에 있으므로 지정하지 않아도 됩니다. 전역 어셈블리 캐시에 있는 어셈블리는 항상 완전히 신뢰됩니다. 이 매개 변수를 사용하여 전역 어셈블리 캐시에 없는 강력한 이름의 어셈블리를 지정할 수 있습니다.
샌드박싱된 도메인에 RestrictedMemberAccess 추가
호스트 응용 프로그램에서는 익명으로 호스팅된 동적 메서드를 사용하여 코드를 내보내는 어셈블리의 신뢰 수준보다 낮거나 같은 어셈블리에 있는 전용 데이터에 액세스할 수 있습니다. 이 제한된 기능을 통해 JIT(Just-In-Time) 가시성 검사를 생략할 수 있도록 호스트 응용 프로그램은 ReflectionPermissionFlag.RestrictedMemberAccess(RMA) 플래그가 설정된 ReflectionPermission 개체를 권한 부여 설정에 추가합니다.
예를 들어, 호스트가 인터넷 응용 프로그램에 인터넷 권한과 RMA를 부여하여 인터넷 응용 프로그램이 해당 어셈블리에 있는 전용 데이터에 액세스하는 코드를 내보낼 수 있도록 할 수 있습니다. 신뢰 수준이 같거나 낮은 어셈블리에만 액세스할 수 있기 때문에 인터넷 응용 프로그램은 .NET Framework 어셈블리 같은 완전 신뢰 어셈블리의 멤버에 액세스할 수 없습니다.
참고: |
---|
권한 상승을 방지하기 위해 익명으로 호스팅된 동적 메서드를 생성하면 내보내는 어셈블리의 스택 정보가 포함됩니다. 이 메서드를 호출하면 스택 정보가 검사됩니다. 따라서 완전 신뢰 코드에서 호출되는 익명으로 호출된 동적 메서드는 여전히 내보내는 어셈블리의 신뢰 수준으로 제한됩니다. |
부분 신뢰와 RMA가 부여된 응용 프로그램 도메인을 만들려면
도우미 함수를 사용하여 보안 정책에서 Internet 명명된 집합을 검색합니다.
internetSet = GetNamedPermissionSet("Internet")
internetSet = GetNamedPermissionSet("Internet");
RestrictedMemberAccess 플래그가 설정된 새 ReflectionPermission 개체를 만들고 PermissionSet.SetPermission 메서드를 사용하여 권한 부여 설정에 권한을 추가합니다.
internetSet.SetPermission( _ New ReflectionPermission( _ ReflectionPermissionFlag.RestrictedMemberAccess))
internetSet.SetPermission( new ReflectionPermission( ReflectionPermissionFlag.RestrictedMemberAccess));
권한 부여 설정에 권한이 아직 포함되어 있지 않은 경우 AddPermission 메서드가 권한 부여 설정에 권한을 추가합니다. 권한 부여 설정에 권한이 이미 포함되어 있는 경우 지정된 플래그가 기존 권한에 추가됩니다.
참고: RMA는 익명으로 호스팅된 동적 메서드의 기능입니다. 일반 동적 메서드가 JIT 가시성 검사를 생략하면 RestrictedMemberAccess 플래그와 함께 ReflectionPermissionFlag.MemberAccess 플래그가 설정된 ReflectionPermission이 내보낸 코드에 부여되어야 합니다.
증명 정보, 응용 프로그램 도메인 설치 정보 및 권한 부여 설정을 지정하여 응용 프로그램 도메인을 만듭니다.
ad = AppDomain.CreateDomain("ChildDomain2", _ internetZone, _ adSetup, _ internetSet, _ Nothing)
ad = AppDomain.CreateDomain("ChildDomain2", internetZone, adSetup, internetSet, null);
제한된 권한이 부여된 폴더 만들기
다음 절차에서는 인터넷 권한을 폴더에 연결하는 코드 그룹을 만들고 이 폴더에서 실행되는 코드에 대한 권한 부여 설정에 RestrictedMemberAccess 플래그를 추가하는 방법을 보여 줍니다.
인터넷 권한이 부여된 폴더를 만들려면
시작을 클릭하고 제어판, 관리 도구를 차례로 가리킨 다음 Microsoft .NET Framework 3.5 구성을 클릭합니다. 구성 도구를 사용하려면 시스템 관리자 권한이 있어야 합니다.
왼쪽 창의 .NET Framework 2.0 구성에서 내 컴퓨터, 런타임 보안 정책</, 시스템, 코드 그룹, All_Code를 차례로 확장합니다.
오른쪽 창에서 자식 코드 그룹 추가를 클릭하여 코드 그룹 만들기 마법사를 실행합니다.
"Internet Sandbox"와 같은 코드 그룹 이름을 지정하고 필요한 경우 설명을 입력합니다. 다음을 클릭합니다.
이 코드 그룹에 대한 조건 형식 선택 목록에서 URL을 선택합니다. URL 상자에 사용할 폴더의 전체 경로를 입력하고 다음을 클릭합니다. 예를 들어, 다음을 입력할 수 있습니다.
file://c:/InternetSandbox/*
기존 권한 집합 사용 목록에서 인터넷을 선택하고 다음을 클릭합니다.
참고: 명명된 권한 집합 및 RestrictedMemberAccess 플래그가 설정된 ReflectionPermission 개체를 지정하려면 새 권한 집합 만들기를 클릭한 다음 XML 파일을 사용하여 사용자 지정 권한 집합을 지정합니다.
마침을 클릭하여 코드 그룹을 만듭니다.
5단계에서 지정한 폴더에 제한된 신뢰 수준으로 실행할 어셈블리를 저장합니다.
샌드박싱된 응용 프로그램 도메인에서 코드 실행
다음 절차에서는 응용 프로그램 도메인에서 실행할 수 있는 메서드를 사용하여 클래스를 정의하는 방법, 도메인에서 클래스의 인스턴스를 만드는 방법 및 해당 메서드를 실행하는 방법에 대해 설명합니다.
응용 프로그램 도메인에서 메서드를 정의하고 실행하려면
MarshalByRefObject에서 파생되는 클래스를 정의합니다. 이렇게 하면 다른 응용 프로그램 도메인에서 클래스의 인스턴스를 만들고 응용 프로그램 도메인 경계를 넘어 메서드를 호출할 수 있습니다. 이 예제에서 클래스 이름은 Worker로 지정됩니다.
Public Class Worker Inherits MarshalByRefObject
public class Worker : MarshalByRefObject {
실행할 코드가 포함된 공용 메서드를 정의합니다. 이 예제에서 코드는 간단한 동적 메서드를 내보내고 메서드를 실행할 대리자를 만든 다음 대리자를 호출합니다.
Public Sub SimpleEmitDemo() Dim meth As DynamicMethod = new DynamicMethod("", Nothing, Nothing) Dim il As ILGenerator = meth.GetILGenerator() il.EmitWriteLine("Hello, World!") il.Emit(OpCodes.Ret) Dim t1 As Test1 = CType(meth.CreateDelegate(GetType(Test1)), Test1) t1() End Sub
public void SimpleEmitDemo() { DynamicMethod meth = new DynamicMethod("", null, null); ILGenerator il = meth.GetILGenerator(); il.EmitWriteLine("Hello, World!"); il.Emit(OpCodes.Ret); Test1 t1 = (Test1) meth.CreateDelegate(typeof(Test1)); t1(); }
기본 프로그램에서 어셈블리의 표시 이름을 가져옵니다. 이 이름은 샌드박싱된 응용 프로그램 도메인에서 Worker 클래스의 인스턴스를 만들 때 사용됩니다.
Dim asmName As String = [Assembly].GetExecutingAssembly().FullName
String asmName = Assembly.GetExecutingAssembly().FullName;
기본 프로그램에서 이 연습의 첫 번째 절차에 설명된 대로 샌드박싱된 응용 프로그램 도메인을 만듭니다. SimpleEmitDemo 메서드는 공용 메서드만 사용하므로 Internet 권한 집합에 권한을 추가할 필요는 없습니다.
기본 프로그램을 사용하여 샌드박싱된 응용 프로그램 도메인에서 Worker 클래스의 인스턴스를 만듭니다.
Dim w As Worker = _ CType(ad.CreateInstanceAndUnwrap(asmName, "Worker"), Worker)
Worker w = (Worker) ad.CreateInstanceAndUnwrap(asmName, "Worker");
CreateInstanceAndUnwrap 메서드는 대상 응용 프로그램 도메인에 개체를 만들고 이 개체의 속성과 메서드를 호출하는 데 사용할 수 있는 프록시를 반환합니다.
참고: Visual Studio에서 이 코드를 사용하는 경우에는 네임스페이스를 포함하도록 클래스의 이름을 변경해야 합니다. 기본적으로 네임스페이스는 프로젝트의 이름입니다. 예를 들어, 프로젝트가 "PartialTrust"이면 클래스 이름은 "PartialTrust.Worker"여야 합니다.
코드를 추가하여 SimpleEmitDemo 메서드를 호출합니다. 그러면 이 호출이 응용 프로그램 도메인 경계를 넘어 마샬링되고 코드가 샌드박싱된 응용 프로그램 도메인에서 실행됩니다.
w.SimpleEmitDemo()
w.SimpleEmitDemo();
익명으로 호스팅된 동적 메서드 사용
익명으로 호스팅된 동적 메서드는 시스템에서 제공하는 어셈블리에 연결됩니다. 따라서 다른 코드와 격리됩니다. 반면에 일반 동적 메서드는 기존 모듈이나 형식에 연결되어야 합니다.
참고: |
---|
익명 호스팅을 제공하는 어셈블리에 동적 메서드를 연결하는 유일한 방법은 다음 절차에서 설명하는 생성자를 사용하는 것입니다. 익명 호스팅 어셈블리에서는 모듈을 명시적으로 지정할 수 있습니다. |
일반 동적 메서드는 연결된 모듈의 내부 멤버나 연결된 형식의 전용 멤버에 액세스할 수 있습니다. 익명으로 호스팅된 동적 메서드는 다른 코드와 격리되므로 전용 데이터에 액세스할 수 없지만 JIT 가시성 검사를 생략하는 제한된 기능이 있어 전용 데이터에 액세스할 수 있습니다. 이 기능은 코드를 내보내는 어셈블리의 신뢰 수준보다 낮거나 같은 어셈블리에서만 사용할 수 있습니다.
권한 상승을 방지하기 위해 익명으로 호스팅된 동적 메서드를 생성하면 내보내는 어셈블리의 스택 정보가 포함됩니다. 이 메서드를 호출하면 스택 정보가 검사됩니다. 따라서 완전 신뢰 코드에서 호출되는 익명으로 호출된 동적 메서드는 여전히 내보내는 어셈블리의 신뢰 수준으로 제한됩니다.
익명으로 호스팅된 동적 메서드를 사용하려면
연결된 모듈이나 형식을 지정하지 않는 생성자를 사용하여 익명으로 호스팅된 동적 메서드를 만듭니다.
Dim meth As DynamicMethod = new DynamicMethod("", Nothing, Nothing) Dim il As ILGenerator = meth.GetILGenerator() il.EmitWriteLine("Hello, World!") il.Emit(OpCodes.Ret)
DynamicMethod meth = new DynamicMethod("", null, null); ILGenerator il = meth.GetILGenerator(); il.EmitWriteLine("Hello, World!"); il.Emit(OpCodes.Ret);
익명으로 호스팅된 동적 메서드가 공개 형식 및 메서드만 사용하는 경우 제한된 멤버 액세스가 필요하지 않고 JIT 가시성 검사를 생략하지 않아도 됩니다.
특별한 권한이 없어도 동적 메서드를 내보낼 수 있지만 내보낸 코드에는 사용되는 형식과 메서드에서 요구하는 권한이 필요합니다. 예를 들어, 내보낸 코드가 파일에 액세스하는 메서드를 호출하면 FileIOPermission이 필요합니다. 신뢰 수준에 해당 권한이 포함되어 있지 않을 경우 내보낸 코드가 실행될 때 보안 예외가 throw됩니다. 여기에 표시된 코드는 Console.WriteLine 메서드만 사용하는 동적 메서드를 내보내기 때문에 부분 신뢰 위치에서 실행할 수 있습니다.
또는 DynamicMethod(String, Type, array<Type[], Boolean) 생성자를 사용하고 restrictedSkipVisibility 매개 변수에 대해 true를 지정하여 JIT 가시성 검사를 생략하는 제한된 기능이 있는 익명으로 호스팅된 동적 메서드를 만듭니다.
Dim meth As New DynamicMethod("", _ GetType(Char), _ New Type() {GetType(String)}, _ True)
DynamicMethod meth = new DynamicMethod("", typeof(char), new Type[] { typeof(String) }, true);
제한 사항은 익명으로 호스팅된 동적 메서드가 내보내는 어셈블리의 신뢰 수준보다 낮거나 같은 어셈블리에서만 전용 데이터에 액세스할 수 있다는 점입니다. 예를 들어, 동적 메서드를 인터넷 신뢰 수준으로 실행하는 경우 마찬가지로 인터넷 신뢰 수준으로 실행되는 어셈블리의 전용 데이터에는 액세스할 수 있지만 .NET Framework 어셈블리의 전용 데이터에는 액세스할 수 없습니다. .NET Framework 어셈블리는 전역 어셈블리 캐시에 설치되고 항상 완전히 신뢰됩니다.
호스트 응용 프로그램이 ReflectionPermissionFlag.RestrictedMemberAccess 플래그가 설정된 ReflectionPermission을 부여하는 경우에만 익명으로 호스팅된 동적 메서드는 이 제한된 기능을 사용하여 JIT 가시성 검사를 생략할 수 있습니다. 이 권한은 메서드를 호출할 때 요청됩니다.
참고: 동적 메서드를 생성하면 내보내는 어셈블리에 대한 호출 스택 정보가 포함됩니다. 따라서 메서드를 호출하는 어셈블리가 아니라 내보내는 어셈블리의 권한이 요청됩니다. 이것은 내보낸 코드가 더 높은 권한으로 실행되는 것을 방지합니다.
이 연습의 끝 부분에 있는 전체 코드 예제에서는 제한된 멤버 액세스의 사용과 제한 사항을 보여 줍니다. 해당 Worker 클래스에는 가시성 검사를 생략하는 제한된 기능이 포함 또는 포함되지 않는 익명으로 호스팅된 동적 메서드를 만들 수 있는 메서드가 포함되어 있고, 예제에서는 신뢰 수준이 다른 응용 프로그램 도메인에서 이 메서드를 실행할 경우의 결과를 보여 줍니다.
참고: 가시성 검사를 생략하는 제한된 기능은 익명으로 호스팅된 동적 메서드의 기능입니다. 일반 동적 메서드가 JIT 가시성 검사를 생략할 경우 해당 메서드에 ReflectionPermissionFlag.MemberAccess 플래그가 설정된 ReflectionPermission이 부여되어야 합니다. 또한 내보내는 어셈블리가 아닌 다른 어셈블리에 있는 전용 데이터에 액세스하는 일반 동적 메서드에는 RestrictedMemberAccess 플래그가 설정된 ReflectionPermission 또는 SecurityPermissionFlag.ControlEvidence 플래그가 설정된 SecurityPermission이 부여되어야 합니다.
예제
설명
다음 코드 예제에서는 RestrictedMemberAccess 플래그를 사용하여 익명으로 호스팅된 동적 메서드의 JIT 가시성 검사를 생략하는 방법을 보여 줍니다. 이렇게 하려면 대상 멤버의 신뢰 수준이 코드를 내보내는 어셈블리의 신뢰 수준보다 낮거나 같아야 합니다.
이 예제에서는 응용 프로그램 도메인 경계를 넘어 마샬링할 수 있는 Worker 클래스를 정의합니다. 이 클래스에는 동적 메서드를 내보내고 실행하는 두 개의 AccessPrivateMethod 메서드 오버로드가 있습니다. 첫 번째 오버로드는 Worker 클래스의 PrivateMethod 메서드를 호출하는 동적 메서드를 내보냅니다. 이 오버로드에서 동적 메서드를 내보낼 때 JIT 가시성 검사를 수행하거나 생략할 수 있습니다. 두 번째 오버로드는 String 클래스의 internal 속성(Visual Basic에서는 Friend 속성)에 액세스하는 동적 메서드를 내보냅니다.
이 예제에서는 도우미 메서드를 사용하여 보안 정책에서 Internet 권한을 가져온 다음 도메인에서 실행되는 모든 코드에 이 권한 부여 설정이 사용되도록 지정하는 AppDomain.CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, array<StrongName[]) 메서드 오버로드를 사용하여 응용 프로그램 도메인을 만듭니다. 이 예제에서는 응용 프로그램 도메인에 Worker 클래스의 인스턴스를 만들고 AccessPrivateMethod 메서드를 두 번 실행합니다.
AccessPrivateMethod 메서드를 처음 실행하면 JIT 가시성 검사가 수행됩니다. JIT 가시성 검사에서는 동적 메서드가 전용 메서드에 액세스하지 못하도록 차단하므로 동적 메서드를 호출할 때 작업이 실패합니다.
AccessPrivateMethod 메서드를 두 번째로 실행할 때는 JIT 가시성 검사가 생략됩니다. Internet 권한 부여 설정에서는 가시성 검사를 생략하기에 충분한 권한을 부여하지 않으므로 동적 메서드를 컴파일할 때 작업이 실패합니다.
이 예제에서는 도우미 메서드를 사용하여 Internet 권한 부여 설정을 가져오고 ReflectionPermissionFlag.RestrictedMemberAccess가 설정된 ReflectionPermission을 권한 부여 설정에 추가합니다. 그런 다음 도메인에서 실행되는 모든 코드에 새 부여 집합의 권한을 부여하도록 지정하여 두 번째 도메인을 만듭니다. 이 예제에서는 새 응용 프로그램 도메인에 Worker 클래스의 인스턴스를 만들고 AccessPrivateMethod 메서드의 두 오버로드를 모두 실행합니다.
AccessPrivateMethod 메서드의 첫 번째 오버로드를 실행할 때는 JIT 가시성 검사가 생략됩니다. 코드를 내보내는 어셈블리가 전용 메서드를 포함하는 어셈블리와 같으므로 동적 메서드가 성공적으로 컴파일되고 실행됩니다. 따라서 신뢰 수준이 동일합니다. Worker 클래스를 포함하는 응용 프로그램에 여러 어셈블리가 있으면 각 어셈블리의 신뢰 수준이 모두 같으므로 동일한 프로세스가 해당 어셈블리 각각에 대해 성공합니다.
AccessPrivateMethod 메서드의 두 번째 오버로드를 실행할 때에도 JIT 가시성 검사가 생략됩니다. 그러나 이번에는 해당 메서드에서 String 클래스의 internalFirstChar 속성에 액세스하므로 동적 메서드를 컴파일할 때 작업이 실패합니다. String 클래스가 들어 있는 어셈블리는 완전히 신뢰되므로 코드를 내보내는 어셈블리보다 신뢰 수준이 높습니다.
이러한 비교를 통해 부분 신뢰 코드에서 ReflectionPermissionFlag.RestrictedMemberAccess를 사용하여 신뢰할 수 있는 코드의 보안에 악영향을 주지 않고 다른 부분 신뢰 코드에 대한 가시성 검사를 생략하는 방법을 확인할 수 있습니다.
코드
Imports System
Imports System.Reflection.Emit
Imports System.Reflection
Imports System.Security
Imports System.Security.Permissions
Imports System.Security.Policy
Imports System.Collections
Imports System.Diagnostics
' This code example works properly only if it is run from a fully
' trusted location, such as your local computer.
' Delegates used to execute the dynamic methods.
'
Public Delegate Sub Test(ByVal w As Worker)
Public Delegate Sub Test1()
Public Delegate Function Test2(ByVal instance As String) As Char
' The Worker class must inherit MarshalByRefObject so that its public
' methods can be invoked across application domain boundaries.
'
Public Class Worker
Inherits MarshalByRefObject
Private Sub PrivateMethod()
Console.WriteLine("Worker.PrivateMethod()")
End Sub
Public Sub SimpleEmitDemo()
Dim meth As DynamicMethod = new DynamicMethod("", Nothing, Nothing)
Dim il As ILGenerator = meth.GetILGenerator()
il.EmitWriteLine("Hello, World!")
il.Emit(OpCodes.Ret)
Dim t1 As Test1 = CType(meth.CreateDelegate(GetType(Test1)), Test1)
t1()
End Sub
' This overload of AccessPrivateMethod emits a dynamic method and
' specifies whether to skip JIT visiblity checks. It creates a
' delegate for the method and invokes the delegate. The dynamic
' method calls a private method of the Worker class.
Overloads Public Sub AccessPrivateMethod( _
ByVal restrictedSkipVisibility As Boolean)
' Create an unnamed dynamic method that has no return type,
' takes one parameter of type Worker, and optionally skips JIT
' visiblity checks.
Dim meth As New DynamicMethod("", _
Nothing, _
New Type() { GetType(Worker) }, _
restrictedSkipVisibility)
' Get a MethodInfo for the private method.
Dim pvtMeth As MethodInfo = GetType(Worker).GetMethod( _
"PrivateMethod", _
BindingFlags.NonPublic Or BindingFlags.Instance)
' Get an ILGenerator and emit a body for the dynamic method.
Dim il As ILGenerator = meth.GetILGenerator()
' Load the first argument, which is the target instance, onto the
' execution stack, call the private method, and return.
il.Emit(OpCodes.Ldarg_0)
il.EmitCall(OpCodes.Call, pvtMeth, Nothing)
il.Emit(OpCodes.Ret)
' Create a delegate that represents the dynamic method, and
' invoke it.
Try
Dim t As Test = CType(meth.CreateDelegate(GetType(Test)), Test)
Try
t(Me)
Catch ex As Exception
Console.WriteLine("{0} was thrown when the delegate was invoked.", _
ex.GetType().Name)
End Try
Catch ex As Exception
Console.WriteLine("{0} was thrown when the delegate was compiled.", _
ex.GetType().Name)
End Try
End Sub
' This overload of AccessPrivateMethod emits a dynamic method that takes
' a string and returns the first character, using a private field of the
' String class. The dynamic method skips JIT visiblity checks.
Overloads Public Sub AccessPrivateMethod()
Dim meth As New DynamicMethod("", _
GetType(Char), _
New Type() {GetType(String)}, _
True)
' Get a MethodInfo for the 'get' accessor of the private property.
Dim pi As PropertyInfo = GetType(String).GetProperty( _
"FirstChar", _
BindingFlags.NonPublic Or BindingFlags.Instance)
Dim pvtMeth As MethodInfo = pi.GetGetMethod(True)
' Get an ILGenerator and emit a body for the dynamic method.
Dim il As ILGenerator = meth.GetILGenerator()
' Load the first argument, which is the target string, onto the
' execution stack, call the 'get' accessor to put the result onto
' the execution stack, and return.
il.Emit(OpCodes.Ldarg_0)
il.EmitCall(OpCodes.Call, pvtMeth, Nothing)
il.Emit(OpCodes.Ret)
' Create a delegate that represents the dynamic method, and
' invoke it.
Try
Dim t As Test2 = CType(meth.CreateDelegate(GetType(Test2)), Test2)
Dim first As Char = t("Hello, World!")
Console.WriteLine("{0} is the first character.", first)
Catch ex As Exception
Console.WriteLine("{0} was thrown when the delegate was compiled.", _
ex.GetType().Name)
End Try
End Sub
End Class
Friend Class Example
' The entry point for the code example.
Shared Sub Main()
' Get the display name of the executing assembly, to use when
' creating objects to run code in application domains.
Dim asmName As String = [Assembly].GetExecutingAssembly().FullName
' Create evidence for a partially trusted location and a setup object
' that specifies the current directory for the application directory.
Dim zoneEvidence() As Object = { New Zone(SecurityZone.Internet) }
Dim internetZone As New Evidence(zoneEvidence, zoneEvidence)
Dim adSetup As New AppDomainSetup()
adSetup.ApplicationBase = "."
' Retrieve the Internet grant set from system policy, and create
' an application domain in which all code that executes is granted
' the permissions of an application run from the Internet.
Dim internetSet As PermissionSet = GetNamedPermissionSet("Internet")
Dim ad As AppDomain = AppDomain.CreateDomain("ChildDomain1", _
internetZone, _
adSetup, _
internetSet, _
Nothing)
' Create an instance of the Worker class in the partially trusted
' domain. Note: If you build this code example in Visual Studio,
' you must change the name of the class to include the default
' namespace, which is the project name. For example, if the project
' is "AnonymouslyHosted", the class is "AnonymouslyHosted.Worker".
Dim w As Worker = _
CType(ad.CreateInstanceAndUnwrap(asmName, "Worker"), Worker)
' Emit a simple dynamic method that prints "Hello, World!"
w.SimpleEmitDemo()
' Emit and invoke a dynamic method that calls a private method
' of Worker, with JIT visibility checks enforced. The call fails
' when the delegate is invoked.
w.AccessPrivateMethod(False)
' Emit and invoke a dynamic method that calls a private method
' of Worker, skipping JIT visibility checks. The call fails when
' the method is compiled.
w.AccessPrivateMethod(True)
' Unload the application domain. Now create a grant set composed
' of the permissions granted to an Internet application plus
' RestrictedMemberAccess, and use it to create an application
' domain in which partially trusted code can call private members,
' as long as the trust level of those members is equal to or lower
' than the trust level of the partially trusted code.
AppDomain.Unload(ad)
internetSet = GetNamedPermissionSet("Internet")
internetSet.SetPermission( _
New ReflectionPermission( _
ReflectionPermissionFlag.RestrictedMemberAccess))
ad = AppDomain.CreateDomain("ChildDomain2", _
internetZone, _
adSetup, _
internetSet, _
Nothing)
' Create an instance of the Worker class in the partially trusted
' domain.
w = CType(ad.CreateInstanceAndUnwrap(asmName, "Worker"), Worker)
' Again, emit and invoke a dynamic method that calls a private method
' of Worker, skipping JIT visibility checks. This time compilation
' succeeds because of the grant for RestrictedMemberAccess.
w.AccessPrivateMethod(True)
' Finally, emit and invoke a dynamic method that calls an internal
' method of the String class. The call fails, because the trust level
' of the assembly that contains String is higher than the trust level
' of the assembly that emits the dynamic method.
w.AccessPrivateMethod()
End Sub
' This method retrieves a named permission set from security policy.
' The return value is the intersection of all permission sets with the
' given name, from all policy levels, or an empty permission set if the
' name is not found.
Private Shared Function GetNamedPermissionSet(ByVal name As String) As PermissionSet
If (String.IsNullOrEmpty(name)) Then
Throw New ArgumentException("name", "Cannot search for a permission set without a name.")
End If
Dim foundName As Boolean = False
Dim setIntersection As New PermissionSet(PermissionState.Unrestricted)
' Search all policy levels.
Dim levelEnumerator As IEnumerator = SecurityManager.PolicyHierarchy()
While (levelEnumerator.MoveNext())
Dim level As PolicyLevel = levelEnumerator.Current
Debug.Assert(level IsNot Nothing)
' If this policy level has a named permission set with the
' specified name, intersect it with previous levels.
Dim levelSet As PermissionSet = level.GetNamedPermissionSet(name)
If (levelSet IsNot Nothing) Then
foundName = True
setIntersection = setIntersection.Intersect(levelSet)
' Intersect() returns null for an empty set. If this occurs
' at any point, the resulting permission set is empty.
If (setIntersection Is Nothing) Then
Return New PermissionSet(PermissionState.None)
End If
End If
End While
If Not foundName Then
setIntersection = New PermissionSet(PermissionState.None)
End If
Return setIntersection
End Function
End Class
' This code example produces the following output:
'
'Hello, World!
'MethodAccessException was thrown when the delegate was invoked.
'MethodAccessException was thrown when the delegate was compiled.
'Worker.PrivateMethod()
'MethodAccessException was thrown when the delegate was compiled.
'
using System;
using System.Reflection.Emit;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;
using System.Collections;
using System.Diagnostics;
// This code example works properly only if it is run from a fully
// trusted location, such as your local computer.
// Delegates used to execute the dynamic methods.
//
public delegate void Test(Worker w);
public delegate void Test1();
public delegate char Test2(String instance);
// The Worker class must inherit MarshalByRefObject so that its public
// methods can be invoked across application domain boundaries.
//
public class Worker : MarshalByRefObject
{
private void PrivateMethod()
{
Console.WriteLine("Worker.PrivateMethod()");
}
public void SimpleEmitDemo()
{
DynamicMethod meth = new DynamicMethod("", null, null);
ILGenerator il = meth.GetILGenerator();
il.EmitWriteLine("Hello, World!");
il.Emit(OpCodes.Ret);
Test1 t1 = (Test1) meth.CreateDelegate(typeof(Test1));
t1();
}
// This overload of AccessPrivateMethod emits a dynamic method and
// specifies whether to skip JIT visiblity checks. It creates a
// delegate for the method and invokes the delegate. The dynamic
// method calls a private method of the Worker class.
public void AccessPrivateMethod(bool restrictedSkipVisibility)
{
// Create an unnamed dynamic method that has no return type,
// takes one parameter of type Worker, and optionally skips JIT
// visiblity checks.
DynamicMethod meth = new DynamicMethod(
"",
null,
new Type[] { typeof(Worker) },
restrictedSkipVisibility);
// Get a MethodInfo for the private method.
MethodInfo pvtMeth = typeof(Worker).GetMethod("PrivateMethod",
BindingFlags.NonPublic | BindingFlags.Instance);
// Get an ILGenerator and emit a body for the dynamic method.
ILGenerator il = meth.GetILGenerator();
// Load the first argument, which is the target instance, onto the
// execution stack, call the private method, and return.
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Call, pvtMeth, null);
il.Emit(OpCodes.Ret);
// Create a delegate that represents the dynamic method, and
// invoke it.
try
{
Test t = (Test) meth.CreateDelegate(typeof(Test));
try
{
t(this);
}
catch (Exception ex)
{
Console.WriteLine("{0} was thrown when the delegate was invoked.",
ex.GetType().Name);
}
}
catch (Exception ex)
{
Console.WriteLine("{0} was thrown when the delegate was compiled.",
ex.GetType().Name);
}
}
// This overload of AccessPrivateMethod emits a dynamic method that takes
// a string and returns the first character, using a private field of the
// String class. The dynamic method skips JIT visiblity checks.
public void AccessPrivateMethod()
{
DynamicMethod meth = new DynamicMethod("",
typeof(char),
new Type[] { typeof(String) },
true);
// Get a MethodInfo for the 'get' accessor of the private property.
PropertyInfo pi = typeof(System.String).GetProperty(
"FirstChar",
BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo pvtMeth = pi.GetGetMethod(true);
// Get an ILGenerator and emit a body for the dynamic method.
ILGenerator il = meth.GetILGenerator();
// Load the first argument, which is the target string, onto the
// execution stack, call the 'get' accessor to put the result onto
// the execution stack, and return.
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Call, pvtMeth, null);
il.Emit(OpCodes.Ret);
// Create a delegate that represents the dynamic method, and
// invoke it.
try
{
Test2 t = (Test2) meth.CreateDelegate(typeof(Test2));
char first = t("Hello, World!");
Console.WriteLine("{0} is the first character.", first);
}
catch (Exception ex)
{
Console.WriteLine("{0} was thrown when the delegate was compiled.",
ex.GetType().Name);
}
}
// The entry point for the code example.
static void Main()
{
// Get the display name of the executing assembly, to use when
// creating objects to run code in application domains.
String asmName = Assembly.GetExecutingAssembly().FullName;
// Create evidence for a partially trusted location and a setup object
// that specifies the current directory for the application directory.
Object[] zoneEvidence = { new Zone(SecurityZone.Internet) };
Evidence internetZone = new Evidence(zoneEvidence, zoneEvidence);
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = ".";
// Retrieve the Internet grant set from system policy, and create
// an application domain in which all code that executes is granted
// the permissions of an application run from the Internet.
PermissionSet internetSet = GetNamedPermissionSet("Internet");
AppDomain ad = AppDomain.CreateDomain("ChildDomain1",
internetZone,
adSetup,
internetSet,
null);
// Create an instance of the Worker class in the partially trusted
// domain. Note: If you build this code example in Visual Studio,
// you must change the name of the class to include the default
// namespace, which is the project name. For example, if the project
// is "AnonymouslyHosted", the class is "AnonymouslyHosted.Worker".
Worker w = (Worker) ad.CreateInstanceAndUnwrap(asmName, "Worker");
// Emit a simple dynamic method that prints "Hello, World!"
w.SimpleEmitDemo();
// Emit and invoke a dynamic method that calls a private method
// of Worker, with JIT visibility checks enforced. The call fails
// when the delegate is invoked.
w.AccessPrivateMethod(false);
// Emit and invoke a dynamic method that calls a private method
// of Worker, skipping JIT visibility checks. The call fails when
// the method is compiled.
w.AccessPrivateMethod(true);
// Unload the application domain. Now create a grant set composed
// of the permissions granted to an Internet application plus
// RestrictedMemberAccess, and use it to create an application
// domain in which partially trusted code can call private members,
// as long as the trust level of those members is equal to or lower
// than the trust level of the partially trusted code.
AppDomain.Unload(ad);
internetSet = GetNamedPermissionSet("Internet");
internetSet.SetPermission(
new ReflectionPermission(
ReflectionPermissionFlag.RestrictedMemberAccess));
ad = AppDomain.CreateDomain("ChildDomain2",
internetZone,
adSetup,
internetSet,
null);
// Create an instance of the Worker class in the partially trusted
// domain.
w = (Worker) ad.CreateInstanceAndUnwrap(asmName, "Worker");
// Again, emit and invoke a dynamic method that calls a private method
// of Worker, skipping JIT visibility checks. This time compilation
// succeeds because of the grant for RestrictedMemberAccess.
w.AccessPrivateMethod(true);
// Finally, emit and invoke a dynamic method that calls an internal
// method of the String class. The call fails, because the trust level
// of the assembly that contains String is higher than the trust level
// of the assembly that emits the dynamic method.
w.AccessPrivateMethod();
}
// This method retrieves a named permission set from security policy.
// The return value is the intersection of all permission sets with the
// given name, from all policy levels, or an empty permission set if the
// name is not found.
private static PermissionSet GetNamedPermissionSet(string name)
{
if (String.IsNullOrEmpty(name))
throw new ArgumentException("name", "Cannot search for a permission set without a name.");
bool foundName = false;
PermissionSet setIntersection = new PermissionSet(PermissionState.Unrestricted);
// Search all policy levels.
IEnumerator levelEnumerator = SecurityManager.PolicyHierarchy();
while (levelEnumerator.MoveNext())
{
PolicyLevel level = levelEnumerator.Current as PolicyLevel;
Debug.Assert(level != null);
// If this policy level has a named permission set with the
// specified name, intersect it with previous levels.
PermissionSet levelSet = level.GetNamedPermissionSet(name);
if (levelSet != null)
{
foundName = true;
setIntersection = setIntersection.Intersect(levelSet);
// Intersect() returns null for an empty set. If this occurs
// at any point, the resulting permission set is empty.
if (setIntersection == null)
return new PermissionSet(PermissionState.None);
}
}
if (!foundName)
setIntersection = new PermissionSet(PermissionState.None);
return setIntersection;
}
}
/* This code example produces the following output:
Hello, World!
MethodAccessException was thrown when the delegate was invoked.
MethodAccessException was thrown when the delegate was compiled.
Worker.PrivateMethod()
MethodAccessException was thrown when the delegate was compiled.
*/
코드 컴파일
- Visual Studio에서 이 코드 예제를 빌드하는 경우에는 클래스를 CreateInstanceAndUnwrap 메서드에 전달할 때 네임스페이스를 포함하도록 클래스의 이름을 변경해야 합니다. 기본적으로 네임스페이스는 프로젝트의 이름입니다. 예를 들어, 프로젝트가 "PartialTrust"이면 클래스 이름은 "PartialTrust.Worker"여야 합니다.