想加來加去,又不要 SQL Injection 嗎?LINQ 做給你
複合查詢常讓設計師不得不採用組裝式 SQL 語句手法,而其結果也常因為使用 Parameter 會導致程式碼變複雜,而循傳統手法完成該功能,最後留下 SQL Injection 的漏洞。那使用 LINQ To SQL/LINQ To Entities 來改寫的話,真的可以避免 SQL Injection 及簡化程式碼嗎?讓實例說話吧,我們以上例的複合查詢為例,改寫成 LINQ To SQL 版本之程式碼如下 :
protected void Button1_Click(object sender, EventArgs e)
{
NorthwindDataContext context = new NorthwindDataContext();
var baseData = from s1 in context.Customers select s1;
if(TextBox1.Text.Length > 0)
baseData = from s1 in baseData where
s1.CompanyName.Contains(TextBox1.Text) select s1;
if (TextBox2.Text.Length > 0)
baseData = from s1 in baseData where
s1.CustomerID.Contains(TextBox2.Text) select s1;
if (TextBox3.Text.Length > 0)
baseData = from s1 in baseData where
s1.ContactTitle.Contains(TextBox3.Text) select s1;
GridView1.DataSource = baseData;
GridView1.DataBind();
}
此例中,我利用了 LINQ To SQL/LINQ To Entities 只在列舉資料集元素前,才會開始組裝 SQL 語句的共通行為,以疊加式查詢的方式來完成複合查詢的工作,請特別注意,這段程式碼只會送出一段 SQL 語句,不是四個,透過 SQL Profiler 可以證明這點:
exec sp_executesql N'SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address],
[t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax], [t0].[NOTES], [t0].[TEST_ID]
FROM [dbo].[Customers] AS [t0]
WHERE ([t0].[CustomerID] LIKE @p0) AND ([t0].[CompanyName] LIKE @p1)',N'@p0 nvarchar(4),@p1 nvarchar(3)',@p0=N'%FR%',@p1=N'%V%'
讓事實說話:LINQ To SQL VS SQL Injection
我說你不一定信,你可以下載範例,然後對本文所提及的兩個 LINQ To SQL 網頁進行 SQL Injection 的測試,圖 012 是以【' OR 1=1 --】手法來測試登入機制:
圖 12:

附件:
您所在的用户组无法下载或查看附件圖 013 是複合查詢的測試:
圖 13:

附件:
您所在的用户组无法下载或查看附件改用 LINQ To Entities 也是一樣的結果。
幫幫忙,別自己開洞,ExecuteQuery 及 ExecuteCommand
那使用 LINQ To SQL/LINQ To Entities 就能保證不被 SQL Injection 所擾了嗎?那可不一定,因為設計師還是常常會貪一時方便,開啟 SQL Injection 的大門。基於彈性,LINQ To SQL 及 LINQ To Entities 都支援直接將 SQL 語句送往資料庫執行的機制,LINQ To SQL 的 ExecuteQuery 就是一個例子:
protected void Button1_Click(object sender, EventArgs e)
{
NorthwindDataContext context = new NorthwindDataContext();
string str = "SELECT * FROM USERS WHERE USER_ID = '" + TextBox1.Text +
"' AND PASSWORD = '" + TextBox2.Text + "'";
int ret = context.ExecuteQuery<USERS>(str).Count();
if (ret > 0)
Label1.Text = "歡迎你";
else
Label1.Text = "登入失敗";
}
所以,要防堵 SQL Injection,使用 LINQ To SQL/LINQ To Entities 是最具成效及具經濟效益的,不過前提是設計師得幫幫忙,別放著有新的方便且有效率的技巧不學,故意去當打洞工人。
迷思:Stored Procedure 是安全的,Parameter 是無敵的?
的確,是我告訴你,使用 Parameter 是防堵 SQL Injection 最快、最有效、最完整的手法,但是前題是不能與 Stored Procedure 扯上關係!基於網路上的片段資料,設計師總覺得,如果我使用了 Stored Procedure,並使用 Parameter 來傳遞參數,那就對 SQL Injection 完全免疫了!所有軟體專案主導者都同意,當要求不明確時,結果也會不明確,基於防堵 SQL Injection 的大前題下,許多專案主導者都會要求設計師不要在程式中組裝 SQL 語句,而改用 Stored Procedure,但!他們卻高估了程式設計師的的理解力,天才工程師以 Stored Procedure 來處理複合查詢,寫下程式碼如下:
protected void Button1_Click(object sender, EventArgs e)
{
SqlConnection conn = new SqlConnection(
"Data Source=JEFFRAY;Initial Catalog=Northwind;Integrated Security=True");
using (conn)
{
SqlCommand cmd = new SqlCommand("QueryCustomers", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@CompanyName",
TextBox1.Text.Length == 0 ? "" : TextBox1.Text);
cmd.Parameters.AddWithValue("@CustomerID",
TextBox2.Text.Length == 0 ? "" : TextBox2.Text);
cmd.Parameters.AddWithValue("@ContactTitle",
TextBox3.Text.Length == 0 ? "" : TextBox3.Text);
conn.Open();
GridView1.DataSource = cmd.ExecuteReader(CommandBehavior.CloseConnection);
GridView1.DataBind();
}
}
正如你所見,這是一段看不出問題在那的程式碼,直到看到了 QueryCustomers 的預存程序,你會吐血:
CREATE PROCEDURE dbo.QueryCustomers
(
@CompanyName nvarchar(30),
@CustomerID nvarchar(12),
@ContactTitle nvarchar(30)
)
AS
DECLARE @STR nvarchar(255)
DECLARE @WK nvarchar(255)
SET @STR = 'SELECT * FROM Customers'
SET @WK = ''
IF NOT @CompanyName IS NULL
SET @WK = @WK + ' CompanyName LIKE [email=]''%'+@CompanyName+'%''[/email] AND '
IF NOT @CustomerID IS NULL
SET @WK = @WK + ' CustomerID LIKE [email=]''%'+@CustomerID+'%''[/email] AND '
IF NOT @ContactTitle IS NULL
SET @WK = @WK + ' ContactTitle LIKE [email=]''%'+@ContactTitle+'%''[/email] AND '
IF LEN(@STR) > 0
BEGIN
SET @STR = @STR+' WHERE '+SUBSTRING(@WK,0,LEN(@WK)-3)
exec sp_executesql @STR
End
ELSE
exec sp_executesql @STR
結果就是:
圖 14:

附件:
您所在的用户组无法下载或查看附件一旦變成這樣,就算是 LINQ To SQL/LINQ To Entities 也無法救你脫離 SQL Injection 的威脅!所以,你應該明確的告訴設計師,SQL Injection 是由組裝式 SQL 語句而引發的,得注意任何有【組裝式 SQL】發生的程式碼,這當然也包含了 Stored Procedure。此例正確的 Stored Procedure 寫法如下:
CREATE PROCEDURE dbo.SafeQueryCustomers
(
@CompanyName nvarchar(30),
@CustomerID nvarchar(12),
@ContactTitle nvarchar(30)
)
AS
DECLARE @STR nvarchar(255)
DECLARE @WK nvarchar(255)
SET @STR = 'SELECT * FROM Customers'
SET @WK = ''
IF NOT @CompanyName IS NULL
BEGIN
SET @WK = @WK + ' CompanyName LIKE @pCompanyName AND '
SET @CompanyName = '%'
+@CompanyName + '%'
END
IF NOT @CustomerID IS NULL
BEGIN
SET @WK = @WK + ' CustomerID LIKE @pCustomerID AND '
SET @CustomerID = '%'
+@CustomerID + '%'
END
IF NOT @ContactTitle IS NULL
BEGIN
SET @WK = @WK + ' ContactTitle LIKE @pContactTitle AND '
SET @ContactTitle = '%'
+@ContactTitle + '%'
END
IF LEN(@STR) > 0
BEGIN
SET @STR = @STR+' WHERE '+SUBSTRING(@WK,0,LEN(@WK)-3)
exec sp_executesql @STR,
N'@pCompanyName nvarchar(30),@pCustomerID nvarchar(12),@pContactTitle nvarchar(30)',
@pCompanyName=@CompanyName,@pCustomerID=@CustomerID,@pContactTitle=@ContactTitle
End
ELSE
exec sp_executesql @STR
使用參數是防堵 SQL Injection 的不二法門,就算是在 Stored Procedure 中亦是如此。