天天看點

編寫安全的 Transact-SQL

釋出日期: 11/29/2004 | 更新日期: 11/29/2004

Bart Duncan

Microsoft Corporation

摘要:

Microsoft 産品支援服務的客座專欄作家 Bart Duncan 評述一些最佳方法,使用它們可以使 Transact-SQL 代碼更能抵禦攻擊。

編寫安全的 Transact-SQL
本頁内容
編寫安全的 Transact-SQL
簡介
編寫安全的 Transact-SQL
保護開發 SQL Server 的安全
編寫安全的 Transact-SQL
以最低權限帳戶身份進行開發
編寫安全的 Transact-SQL
遵照保護 T-SQL 的最佳方法
編寫安全的 Transact-SQL
了解具有獨特安全考慮事項的 T-SQL 指令
編寫安全的 Transact-SQL
小結
編寫安全的 Transact-SQL
參考資料

簡介

關于如何以安全的方式部署 SQL Server,存在大量很好的資訊源。但是,這些資源的目标使用者通常都是那些對已經開發好的應用程式執行保護任務的資料庫管理者。另外,還有很多内容讨論了如何編寫安全的 .NET 和 ASP.NET 代碼,其中包括通路 SQL Server 的 .NET 代碼。然而,很多這樣的資源關注的是在應用伺服器上運作的資料通路代碼,而不是在 SQL Server 中執行的 Transact-SQL (T-SQL) 代碼。本專欄則将注意力投射到如何開發在 SQL Server 上安全運作的 T-SQL 代碼。

編寫安全的 Transact-SQL

傳回頁首

保護開發 SQL Server 的安全

開發安全 T-SQL 的第一步是保護開發 SQL Server 的安全。為什麼要想方設法地鎖定一個不儲存真實資料的 SQL Server 執行個體,而從不将它展示給最終使用者呢?這是因為,這樣會強制您編寫更安全的 T-SQL,并且當将您的應用程式部署到生産中時,也會更加容易地保護該應用程式。下面是幾個具體的步驟,采用這些步驟您就可以快速保護開發伺服器:

在開發或測試 SQL Server 中,至少應該有一個正在運作的、最新的 Service Pack 和 SQL 安全修補程式,這樣才能確定您的客戶能夠在此 SQL Server 版本上成功運作您的應用程式。
預設情況下,SQL Server 2000 Service Pack 3a 會禁用稱為“交叉資料庫所有權連結”的不安全功能。在開發伺服器上安裝 SP3 時,如果讓該 Service Pack 禁用“交叉資料庫所有權連結”,則有助于驗證您正在基于安全的伺服器配置編寫 T-SQL 代碼。
找出開發 SQL Server 上常見的安全配置問題的一種簡便方法是針對該伺服器運作 Microsoft Baseline Security Analyzer。除了這種方法之外,還可以利用本專欄“參考資料”部分列出的資源;這些資源提供了一些附加步驟,可幫助您保護開發 SQL Server 的安全。

通常情況下,保護開發伺服器安全的最佳方法,就好似它正在生産環境中運作那樣對它進行保護。您離這個目标越接近,那麼就可以越自信于您開發的代碼可以在一個安全的生産環境中正常運作。

編寫安全的 Transact-SQL

傳回頁首

以最低權限帳戶身份進行開發

在開發過程中,大家都着迷于使用具有 sysadmin 或 dbo SQL Server 權限的帳戶,直到部署之前才轉換為一個權限更低的帳戶。使用這種方法存在着一個問題:将設計人員的權限集還原為最低的所需權限集與在開發應用程式過程中編寫這些權限集相比,前者要困難得多。

鑒于部署應用程式之前您要決定可以取消哪些權限,是以請不要使用 SQL sysadmin 帳戶開發 T-SQL 代碼。如果使用 SQL sysadmin 帳戶,可能會造成這樣的結果,即應用程式會以比所需權限更多的特權帳戶運作。是以,開發時請改為使用具有最低權限的帳戶。

使用這樣的帳戶進行開發時,您會逐漸地升高授予的特定權限,以 EXEC(執行)一些必需的存儲過程、從某些表進行 SELECT(選擇)等。請編寫這些 GRANT 語句,以便可以将同樣的最低權限輕松部署到生産環境中,而不會出現任何基于猜測的操作。

這種理念同樣适用于測試。執行臨時測試以及結構更加複雜的測試時,所使用帳戶擁有的權限集和使用者權限應該與在生産環境中所使用帳戶擁有的權限集和使用者權限完全相同。

在開發過程中使用最低權限帳戶的另一個優點在于,您可以避免不小心編寫出需要危險權限或過高權限的代碼。例如,假設您需要在 T-SQL 中與第三方 COM 元件進行互動。為此,一種方法是發送一個 SQL 批處理指令,它直接調用

sp_OACreate

sp_OAMethod

來操縱該 COM 對象。在應用程式使用 sysadmin 帳戶連接配接 SQL Server 的開發環境中,上述方法效果很好。但是,當您嘗試将已經開發完成的應用程式準備用于生産部署時,您就會發現如果使用權限較低的帳戶,那麼該方法不會奏效。為了讓該應用程式能夠使用非 sysadmin 帳戶在生産環境中正常運作,您必須針對 sp_OACreate 顯式授予 EXECUTE 權限。請考慮一下,如果某個使用者最終找到了一個方法,可以使用該應用程式登入執行任意代碼,并利用此權限針對 SQL Server 執行個體化一個類似

Scripting.FileSystemObject

的 COM 對象,将會産生怎樣的安全隐患?

編寫安全的 Transact-SQL

傳回頁首

遵照保護 T-SQL 的最佳方法

防禦一系列稱為“SQL 注入式”的安全漏洞是至關重要的。通常情況下,您會使用多層防護來抵禦 SQL 注入式攻擊:

執行使用者提供輸入的驗證(例如,強制資料類型和最大字元串長度)。
轉義對資料庫引擎可能具有特殊意義的字元序列。在 T-SQL 中,注入式攻擊中最常用的兩個字元串為單引号字元 ( ' ) 和注釋字元序列 ( -- )。
在 T-SQL 語句中,請不要将使用者提供的值進行内聯。請改為使用預處理語句和參數化。

SQL 注入式攻擊在其他一些地方有詳細的說明,是以在此我就不花大量時間來讨論這個問題的細節了。但是要強調一點,SQL 注入式問題并不隻限于在應用層建構的 T-SQL 查詢。隻要執行一個部分由使用者提供值建構的 T-SQL 查詢,就可能會發生 SQL 注入式問題。這就是說,一個在内部建構查詢字元串、并通過 EXEC() 指令或

sp_executesql

存儲過程執行該查詢的存儲過程也可能會受到攻擊。請參閱“參考資料”部分獲得一些資源連結,這些資源提供了各種 SQL 注入式攻擊類型的示例,還提供了一些保護代碼免受這些攻擊的技巧。

另一個最佳方法是避免針對基表授予權限。對于您希望使用者能夠執行的查詢,您應該将其打包在存儲過程中,并隻對這些存儲過程授予 EXECUTE 權限。如果您按照本指南進行操作,即使使用者設法跳過了您的應用程式,直接登入到資料庫,他們也無法回避您已經在存儲過程中建構的任何資料驗證、稽核、業務規則或者行級安全限制。

編寫安全的 Transact-SQL

傳回頁首

了解具有獨特安全考慮事項的 T-SQL 指令

有一些 T-SQL 指令和擴充,它們具有自己獨特的安全考慮事項。其中一個是 sp_OACreate 及其相關的系統過程系列(例如 sp_OAMethod、

sp_OAProperty

等)。以前,我們曾經研究過一個潛在的安全問題,通過授予應用程式登入直接通路這些過程的權限,會帶來該安全問題。為了避免此問題的發生,請絕對不要編寫直接調用 sp_OA 過程的應用程式代碼,而要将對這些過程的所有引用都打包在您自己的 T-SQL 存儲過程中,并隻授予通路這些包裝存儲過程的權限。另外,請不要允許應用程式代碼将 COM 對象或方法的名稱作為可由包裝過程無條件調用的字元串進行傳遞。

另一個具有獨特安全風險集的内置 SQL Server 擴充為 xp_cmdshell。這個系統存儲過程可以運作任何可執行檔案或系統指令。由于一些很顯然的原因,

xp_cmdshell

上的 EXEC 權限預設情況下僅為 sysadmin 使用者,必須顯示地為其他使用者授予該權限。如果您需要應用程式在 SQL Server 上運作某個特定的指令或實用程式,則請注意,不要在應用程式中建構一個 xp_cmdshell 直接通路的相關内容。這樣的風險與直接通路 sp_OACreate 的風險相似。一旦為某個帳戶授予了 xp_cmdshell 的 EXEC 權限,該帳戶不但能夠執行您希望其通路的特定指令,而且能夠執行成百上千個作業系統指令和其他可執行檔案。與 sp_OACreate 相似,始終将 xp_cmdshell 調用打包在另一個存儲過程中,避免直接在 xp_cmdshell 上授予 EXECUTE 權限。

您還應該避免将任何使用者提供的字元串參數或者應用程式提供的字元串參數與将要通過 xp_cmdshell 執行的指令進行串聯。如果無法達到上述要求,則必須了解,有一個專門針對 xp_cmdshell 的潛在的代碼注入式攻擊(至少在 SQL Server 中)。以下面的存儲過程為例:

CREATE PROCEDURE usp_DoFileCopy @filename varchar(255) AS
DECLARE @cmd varchar (8000)
SET @cmd = 'copy //src/share/' + @filename + ' //dest/share/'
EXEC master.dbo.xp_cmdshell @cmd
GO
GRANT EXEC ON usp_DoFileCopy TO myapplogin
      

通過将 xp_cmdshell 調用打包在您自己的存儲過程中并隻針對該

usp_DoFileCopy

存儲過程授予 EXEC 權限,您已經阻止了使用者直接調用 xp_cmdshell 以執行任意指令。然而,以下面的 shell 指令插入為例:

EXEC usp_DoFileCopy @filename = ' & del /S /Q //dest/share/ & '
      

使用這個

@filename

參數,将要執行的字元串為

copy //src/share/ & del /S /Q //dest/share/ & //dest/share

。和号 (

&

) 被作業系統指令解釋器處理為指令分隔符,是以該字元串将被 CMD.EXE 視為三個互不相關的指令。其中第二個指令 (

del /S /Q //dest/share/

) 将嘗試删除 //dest/share 中的所有檔案。通過利用該存儲過程中某個 shell 指令插入漏洞,使用者仍然可以執行任意作業系統指令。針對此類攻擊進行防禦的一種方法是将指令字元串打包在一個 T-SQL 函數中,如下所示。這個使用者定義的函數會添加 shell 轉義符 (

^

),對出現的任何 & 字元或其他具有特殊意義的字元進行轉義。

-- Function: fn_escapecmdshellstring
-- Description: Returns an escaped version of a given string
--              with carets ('^') added in front of all the special 
--              command shell symbols. 
-- Parameter: @command_string nvarchar(4000)
--
CREATE FUNCTION dbo.fn_escapecmdshellstring (
  @command_string nvarchar(4000)) RETURNS nvarchar(4000) AS
BEGIN
  DECLARE @escaped_command_string nvarchar(4000),
    @curr_char nvarchar(1),
    @curr_char_index int    
  SELECT @escaped_command_string = N'',
    @curr_char = N'', 
    @curr_char_index = 1
  WHILE @curr_char_index <= LEN (@command_string)
  BEGIN
    SELECT @curr_char = SUBSTRING (@command_string, @curr_char_index, 1) 
    IF @curr_char IN ('%', '<', '>', '|', '&', '(', ')', '^', '"')
    BEGIN
      SELECT @escaped_command_string = @escaped_command_string + N'^'
    END
    SELECT @escaped_command_string = @escaped_command_string + @curr_char
    SELECT @curr_char_index = @curr_char_index + 1 
  END
  RETURN @escaped_command_string
END
      

下面是消除了指令 shell 插入漏洞之後的存儲過程:

CREATE PROCEDURE usp_DoFileCopy @filename varchar(255) AS
DECLARE @cmd varchar (8000)
SET @cmd = 'copy //src/share/' 
  + dbo.fn_escapecmdshellstring (@filename) 
  + ' //dest/share/'
EXEC master.dbo.xp_cmdshell @cmd
      

第三個具有獨特安全考慮事項的 T-SQL 指令集為那些允許執行動态建構的查詢的指令:EXEC() 和 sp_executesql。SQL 注入式攻擊的風險并不是避免動态 SQL 的唯一理由。任何通過這些指令動态執行的查詢都将在目前使用者的安全上下文中運作,而不是在該存儲過程所有者的上下文中運作。這就意味着,使用動态 SQL 可能會強制您授予使用者直接通路基表的權限。以下面的存儲過程為例:

CREATE PROC dbo.usp_RetrieveMyUserInfo AS 
SELECT * FROM UserInfo WHERE UserName = USER_NAME()
      

此過程會限制目前使用者,使其無法檢視其他任何使用者的資料。但是,如果此過程中的 SELECT 語句是通過動态 EXEC() 或通過 sp_executesql 執行的,您則必須授予使用者對 UserInfo 表的直接 SELECT 權限,這是因為這個動态執行的查詢是在目前使用者的安全上下文中運作的。如果使用者能夠直接登入伺服器,他們則可以使用此權限跳過該存儲過程提供的行級安全,檢視所有使用者的資料。

編寫安全的 Transact-SQL

傳回頁首

小結

總而言之,下面的建議将有助于您開發在 SQL Server 中安全運作的 T-SQL 代碼:

保護您的開發 SQL Server 的安全,就好像它是一個生産伺服器一樣。這樣有助于確定您開發安全的代碼,還可以幫助您定義應用程式正常運作所需的最低權限集。
進行 T-SQL 開發和測試時請使用具有最低權限的 SQL Server 帳戶。不要使用 sysadmin 或 dbo 帳戶。
對于允許 T-SQL 執行任意外部代碼的存儲過程,要非常注意,如 sp_OACreate 和 xp_cmdshell。如果必須使用這些擴充,則一定要考慮它們獨特的安全隐患。
請遵照保護 T-SQL 開發的最佳方法,其中包括:将使用者提供的資料以顯式參數進行傳遞、編寫可避免 SQL 注入式攻擊的代碼、避免使用不必要的動态 SQL、授予通路存儲過程的權限而不要授予直接通路基表的權限。
安全的 T-SQL 才能構成安全的應用程式。利用下面的資源可以確定您的伺服器進行了安全配置,并確定您擁有一個安全的資料庫用戶端應用程式。
編寫安全的 Transact-SQL

傳回頁首

參考資料

SQL Server Security Resource Page

Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication

SQL Server 2000 SP3 Security Features and Best Practices: Secure Multi-tier Deployment

SQL Server for Developers

Bart Duncan

在過去 6 年中一直從事着 SQL Server 産品支援的工作,最近晉升為 Escalation Engineer(進階工程師)。他與妻子居住在德克薩斯州的達拉斯。