在開發人員承受越來越多的安全責任之時,許多開發人員瞭解到的第一個Web應用安全性漏洞,是一個被稱為“SQL注入”的極危險的命令注入形式。命令注入的原始的形式本是指這樣一種漏洞:攻擊者通過提供一個正常使用者意料之外的輸入,改變你的Web應用程式的運行方式,從而允許攻擊者運行伺服器上的非授權的命令。無疑,SQL注入式攻擊是很常見的、被廣泛使用的攻擊形式。幸運的是,一旦我們理解了這個問題,就可以很容易地防止SQL注入式攻擊。更妙的是,現在微軟的資料訪問技術向.net開發人員提供了徹底地清除SQL注入漏洞的機會,當然前提是能夠正確使用。這種技術稱為“語言級整合查詢”(Language Integrated Query (LINQ)),並隨Visual Studio "Orcas" 和 .NET Framework 3.5一起發布。本文將討論如何通過LINQ強化Web應用程式的資料存取碼,從而解決通過SQL注入進行攻擊的問題。
SQL注入是一種Web應用程式的安全性漏洞,通過它攻擊者可以將惡意資料提交給應用程式,欺騙應用程式在伺服器上執行惡意的SQL命令。理論上講,這種攻擊是容易預防的,不過由於其允許攻擊者直接運行針對使用者關鍵資料的資料庫命令,從而成為一種常見的、危害性大的攻擊形式。在非常極端的情況下,攻擊者不但能夠自由地控制使用者的資料,還可以刪除資料表和資料庫,甚至控制整個資料庫伺服器。
如果這種攻擊容易預防,那麼為什麼還如此危險呢?首先,由於眾所周知的經濟上的原因,你的應用程式資料庫是非常誘人的,可以引起攻擊者的極大注意。如果SQL注入漏洞在Web應用程式中可能存在著,那麼對於一個攻擊者來說是很容易檢測到的,然後就可以利用它。很顯然,即使SQL注入錯誤並不是開發人員最經常犯的錯誤,它們也很容易被發現和利用。
對SQL注入的剖析
這裡我們給出一個SQL注入的例子來說明兩個問題,一是SQL注入這種錯誤是很容易犯的,二是只要進行嚴格的程式設計,這種錯誤是很容易預防的。
這個樣本用的Web應用程式套件組合含一個名為SQLInjection.aspx簡單的客戶搜尋網頁面,這個頁面易於受到SQL注入攻擊。此頁麵包含一個CompanyName的輸入伺服器控制項,還有一個資料表格控制項,用於顯示從微軟的樣本資料庫Northwind的搜尋結果(這個資料庫可從SQL Server 2005中找到)。在搜尋期間執行的這個查詢包含一個應用程式設計中很普通的錯誤:它動態地從使用者提供的輸入中產生查詢。這是Web應用程式資料訪問中的一個主要的錯誤,因為這樣實際上潛在地相信了使用者輸入,並直接將其發送給你的伺服器。在從“搜尋”的單擊事件啟動時,這個查詢看起來是這個樣子:
protected void btnSearch_Click(object sender, EventArgs e) { String cmd = "SELECT [CustomerID], [CompanyName], [ContactName] FROM [Customers] WHERE CompanyName ='" + txtCompanyName.Text + "'"; SqlDataSource1.SelectCommand = cmd; GridView1.Visible = true; } |
在這種情況下,如果一個使用者輸入“Ernst Handel”作為公司名,並單擊搜尋按鈕,作為響應螢幕會向使用者顯示那個公司的記錄,這正是我們所期望的理想情況。不過一個攻擊者可以輕易地操縱這個動態查詢。例如,攻擊者通過插入一個UNION子句,並用一個注釋符號終止這個語句的剩餘部分。換句話說,攻擊者不是輸入“Ernst Handel”,而是輸入如下的內容:
Ernst Handel' UNION SELECT CustomerID, ShipName, ShipAddress FROM ORDERS-- |
其結果是這個SQL語句在伺服器端執行,由於添加了這個惡意的請求。它會將這個動態SQL查詢轉換為下面的樣子:
SELECT [CustomerID], [CompanyName], [ContactName] FROM [Customers] WHERE CompanyName ='Ernst Handel' UNION SELECT CustomerID, ShipName, ShipAddress FROM ORDERS--' |
這是一個相當合法的SQL語句,它可以在應用程式資料庫上執行,返回order表中所有的客戶,這些客戶通過應用程式已經處理了定單。
典型的SQL防護
可以看出,在你的應用程式中創造並利用一個SQL注入漏洞是多麼容易。幸運的是,如前所述,只需要採取幾項簡單的對策通常就可以預防SQL注入攻擊。最常用的、成本效率最高的預防SQL注入攻擊的方法是驗證應用程式中所有的最終用於資料訪問的資料輸入。使用者發出的任何輸入,不管是通過Web應用程式輸入的或者是常駐於資料存放區裝置的,都要在伺服器處理你的資料訪問命令之前在伺服器端驗證其類型、長度、格式和範圍。不幸的是,基於代碼的對策並不十分安全,而且有可能失敗,特別是當發生如下情況時:
驗證程式設計不當
驗證僅在客戶層面執行
在應用程式中,驗證時遺漏了欄位(有時即使是一個欄位)。
防止SQL注入的另外一層涉及正確地確定應用程式中所有SQL查詢的參數,不管是在動態SQL語句中還是在預存程序中。例如,如果代碼像下面這樣構建查詢,就比較安全:
SELECT [CustomerID], [CompanyName], [ContactName] FROM [Customers] WHERE CompanyName = @CompanyName |
當作為SQL語句的一部分執行時,參數化查詢將輸入作為一個字面值,因此伺服器就可能將帶參數的輸入作為可執行代碼。即使你使用了預存程序,你仍然必須採取另外一步來確定輸入的參數,因為預存程序並不對嵌入式查詢中的SQL注入提供保護。
即使採取這上述的簡單修正措施,SQL注入對許多公司來說仍然是一個大問題。對Team Dev的挑戰是要教育每一個開發人員謹慎對待這些類型的漏洞,採取有目的的和有效安全標準來防止攻擊,增強標準和操作安全的評估, 確認無任何疏漏。這樣就會需要引入許多變數去保證應用程式安全,因此如果你選擇一項能夠使SQL注入式攻擊成為不可能的資料訪問技術,你的效率將會更高。這正是LINQ發揮作用之所在。
LINQ概述
LINQ增加了用任何類型的資料存放區進行查詢和更新資料的標準模式,無論是SQL資料庫還是XML文檔,還是.NET對象都是這樣。在構建資料庫驅動的應用程式時,LINQ能夠使開發人員像管理C#或者VB中的對象那樣管理相關資料,這稱為“LINQ to SQL”,被看作是ADO.NET資料技術系統的一部分。在最初以CTP形式引入時,LINQ to SQL被認為是DLINQ。
LINQ to SQL使得你將應用程式中的資料作為你所使用的程式設計語言中的本機物件,簡化相關資料管理和資料庫連接的複雜性。事實上,你可以通過LINQ顯示和操作資料庫的資料,而無需你編寫任何SQL語句。在運行時刻,LINQ to SQL將嵌入或“整合”到你的代碼中的查詢轉換成SQL,並在資料庫系統上執行它們。LINQ to SQL以對象的形式將查詢結果返回到應用程式中,完全轉移了你與資料庫及SQL的互動形式。沒有什麼清除Web應用程式中的SQL注入的方法能夠比從應用程式中清除SQL更快。停靠LINQ to SQL,你就可以實現。
保障LINQ資料庫存取的安全
LINQ to SQL在專用於資料存取時,清除了SQL注入存在於你的應用程式中的可能性,原因很簡單:LINQ代表你執行的每次查詢都加上了具體的參數。在LINQ從你植入的查詢語句中構建SQL查詢時,無論源自何處,提交給查詢的任何輸入都被當作字面值。而且,通過IntelliSense和編譯時間的語法檢查,LINQ與Visual Studio Orcas的整合可以協助開發人員構建合法的查詢。編譯器可以捕捉大量的對查詢的錯誤使用,這些錯誤使用可以將功能上的缺陷或其它類型的漏洞帶入到你的應用程式中。與此不同的是,在你獲知它正確與否之前,你編寫的SQL語句只在運行時刻在資料庫系統上解析。針對LINQ to SQL的唯一攻擊媒介是攻擊者欺騙LINQ形成非法的或無意識的SQL。幸運的是,語言和編譯器就是設計來保護這個方面的。
在清楚了上述的基本思想後,下面我們就展示應該如何運用LINQ to SQL防護SQL注入式攻擊,並具體討論一個客戶搜尋的例子。第一步是建立資料庫中有關資料的物件模型。Visual Studio Orcas包含一個新的物件關聯式設計工具(Object Relational Designer),這個設計器使你能夠產生一個完全的物件模型。為了為我們的Northwind Customers表構建一個物件模型,你通過選擇“增加新項目…”並選擇“LINQ to SQL File”模板(這個模板是在物件關聯式設計工具中開啟的),在應用程式中建立一個LINQ to SQL的資料庫。為了給 Customers表自動構建完全的物件模型,在伺服器總管 (Server Explorer)中選擇這個表,並將它拖到物件關聯式設計工具的設計層面上。在這個例子中,物件關聯式設計工具增加了一個名為Customers.designer.cs的檔案,這個檔案以代碼的形式定義了你將要使用的類,而不是編寫代碼直接與資料庫進行互動。
在為Customers表中的資料定義了物件模型的類之後中,你可以為客戶的資料搜尋網頁面直接以代碼的形式查詢資料。LINQ-powered 頁面(LINQtoSQL.aspx.cs)的Page_Load方法,具體展現了由物件關聯式設計工具建立的CustomersDataContext類,重新使用了前面在SQLInjection.aspx頁面中使用的連接字串。下面的LINQ查詢重新使用了與where子句匹配的Customer對象的集合。
protected void Page_Load(object sender, EventArgs e) { string connectionString = ConfigurationManager.ConnectionStrings ["northwndConnectionString1"].ConnectionString; CustomersDataContext db = new CustomersDataContext(connectionString); GridView1.DataSource = from customer in db.Customers where customer.CompanyName == txtCompanyName.Text orderby customer.CompanyName select customer; GridView1.DataBind(); } |
在使用了LINQ to SQL之後,如果我們將“Ernst Handel”作為搜尋值,由LINQ在運行時產生並在伺服器上執行的SQL語句看起來將會是如下這個樣子:
SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] FROM [dbo].[Customers] AS [t0] WHERE [t0].[CompanyName] = @p0 ORDER BY [t0].[CompanyName]} |
可以看出,WHERE子句自動被加上了參數,因此,用傳統的SQL注入式攻擊是無法造成破壞的。不管使用者將什麼值作為輸入提交給搜尋網頁面,這個查詢是安全的,它不允許使用者的輸入執行伺服器上的命令。如果你輸入了前面例子中用來實施SQL注入攻擊的字串,查詢並不會返回任何資訊。事實上,一個使用者用這個查詢可以進行的最大的破壞是執行一次強力攻擊(或稱蠻力攻擊(Brute force attack)),主要通過使用搜尋功能窮舉Customers表中所有公司的記錄,其使用的方法是猜測每一個可能的值。不過,即使這樣也只提供了那個頁面上所暴露的Customers表中的值,並不會給攻擊者注入命令的機會,這裡的命令指的是訪問資料庫中額外的資料表的命令。
LINQ與安全
正如前面的例子所顯示的那樣,在Web應用程式中引入SQL注入漏洞是很容易的,不過採用適當的方法也容易修正這些漏洞。但是,沒有什麼方法天生就能防止開發人員犯這些簡單的但卻是危險的錯誤。然而,微軟的LINQ to SQL技術通過讓開發人員直接與物件模型互動而不是直接與資料庫互動,消除了來自資料庫應用程式的SQL注入攻擊的可能性。內建於c#和Visual Basic的 LINQ基礎結構關注正確地表述合法而安全的SQL語句,可以防止SQL注入攻擊,並使開發人員專註於對他們來說最自然的程式設計語言。不管你是將LINQ to SQL用作新的.NET應用程式開發的一部分,還是對它進行花樣翻新,用於現有的實際應用程式的資料訪問,你都是作了一個構建更安全的應用程式的選擇。