ASP.NET Core中的OWASP Top 10 十大風險-SQL注入


不定時更新翻譯系列,此系列更新毫無時間規律,文筆菜翻譯菜求各位看官老爺們輕噴,如覺得我翻譯有問題請挪步原博客地址

本博文翻譯自:
https://dotnetcoretutorials.com/2017/10/11/owasp-top-10-asp-net-core-sql-injection/

OWASP或者說是開放Web應用程序安全項目,這是一個非營利性的組織,其目的是促進安全的web應用程序的開發和設計。當他們在世界各地舉辦不同的研討會和活動時,你可能聽說過他們,因為“OWASP Top Ten”項目。每隔幾年,OWASP就會發布十大最重要的web安全風險列表。當然,這並不意味着您需要檢查這10個項目,您的網站現在是安全的,但它絕對涵蓋了您的網站上最常見的攻擊媒介的基礎。

當我第一次開始編程並聽說OWASP的時候,對我來說最困難的事情就是把它變成實際的東西,例如,當有人開始談論“CSRF”時,我想知道在.NET中,它是什么樣子,它保護自己的基本原則是什么,.NET的系統中有什么(如果有的話)?這篇10部分的文章系列將嘗試在ASP.net Core的領域中回答這些問題。您絕對不會突然對所有網絡攻擊都可以免疫,但是希望可以幫助您從ASP.net Core角度了解OWASP十大安全風險。

另外要注意的是,我將基於OWASP 2017 候選發布版的前10名。這些尚未被接受為2017年的官方十強,如果他們改變,我一定會添加額外的文章來涵蓋一切。

所以不用多說,我們開始吧!我們列出的第一個項目是SQL注入。

SQL注入如何工作?

SQL注入可以通過修改一個已知的輸入參數來修改SQL語句,而這個SQL語句的執行方式與我們預期的非常不同。這聽起來像是一大堆的胡言亂語,我們來舉個例子吧。

如果您想要在您的機器上運行整個工作代碼項目,您可以從Github那里獲取任何東西。您仍然可以在沒有代碼的情況下繼續進行下去,因為我將在整個過程中發布代碼示例和屏幕截圖。

首先讓我們創建一個包含兩個表的數據庫。第一個表名為“NonsensitiveDataTable”,它包含一些我們不敏感可以向用戶分享的數據。第二個表名為“SensitiveDataTable”它包含用戶信用卡和社會保障號碼。下面是SQL Server中的表。

現在我們假設我們有一個API方法。這個方法的作用就是讓我們通過id來向NonSensitiveDataTable表請求數據。

[HttpGet]
[Route("nonsensitive")]
public string GetNonSensitiveDataById()
{
	using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString")))
	{
		connection.Open();
		SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Id = {Request.Query["id"]}", connection);
		using (var reader = command.ExecuteReader())
		{
			if (reader.Read())
			{
				string returnString = string.Empty;
				returnString += $"Name : {reader["Name"]}. ";
				returnString += $"Description : {reader["Description"]}";
				return returnString;
			}
			else
			{
				return string.Empty;
			}
		}
	}
}

這是一個相當極端的例子,編碼相當差,但它說明了這一點。我們通過調用這個方法獲取數據,似乎沒問題。

但是只要看代碼,我們就可以看到有疑問的東西。值得注意的是:

SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Id = {Request.Query["id"]}", connection);

我們正在將Id參數直接傳遞到SQL語句中,難道我們可以在那里輸入任何東西?那么我們知道當我們要求2的ID時,“Mark Twain”的記錄就會被返回。但讓我們來試試這個:

哇…。那么為什么我們添加測試“OR id = 1”會突然改變一切。答案在於我們上面的代碼。當我們簡單地將“2”的id傳遞給我們的代碼時,最終執行這個語句。

SELECT * FROM NonSensitiveDataTable WHERE Id = 2

但是當我們通過OR語句時,它實際上會像這樣讀取:

SELECT * FROM NonSensitiveDataTable WHERE Id = 2 OR Id = 1

所以在這里我們已經設法通過更改查詢參數來修改執行的SQL語句。但沒有什么大不了的對嗎?用戶將收到錯誤的數據,那就是結束了。那么,因為我們知道我們可以修改SQL語句。讓我們嘗試一些時髦的東西。讓我們試試這個網址:

呃哦,看起來不好看 運行的查詢結果如下所示:

SELECT * FROM NonSensitiveDataTable WHERE Id = 999 UNION SELECT * FROM SensitiveDataTable

我們在NonSensitiveDataTable表中查詢ID為999的記錄,並且和SensitiveDataTable表的數據關聯起來,因為沒有999的ID的記錄我們會立即跳過,不過我們發現我們的敏感信息已經被泄露。

