解決因SQL Server數據庫引用程序集而不得不開啓trustworthy方法

​前文中提到有提到,當SQL Server 數據庫開啓trustworthy時,存在着用戶提升權限的風險。那麼,我們是不是直接查出開啓trustworthy的數據庫,將其關閉就解決風險了呢?如下腳本可以直接關閉實例下所有開啓trustworthy數據庫的trustworthy屬性:

use master
go
declare @sql varchar(max)     
set @sql=''
select @sql=@sql+'alter database '+ QUOTENAME(name,'[')+' set trustworthy ' +' off '
from sys.databases
where is_trustworthy_on=1
print @sql
--exec(@sql)

有心的朋友可能已經注意到,上面腳本的最後一句是註釋掉的,我們是不能直接執行這個腳本的(重要的事情說三篇,不能直接執行!不能直接執行!不能直接執行!,我們必須確認數據庫上是否有需要開啓trustworthy的內容,如程序集。

    帶有EXTERNAL_ACCESS 或 UNSAFE的程序集能正常執行的方案有兩種:

  1. 開啓數據庫trustworthy,保證數據庫擁有者有程序集 [EXTERNAL ACCESS] 或者 [UNSAFE] 權限。

  2.  程序集已使用其對應登錄名具有 EXTERNAL ACCESS ASSEMBLY 權限的證書或非對稱密鑰加以簽名。

考慮到前文中提到的安全隱患,如果數據庫中存在着帶有EXTERNAL_ACCESS 或 UNSAFE的程序集,並且數據庫的trustworthy爲開啓狀態,我們就需要採用方案2來代替方案1,消除安全隱患,下面我將分享方案2的處理步驟。

 

爲ddl加強名稱簽名

強名稱簽名的私鑰公鑰生成

本文中是使用Windows強名稱簽名工具sn生成私鑰和公鑰的,首先我們需要到目錄 :

C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools 下查找是否有sn工具,沒有的話,就要另外安裝,如果存在,以管理員身份打開CMD,切換到上面對應目錄下:

使用強名稱工具sn生成私鑰、公鑰的具體腳本及步驟如下:

最終將生成的私鑰pri.snk 拷貝到項目文件中

在VS解決方案資源管理器中,右擊項目名稱→屬性

在彈出的屬性列表中選擇“簽名”→勾選爲程序集簽名,找到前面生成的私鑰文件,這裏是pri.snk:

最後重新生成dll,到此完成程序集的強名稱簽名。

將dll 部署到SQL Server數據庫

下面我將給出將強名稱簽名的程序集部署到數據庫上的腳本,並進行測試,如下:

use DB1
go
exec sp_configure 'show advanced options',1
go
reconfigure
go
exec sp_configure 'clr enabled',1
reconfigure with override
exec sp_configure 'show advanced options',0
go
reconfigure
go
​
USE master  --這個數據庫一定是master
--創建非對稱密鑰
CREATE ASYMMETRIC KEY SQLCLRTestKey FROM EXECUTABLE FILE = 'D:\TgSQLPlus\Skew.dll'  
--創建登錄名
CREATE LOGIN SQLCLRTestLogin FROM ASYMMETRIC KEY SQLCLRTestKey
--把權限授予給該登錄名 
GRANT EXTERNAL ACCESS ASSEMBLY TO SQLCLRTestLogin;
USE DB1
--創建程序集
create ASSEMBLY DescriptiveStatistics from
'D:\TgSQLPlus\Skew.dll'
with PERMISSION_SET=EXTERNAL_ACCESS;
--創建用戶定義函數(UDF)
CREATE AGGREGATE dbo.Skew(@s float)
returns float
external name DescriptiveStatistics.Skew;
create table test (name varchar(60),id bigint)
--測試
insert into test
select top 100 name,object_id
from sys.objects
go 10
with CustomerSalesCTE as
(
select
      name
       ,SUM(id) as TotalAmount
from test 
group by name
)
select
       ROUND(avg(TotalAmount),2) as Average
       ,ROUND(STDEV(TotalAmount),2) as StandardDeviation
       ,ROUND(dbo.Skew(cast(TotalAmount as decimal(30,2))),6) as Skewness
     
from CustomerSalesCTE
--確認trustworthy是否啓用
select name,is_trustworthy_on from sys.databases
where name='DB1'

測試發現trustworthy關閉狀態,自定義函數正常運行。

注意:

  • 如果兩個程序集使用同一個私鑰進行簽名,在master庫創建非對稱祕鑰時,會報如下錯誤:

消息 15396,級別 16,狀態 1,第 103 行 名爲 '' 的非對稱密鑰已存在,或已將此非對稱密鑰添加到該數據庫中。

  • 在trustworthy關閉狀態下,要先在master庫創建非對稱祕鑰,使用非對稱祕鑰創建登陸名,並賦予[EXTERNAL ACCESS]權限,然後再在指定數據庫中創建程序集,順序不能顛倒,否則會報如下錯誤:

消息 10327,級別 14,狀態 1,第 111 行

針對程序集 'DatabaseKurt' 的 CREATE ASSEMBLY 失敗,因爲程序集 'DatabaseKurt' 未獲授權,不滿足 PERMISSION_SET = EXTERNAL_ACCESS。滿足以下兩個條件之一時將給程序集授權: 數據庫所有者(DBO)擁有 EXTERNAL ACCESS ASSEMBLY 權限,且數據庫具有 TRUSTWORTHY 數據庫屬性;或者,程序集已使用其對應登錄名具有 EXTERNAL ACCESS ASSEMBLY 權限的證書或非對稱密鑰加以簽名。

  • 登陸賬戶的權限如果和創建程序集PERMISSION_SET選項不一致,即賦予賬戶[EXTERNAL ACCESS],PERMISSION_SET=UNSAFE,或者相反時,創建程序集亦會出現如下錯誤:

消息 10327,級別 14,狀態 1,第 111 行

針對程序集 'DatabaseKurt' 的 CREATE ASSEMBLY 失敗,因爲程序集 'DatabaseKurt' 未獲授權,不滿足 PERMISSION_SET = UNSAFE。滿足以下兩個條件之一時將給程序集授權: 數據庫所有者(DBO)擁有 UNSAFE ASSEMBLY 權限,且數據庫具有 TRUSTWORTHY 數據庫屬性;或者,程序集已使用其對應登錄名具有 UNSAFE ASSEMBLY 權限的證書或非對稱密鑰加以簽名。

最後如果測試發現報如下錯誤:

消息 10314,級別 16,狀態 4,第 74 行

在嘗試加載程序集 ID 65544 時 Microsoft .NET Framework 出錯。服務器可能資源不足,或者不信任該程序集,因爲它的 PERMISSION_SET 設置爲 EXTERNAL_ACCESS 或 UNSAFE。請重新運行查詢,或檢查有關的文檔瞭解如何解決程序集信任問題。有關此錯誤的詳細信息:

System.IO.FileLoadException: 未能加載文件或程序集“databasekurt, Version=0.0.0.0, Culture=neutral, PublicKeyToken=5db4a667503bfd56”或它的某一個依賴項。發生與安全有關的錯誤。 (異常來自 HRESULT:0x8013150A)

System.IO.FileLoadException:

   在 System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)

   在 System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)

   在 System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean forIntrospection)

   在 System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)

   在 System.Reflection.Assembly.Load(String assemblyString)

我們需要從頭檢查各個步驟是否完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章