WPF 部分信任安全

一般情况下,应该限制 Internet 应用程序直接访问关键系统资源,防止恶意损坏。 默认情况下,HTML 和客户端脚本语言无法访问关键系统资源。 因为 Windows Presentation Foundation (WPF) 浏览器托管的应用程序可从该浏览器中启动,所以它们应该符合一组类似的限制。 为了实施这些限制,WPF 同时依赖于代码访问安全性 (CAS) 和 ClickOnce(请参阅 WPF 安全策略 - 平台安全性)。 默认情况下,浏览器托管的应用程序请求 Internet 区域 CAS 权限集,而不考虑其是从 Internet、本地 Intranet 还是本地计算机启动。 如果应用程序的运行权限小于完整权限集,则说明该应用程序正在部分信任环境下运行。

WPF 提供了各种各样的支持,确保可以在部分信任环境中安全地使用尽可能多的功能;还与 CAS 一起为部分信任编程提供了附加支持。

本主题包含以下各节:

WPF 功能部分信任支持

下表列出了可在 Internet 区域权限集限制范围内安全使用的 Windows Presentation Foundation (WPF) 高级功能。

表 1:在部分信任环境中安全的 WPF 功能

功能区域 功能
常规 浏览器窗口

源站点访问

IsolatedStorage(512KB 限制)

UIAutomation 提供程序

命令

输入法编辑器 (IME)

触笔和墨迹

使用鼠标捕获和移动事件模拟的拖/放

OpenFileDialog

XAML 反序列化(通过 XamlReader.Load)
Web 集成 浏览器下载对话框

顶级用户启动的导航

mailto:links

统一资源标识符参数

HTTPWebRequest

IFRAME 中托管的 WPF 内容

使用框架托管同一站点 HTML 页

使用 WebBrowser 托管同一站点 HTML 页

Web 服务 (ASMX)

Web 服务(使用 Windows Communication Foundation)

脚本编写

文档对象模型
视觉对象 2D 和 3D

动画

媒体(源站点和跨域)

图像处理/音频/视频
正在读取 流文档

XPS 文档

嵌入式字体与系统字体

CFF 字体与 TrueType 字体
正在编辑 拼写检查

RichTextBox

纯文本和墨迹剪贴板支持

用户启动的粘贴

复制选定内容
控件 常规控件

此表包括高级 WPF 功能。 有关详细信息,请参阅 Windows SDK 文档,其中介绍了 WPF 中每个成员所需的权限。 此外,以下功能含有部分信任执行的相关详细信息,其中包括特殊注意事项。

下表概括了不能在 Internet 区域权限集限制范围内安全运行的 WPF 功能。

表 2:在部分信任环境中不安全的 WPF 功能

功能区域 功能
常规 窗口(应用程序定义的窗口和对话框)

SaveFileDialog

文件系统

注册表访问

拖放

XAML 序列化(通过 XamlWriter.Save)

UIAutomation 客户端

源窗口访问 (HwndHost)

完全语音支持

Windows 窗体互操作性
视觉对象 位图效果

图像编码
正在编辑 RTF 格式剪贴板

完全 XAML 支持

部分信任编程

对于 XBAP 应用程序,超出默认权限集的代码将有不同的行为,具体情况视安全区域而定。 在某些情况下,用户会在尝试安装它时收到警告。 用户可以选择继续或取消安装。 下表描述每个安全区域的应用程序的行为,以及为了使应用程序接收完全信任而必须执行的操作。

警告

XBAP 需要旧版浏览器(例如 Internet Explorer 和 Firefox)才能运行。 Windows 10 和 Windows 11 通常不支持这些较旧的浏览器版本。 由于安全风险,新式浏览器不再支持 XBAP 应用所需的技术。 不再支持启用 XBAP 的插件。

安全区域 行为 获取完全信任
本地计算机 自动完全信任 无需采取任何措施。
Intranet 和受信任的站点 提示完全信任 使用证书对 XBAP 进行签名,以便用户在提示中看到源。
Internet 失败,并显示“未授予信任” 使用证书对 XBAP 进行签名。

注意

上表中描述的行为针对不遵循 ClickOnce 受信任部署模型的完全信任 XBAP。

通常,超出允许权限的代码可能是在独立应用程序和浏览器托管的应用程序之间共享的公用代码。 CAS 和 WPF 提供了几个用于管理此方案的技巧。

使用 CAS 检测权限

在某些情况下,独立应用程序和 XBAP 可能同时使用库程序集中的共享代码。 这时,代码执行的功能所需要的权限可能超出应用程序的授权权限集允许的权限。 通过使用 Microsoft .NET Framework 安全性,应用程序可检测其是否具有某个权限。 具体来说,它可以通过在所需权限的实例上调用 Demand 方法来测试其是否具有特定权限。 以下示例对此进行了演示,示例中的代码查询其是否能够将文件保存到本地磁盘:

using System.IO;
using System.IO.IsolatedStorage;
using System.Security;
using System.Security.Permissions;
using System.Windows;

namespace SDKSample
{
    public class FileHandling
    {
        public void Save()
        {
            if (IsPermissionGranted(new FileIOPermission(FileIOPermissionAccess.Write, @"c:\newfile.txt")))
            {
                // Write to local disk
                using (FileStream stream = File.Create(@"c:\newfile.txt"))
                using (StreamWriter writer = new StreamWriter(stream))
                {
                    writer.WriteLine("I can write to local disk.");
                }
            }
            else
            {
                MessageBox.Show("I can't write to local disk.");
            }
        }

        // Detect whether or not this application has the requested permission
        bool IsPermissionGranted(CodeAccessPermission requestedPermission)
        {
            try
            {
                // Try and get this permission
                requestedPermission.Demand();
                return true;
            }
            catch
            {
                return false;
            }
        }


Imports System.IO
Imports System.IO.IsolatedStorage
Imports System.Security
Imports System.Security.Permissions
Imports System.Windows

Namespace SDKSample
    Public Class FileHandling
        Public Sub Save()
            If IsPermissionGranted(New FileIOPermission(FileIOPermissionAccess.Write, "c:\newfile.txt")) Then
                ' Write to local disk
                Using stream As FileStream = File.Create("c:\newfile.txt")
                Using writer As New StreamWriter(stream)
                    writer.WriteLine("I can write to local disk.")
                End Using
                End Using
            Else
                MessageBox.Show("I can't write to local disk.")
            End If
        End Sub

        ' Detect whether or not this application has the requested permission
        Private Function IsPermissionGranted(ByVal requestedPermission As CodeAccessPermission) As Boolean
            Try
                ' Try and get this permission
                requestedPermission.Demand()
                Return True
            Catch
                Return False
            End Try
        End Function

    }
}
    End Class
End Namespace

如果应用程序不具有所需权限,则对 Demand 的调用会引发安全异常。 如果没有引发异常,则表示已授予该权限。 IsPermissionGranted 封装了这一行为,并根据情况返回 truefalse

功能下降

对可从不同区域执行的代码而言,能够检测代码是否具有完成所需操作的权限是很有意义的。 能够检测区域固然不错,但如果能够为用户提供替代方法,则要好得多。 例如,完全信任应用程序通常使用户能够在所需的任何位置创建文件,而部分信任应用程序只能在独立存储中创建文件。 如果用于创建文件的代码存在于完全信任(独立)应用程序和部分信任(浏览器托管的)应用程序共享的程序集中,并且这两个应用程序都希望用户能够创建文件,则共享代码应首先检测其是在部分信任环境还是完全信任环境中运行,然后才能在适当的位置创建文件。 下面的代码对这两种情况进行了演示。

using System.IO;
using System.IO.IsolatedStorage;
using System.Security;
using System.Security.Permissions;
using System.Windows;

namespace SDKSample
{
    public class FileHandlingGraceful
    {
        public void Save()
        {
            if (IsPermissionGranted(new FileIOPermission(FileIOPermissionAccess.Write, @"c:\newfile.txt")))
            {
                // Write to local disk
                using (FileStream stream = File.Create(@"c:\newfile.txt"))
                using (StreamWriter writer = new StreamWriter(stream))
                {
                    writer.WriteLine("I can write to local disk.");
                }
            }
            else
            {
                // Persist application-scope property to
                // isolated storage
                IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
                using (IsolatedStorageFileStream stream =
                    new IsolatedStorageFileStream("newfile.txt", FileMode.Create, storage))
                using (StreamWriter writer = new StreamWriter(stream))
                {
                    writer.WriteLine("I can write to Isolated Storage");
                }
            }
        }

        // Detect whether or not this application has the requested permission
        bool IsPermissionGranted(CodeAccessPermission requestedPermission)
        {
            try
            {
                // Try and get this permission
                requestedPermission.Demand();
                return true;
            }
            catch
            {
                return false;
            }
        }


Imports System.IO
Imports System.IO.IsolatedStorage
Imports System.Security
Imports System.Security.Permissions
Imports System.Windows

Namespace SDKSample
    Public Class FileHandlingGraceful
        Public Sub Save()
            If IsPermissionGranted(New FileIOPermission(FileIOPermissionAccess.Write, "c:\newfile.txt")) Then
                ' Write to local disk
                Using stream As FileStream = File.Create("c:\newfile.txt")
                Using writer As New StreamWriter(stream)
                    writer.WriteLine("I can write to local disk.")
                End Using
                End Using
            Else
                ' Persist application-scope property to 
                ' isolated storage
                Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
                Using stream As New IsolatedStorageFileStream("newfile.txt", FileMode.Create, storage)
                Using writer As New StreamWriter(stream)
                    writer.WriteLine("I can write to Isolated Storage")
                End Using
                End Using
            End If
        End Sub

        ' Detect whether or not this application has the requested permission
        Private Function IsPermissionGranted(ByVal requestedPermission As CodeAccessPermission) As Boolean
            Try
                ' Try and get this permission
                requestedPermission.Demand()
                Return True
            Catch
                Return False
            End Try
        End Function

    }
}
    End Class
End Namespace

在很多情况下,应该能够找到部分信任替代方法。

在受控环境(例如 Intranet)中,可将自定义托管框架安装到整个客户端群的全局程序集缓存 (GAC) 中。 这些库可执行需要完全信任的代码,并且可以通过使用 AllowPartiallyTrustedCallersAttribute 从仅授予了部分信任的应用程序中引用(有关详细信息,请参阅安全性WPF 安全策略 - 平台安全性)。

浏览器主机检测

需要按权限进行检查时,使用 CAS 检查是否具有权限是一种恰当的方法。 然而,这一技巧依赖于在正常处理过程中捕获异常(通常不鼓励这样做),并且可能导致性能问题。 如果 XAML 浏览器应用程序 (XBAP) 仅在 Internet 区域沙盒中运行,则可以使用 BrowserInteropHelper.IsBrowserHosted 属性,该属性为 XAML 浏览器应用程序 (XBAP) 返回 true。

警告

XBAP 需要旧版浏览器(例如 Internet Explorer 和 Firefox)才能运行。 Windows 10 和 Windows 11 通常不支持这些较旧的浏览器版本。 由于安全风险,新式浏览器不再支持 XBAP 应用所需的技术。 不再支持启用 XBAP 的插件。

注意

IsBrowserHosted 只区分应用程序是否在浏览器中运行,而不区分应用程序正在通过哪个权限集运行。

管理权限

默认情况下,XBAP 在部分信任环境(默认 Internet 区域权限集)下运行。 但是,根据应用程序的要求,可以更改默认的权限集。 例如,如果 XBAP 是从本地 Intranet 启动的,则可以利用增强的权限集,如下表所示。

警告

XBAP 需要旧版浏览器(例如 Internet Explorer 和 Firefox)才能运行。 Windows 10 和 Windows 11 通常不支持这些较旧的浏览器版本。 由于安全风险,新式浏览器不再支持 XBAP 应用所需的技术。 不再支持启用 XBAP 的插件。

表 3:LocalIntranet 和 Internet 权限

权限 Attribute LocalIntranet Internet
DNS 访问 DNS 服务器
环境变量 读取
文件对话框 开放
文件对话框 非受限
独立存储 按用户隔离程序集
独立存储 未知隔离
独立存储 无限制用户配额
媒体 安全音频、视频和图像
打印 默认打印
打印 安全打印
反射 发出
安全性 托管代码执行
安全性 声明授予的权限
用户界面 非受限
用户界面 安全顶级窗口
用户界面 自己的剪贴板
Web 浏览器 HTML 中的安全框架导航

注意

如果由用户启动,则剪切和粘贴只允许以部分信任方式执行。

如果需要增加权限,则需要更改项目设置和 ClickOnce 应用程序清单。 有关详细信息,请参阅 WPF XAML 浏览器应用程序概述。 以下各个文档可能也会有帮助。

如果 XBAP 需要完全信任,则可使用相同的工具来增加所需权限。 但只有从本地计算机、Intranet 或从浏览器的受信任或允许站点中列出的 URL 安装和启动 XBAP 时,它才会得到完全信任。 如果从 Intranet 或受信任站点安装应用程序,则用户会收到标准 ClickOnce 提示,通知用户提升了权限。 用户可以选择继续或取消安装。

或者,可以使用 ClickOnce 受信任部署模型从任何安全区域中进行完全信任部署。 有关详细信息,请参阅受信任的应用程序部署概述安全性

另请参阅