如果這是一個電子商務網站,他們將會有一個“客戶”表和一個“訂單”表等等。那么這個時候我們的信息已經被泄露,SQL注入並不像復制粘貼URL那樣簡單,但是有了它們我們好像有了打開王國大門的鑰匙。

好的,讓我們再試一次。讓我們嘗試以下查詢。它會告訴我們數據庫中的表名。

SELECT * FROM NonSensitiveDataTable WHERE Id = 999 UNION SELECT 1 as ID, Name as NAME, Name as Description FROM sys.Tables WHERE name <> 'NonSensitiveDataTable'

您經常會在web上發現SQL注入的“規則”,其中有大量的SQL命令可以嘗試。你有它,少一點猜測。我們可以在這里坐上幾天,把命令扔到這里。在SQL注入的情況下,對方的數據庫里有什么簡直一目了然

我將留給您一個最后的查詢,您可以嘗試並運行。假設你在試圖獲取數據時感到沮喪,你只是想毀掉某人的一天。想象一下運行以下命令:

SELECT * FROM NonSensitiveDataTable WHERE Id = 999; DROP TABLE SensitiveDataTable

這里我們已經放棄了,我們只需要刪除一個表。也許我們甚至可以獲取所有的數據,如果我們只是想讓DBA的生活變得痛苦。這肯定是一種方法!

現在我們大致知道了SQL注入是什么,讓我們繼續保護自己!

清理您的輸入

您經常會發現,關於任何描述SQL注入攻擊的文章的第一條評論是“始終對您的輸入進行清理”(緊接着“在您的查詢中使用參數代替更好!” )- 但這些稍后再說。對您的輸入進行清理可能意味着幾件事情,讓我們來看看。

轉換為非字符串類型

在我們的示例代碼中,因為我們知道我們的Id應該是一個int。我們可以這樣:

int id = int.Parse(Request.Query["id"]);
SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Id = {id}", connection);

完美!現在如果有人試圖在我們的查詢字符串中添加進一步的信息,它不會被解析為int,我們就會避免注入!

在ASP.net Cor中一個有個更簡單的例子只要允許我們的路由為我們工作,我們就可以達到上面的目的。

[HttpGet]
[Route("nonsensitiveroute/{id}")]
public string GetNonSensitiveDataByIdWithRoute(int id)

現在我們甚至不需要做任何手工解析。ASP.net Core路由將為我們處理它!

當然,如果您的查詢實際上是針對整數列的,那么這能起作用。如果我們真的需要傳遞字符串,那又會怎樣呢?

白名單/黑名單/字符替換

老實說,我不想深入到這個問題,因為我認為這是一個很容易出錯的處理SQL注入的方法。但是,如果必須,您可以在傳遞到SQL之前,對已知正確值的白名單或黑名單運行字符串。或者您可以替換某些字符的所有實例(引號,分號等)。但是所有這些都依賴於你在攻擊之前的一步。你也會把你對SQL語言的復雜性的知識與別人混淆。

雖然像PHP這樣的語言具有“轉義”SQL字符串的內置函數,但.NET core不支持。此外,簡單地轉義引號也不能從根本解決SQL注入問題.。

始終對輸入進行清理需要您“記住”來做到這一點。這並不是一種可以遵循的模式。這很有效。但這不是理想的解決方案。

參數化查詢

在您的SQL查詢中使用參數將成為您抵御SQL注入攻擊的首選。即使您正在使用某種類型的ORM(在下面討論了一點),但在幕后,他們可能會使用參數化的查詢。

那么我們如何在我們的示例項目中使用它們呢?我們將會稍微改變一下,而不是在我們的NonSensitiveDataTable表中直接使用“name”字段中進行查詢。這樣做只是因為它給了我們更多的靈活性。

[HttpGet]
[Route("nonsensitivewithparam")]
public string GetNonSensitiveDataByNameWithParam()
{
	using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString")))
	{
		connection.Open();
		SqlCommand command = new SqlCommand($"SELECT * FROM NonSensitiveDataTable WHERE Name = @name", connection);
		command.Parameters.AddWithValue("@name", Request.Query["name"].ToString());
		using (var reader = command.ExecuteReader())
		{
			if (reader.Read())
			{
				string returnString = string.Empty;
				returnString += $"Name : {reader["Name"]}. ";
				returnString += $"Description : {reader["Description"]}";
				return returnString;
			}
			else
			{
				return string.Empty;
			}
		}
	}
}

你可以看到我們在我們的查詢中添加了哪些參數嗎?那么這是如何工作的?那么在SQL Profiler的幫助下,正在發送的實際SQL如下所示:

exec sp_executesql N'SELECT * FROM NonSensitiveDataTable WHERE Name = @name'
,N'@name nvarchar(12)'
,@name=N'Bart Simpson'

哇...這是一個很大的不同。那么這里發生了什么?我們正在發送查詢,但是說“以后,我將告訴您查詢什么數據”。我們傳遞想要查詢的確切值,而不是實際的SELECT語句。通過這種方式,我們的原始查詢保持不變,並且不影響用戶對查詢字符串的類型。

使用參數的最好的方法是它們成為一個簡單的模式。你不必“記住”來逃避某些字符串,或者“記住”使用白名單。

存儲過程

SQL存儲過程是避免SQL注入攻擊的另一個好方法。盡管現在存儲過程似乎對開發人員無所適從,但是它們與參數化查詢類似,因為您將通過SELECT語句和查詢數據在兩個不同的“批次”中傳遞。我們快速創建一個存儲過程來嘗試(如果您使用GIT中的示例項目,則您的數據庫中已經有該SP,並且不需要運行以下操作)。

CREATE PROCEDURE SP_GetNonSensitiveDataByName
	@Name nvarchar(MAX)
AS
BEGIN
	SET NOCOUNT ON;
	SELECT * FROM NonSensitiveDataTable WHERE Name = @Name
END

讓我們創建一個API方法,運行它。

[HttpGet]
[Route("nonsensitivewithsp")]
public string GetNonSensitiveDataByNameWithSP()
{
	using (SqlConnection connection = new SqlConnection(_configuration.GetValue<string>("ConnectionString")))
	{
		connection.Open();
		SqlCommand command = new SqlCommand("SP_GetNonSensitiveDataByName", connection);
		command.CommandType = System.Data.CommandType.StoredProcedure;
		command.Parameters.AddWithValue("@name", Request.Query["name"].ToString());
		using (var reader = command.ExecuteReader())
		{
			if (reader.Read())
			{
				string returnString = string.Empty;
				returnString += $"Name : {reader["Name"]}. ";
				returnString += $"Description : {reader["Description"]}";
				return returnString;
			}
			else
			{
				return string.Empty;
			}
		}
	}
}

當我們使用SQL Profiler運行這個方法時,我們可以看到以下運行。

exec SP_GetNonSensitiveDataByName @name=N'bart simpson'

如您所見,它與上面的參數化查詢非常相似,我們的查詢的實際細節是單獨發送的。雖然現在很少看到一個圍繞存儲過程構建的整個項目,但它們可以保護您免受SQL注入攻擊。

使用ORM

現在下一部分是減輕SQL注入攻擊的有趣部分。因為在某種意義上,它成為一個一站式的保護。但是我把它放在最后的原因是因為我認為最好能理解在保護你的ORM的基礎上發生了什么。

如果您使用的實體框架。當您運行Linq查詢以獲取數據時,任何linq“Where”語句將被打包為參數查詢並發送到SQL Server。這意味着你真的需要走出自己的方式來打開自己,直到SQL注入,但這不是不可能的!幾乎所有的ORM都可以發送原始的SQL查詢,如果你真的想。看看Microsoft的這篇文章通過Entity Framework Core發送原始SQL。至少,使用ORM可以使SQL注入成為“默認”,而不是從外部添加一些東西。

賦予最低的權限

還記得我們的drop table命令嗎?如果你忘了,下面是:

SELECT * FROM NonSensitiveDataTable WHERE Id = 999; DROP TABLE SensitiveDataTable

我們的網站真的需要刪除一個表嗎?不太可能然而,在我們的場景中,我們已經把王國大門的鑰匙交給了外人讓他去做任何它喜歡做的事。授予細粒度權限或最低權限級別是我們“停止”SQL注入攻擊的最后一步。我把“停止”放在引號中,因為在現實中我們仍然容易受到敏感數據泄露的攻擊,但是我們的攻擊者至少不能刪除表(但是它們仍然可以運行delete命令!)。

大多數SQL系統(MYSQL,Postgres,MSSQL)內置SQL角色,只允許簡單的讀取和寫入來完成,但不能修改實際的表模式。應盡可能使用這些角色來限制任何可能的攻擊。

總結

正如我們所看到的,SQL注入對於泄露敏感數據甚至是毀滅它可能是非常殘酷的。但是我們也看到ASP.net Core 已經有了我們可以保護自己的方法。在依賴於輸入解析/類型轉換的過程中,我們可以使用參數化查詢,來避免SQL注入。

歡迎轉載,轉載請注明翻譯原文出處(本文章),原文出處(原博客地址),然后謝謝觀看

如果覺得我的翻譯對您有幫助,請點擊推薦支持:)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM