在討論如何在應用程序中應用安全性之前,您應該首先了解如何保護應用程序。為了進行惡意操作,攻擊者會識別并利用應用程序的漏洞。我們經常將漏洞描述為一種弱點,它可以允許執行不需要的操作,通常帶有惡意意圖。
了解漏洞的一個很好的起點就是了解開放Web應用程序安全項目,也稱為 OWASP(https://www.owasp.org)。 在 OWASP上,您將找到應該在應用程序中避免的最常見漏洞的描述。 在進入下一章之前,讓我們花幾分鐘時間進行理論上的討論,然后從下一章開始應用 Spring Security 的概念。 在應注意的常見漏洞中,您會發現以下這些:
- Broken authentication
- Session fixation
- Cross-site scripting (XSS)
- Cross-site request forgery (CSRF)
- Injections
- Sensitive data exposure
- Lack of method access control
- Using dependencies with known vulnerabilities
這些項與應用程序級安全性相關,而且其中大多數也與使用 Spring Security 直接相關。我們將在本書中詳細討論它們與 Spring Security 的關系,以及如何保護您的應用程序不受這些問題的影響,但首先是一個概述。
身份驗證和授權中的漏洞
我們將深入討論身份驗證和授權,并且您將學習使用Spring Security實施身份驗證和授權的幾種方法。 身份驗證表示應用程序識別嘗試使用它的人的過程。 當某人或某物使用該應用程序時,我們希望找到其身份,以便授予或不授予進一步的訪問權限。 在現實世界中的應用中,您還會發現匿名訪問的情況,但在大多數情況下,只有在被識別后,用戶才能使用數據或執行特定操作。 獲得用戶身份后,我們就可以處理授權。
授權是確定經過身份驗證的調用方是否具有使用特定功能和數據的特權的過程。 例如,在移動銀行應用程序中,大多數經過身份驗證的用戶都可以轉賬,但只能從其帳戶中轉賬。
我們可以說,如果一個有不良意圖的人以某種方式獲得對不屬于他們的功能或數據的訪問權,我們的授權將被破壞。 諸如 Spring Security 之類的框架有助于降低此漏洞的可能性,但如果使用不當,仍然有可能發生這種情況。 例如,您將使用 Spring Security 為具有特定角色的經過身份驗證的個人定義對特定端點的訪問。 如果數據級別沒有限制,那么有人可能會找到一種使用屬于另一個用戶的數據的方法。
請看圖1.5。經過身份驗證的用戶可以訪問 /products/{name} 端點。在瀏覽器中,web應用程序調用這個端點來從數據庫中檢索并顯示用戶的產品。但是,如果應用程序在返回這些產品時沒有驗證這些產品屬于誰,會發生什么呢?一些用戶可以找到一種方法來獲取另一個用戶的詳細信息。這種情況只是應用程序設計一開始就應該考慮的示例之一,這樣您就可以避免這種情況。
圖 1.5
登錄的用戶可以看到他們的產品。如果應用服務器只檢查用戶是否登錄,那么用戶可以調用相同的端點來檢索其他用戶的產品。這樣,John就能夠看到屬于Bill的數據。導致此問題的問題是應用程序沒有為數據檢索對用戶進行身份驗證。
在整個系列中,我們都會提到漏洞。我們將在后面從身份驗證和授權的基本配置開始討論漏洞。然后,我們將討論漏洞如何與 Spring Security 和 Spring Data 的集成相關,以及如何設計一個應用程序來避免這些漏洞,使用 OAuth 2。
什么是會話固定?
會話固定漏洞是 web 應用程序的一個更具體、更嚴重的弱點。如果存在,則允許攻擊者通過重用以前生成的會話 ID 來模擬有效用戶。如果在身份驗證過程中,web 應用程序沒有分配唯一的會話 ID,就會發生此漏洞。這可能會導致重用現有的會話 id。利用這個漏洞包括獲取一個有效的會話 ID,并讓目標受害者的瀏覽器使用它。
根據您實施Web應用程序的方式,個人可以使用多種方法來使用此漏洞。 例如,如果應用程序在URL中提供了會話ID,則可能會誘騙受害者單擊惡意鏈接。 如果應用程序使用隱藏屬性,則攻擊者可以使受害者欺騙并使用外部表單,然后將操作發布到服務器。 如果應用程序將會話的值存儲在cookie中,則攻擊者可以注入腳本并強制受害者的瀏覽器執行該腳本。
什么是跨站點腳本(XSS)?
跨站點腳本(也稱為XSS)允許將客戶端腳本注入到服務器公開的Web服務中,從而允許其他用戶運行這些腳本。 在使用甚至存儲之前,您應該正確地“清理”請求,以避免意外執行外來腳本。 潛在影響可能與帳戶模擬(與會話固定結合)或參與分布式攻擊(如DDoS)有關。
讓我們舉個例子。用戶在web應用程序中發布消息或評論。發布消息后,該站點將顯示它,以便訪問該頁面的每個人都可以看到它。每天可能有數百人訪問這個頁面,這取決于該網站的受歡迎程度。對于我們的示例,我們將認為它是一個已知的站點,并且有大量的個人訪問它的頁面。如果該用戶發布了一個腳本,當在web頁面上找到該腳本時,瀏覽器會執行該腳本(圖1.6和1.7),該怎么辦?
圖 1.6
一個用戶在網絡論壇上發布了一個包含腳本的評論。用戶定義腳本,以便發出請求,試圖發布或從另一個應用程序(App X)獲取大量數據,該應用程序代表攻擊的受害者。如果web論壇應用程序允許跨站腳本(XSS),那么顯示帶有惡意評論的頁面的所有用戶都會收到它。
圖 1.7
用戶訪問顯示惡意腳本的頁面。 他們的瀏覽器執行腳本,然后嘗試發布或從App X獲取大量數據。
什么是跨站請求偽造(CSRF)?
跨站請求偽造 (CSRF) 漏洞在web應用程序中也很常見。CSRF 攻擊假設可以從應用程序外部提取并重用調用特定服務器上的操作的 URL (圖1.8)。如果服務器信任執行而不檢查請求的來源,則可以從任何其他地方執行。通過 CSRF,攻擊者可以通過隱藏操作,讓用戶在服務器上執行不需要的操作。通常,通過此漏洞,攻擊者的目標是更改系統中的數據的操作。
圖 1.8
跨站點請求偽造(CSRF)的步驟。 登錄他們的帳戶后,用戶訪問包含偽造代碼的頁面。 然后,惡意代碼代表毫無戒心的用戶執行操作。
緩解此漏洞的方法之一是使用令牌來標識請求或使用跨域資源共享(CORS)限制。 換句話說,請驗證請求的來源。 在后面章節中,我們將詳細介紹 Spring Security 如何處理 CSRF 和CORS 漏洞。
了解web應用程序中的注入漏洞
對系統的注入攻擊非常普遍。 在注入攻擊中,利用漏洞的攻擊者會將特定數據引入系統。 目的是損害系統,以不必要的方式更改數據或檢索攻擊者不希望訪問的數據。
注入攻擊有很多類型。 甚至我們在前面章節中提到的 XSS 都可以視為注入漏洞。 最后,注入攻擊會注入客戶端腳本,從而以某種方式損害系統。 其他示例可能是 SQL 注入,XPath 注入,OS 命令注入,LDAP 注入等等。
漏洞的注入類型很重要,利用這些漏洞的結果可能是更改、刪除或訪問系統中的數據遭到破壞。例如,如果您的應用程序在某種程度上容易受到 LDAP 注入的攻擊,那么攻擊者可以通過繞過身份驗證,從而控制系統的基本部分而獲益。XPath 或 OS 命令注入也會發生同樣的情況。
SQL 注入是最古老的、可能也是眾所周知的一種注入漏洞。如果您的應用程序有 SQL 注入漏洞,攻擊者可以嘗試更改或運行不同的 SQL 查詢,以更改、刪除或從您的系統中提取數據。在最高級的 SQL 注入攻擊中,個人可以在系統上運行 OS 命令,從而導致整個系統的危害。
處理敏感數據的泄露
即使就復雜性而言,機密數據的披露似乎是最容易理解、最不復雜的漏洞,但它仍然是最常見的錯誤之一。之所以會出現這種情況,可能是因為在網上找到的大多數教程和示例,以及說明不同概念的書籍,為了簡單起見,都直接在配置文件中定義了憑據。如果一個假設的例子最終集中在其他事情上,這就說得通了。
注意:
大多數時候,開發人員會不斷地從理論例子中學習。一般來說,為了讓讀者專注于一個特定的主題,例子會被簡化。但是這種簡化的缺點是開發人員習慣了錯誤的方法。開發人員可能會錯誤地認為他們讀到的所有東西都是好的實踐。
這個方面與 Spring Secyrity 有什么關系?我們將在本書的示例中處理憑證和私鑰。我們可能會在配置文件中使用 secrets,但是我們會為這些情況做一個說明,以提醒您應該將敏感數據存儲在金庫中。當然,對于一個已開發的系統,開發人員不允許在所有環境中看到這些敏感鍵的值。通常,至少在生產中,只有一小部分人應該被允許訪問私人數據。
通過在配置文件中設置此類值,例如 Spring Boot 項目中的 application.properties 或 application.yml 文件,您可以使那些可以看到源代碼的人訪問這些私有值。 此外,您可能還會發現自己將這些值更改的所有歷史記錄存儲在源代碼的版本管理系統中。
與敏感數據暴露相關的還有應用程序寫入控制臺或存儲在 Splunk 或 Elasticsearch 等數據庫中的日志信息。我經常看到一些日志,這些日志披露了被開發人員遺忘的敏感數據。
切勿記錄非公開信息。 公開,我的意思是任何人都可以查看或訪問該信息。 諸如私鑰或證書之類的東西不是公開的,不應與您的錯誤,警告或信息消息一起記錄。
下次您從應用程序中記錄某些內容時,請確保所記錄的內容與以下消息之一不同:
[error] The signature of the request is not correct. The correct key to be used should have been X.
[warning] Login failed for username X and password Y. User with username X as password Z.
[info] A login was performed with success by user X with password Y.
請注意服務器返回給客戶端的內容,尤其是但不限于應用程序遇到異常的情況。 通常由于缺乏時間或經驗,開發人員忘記實施所有此類情況。 這樣(通常在錯誤的請求之后發生),應用程序將返回過多的細節以暴露實現。
此應用程序行為也是由于數據泄露而引起的漏洞。 如果您的應用由于請求錯誤(例如缺少一部分)而遇到NullPointerException,則該異常不應出現在響應的正文中。 同時,HTTP狀態應該是400,而不是500。類型為4XX的HTTP狀態代碼旨在表示客戶端的問題。 最終,錯誤的請求是客戶端問題,因此應用程序應相應地代表它。 類型為5XX的HTTP狀態代碼旨在通知您服務器上存在問題。 您是否在下一個代碼段的響應中看到了問題?
{
"status": 500,
"error": "Internal Server Error",
"message": "Connection not found for IP Address 10.2.5.8/8080",
"path": "/product/add"
}
異常消息似乎正在公開一個IP地址。 攻擊者可以使用此地址來了解網絡配置,并最終找到一種方法來控制基礎結構中的VM。 當然,僅憑這些數據,就不會造成任何傷害。 但是,收集不同的公開信息并將它們匯總在一起可以提供對系統產生不利影響所需的一切。 在響應中具有異常堆棧也不是一個好選擇,例如:
at JAVA.base/java.util.concurrent.ThreadPoolExecutor
? .runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker
? .run(ThreadPoolExecutor.java:628) ~[na:na]
at org.Apache.Tomcat.util.threads.TaskThread$WrappingRunnable
? .run(TaskThread.java:61) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
at java.base/java.lang.Thread.run(Thread.java:830) ~[na:na]
這種方法還公開了應用程序的內部結構。 從異常堆棧中,您可以看到命名符號以及用于特定操作的對象及其之間的關系。 但更糟糕的是,日志有時會泄露您的應用程序使用的依賴項版本。 (您是否在前面的異常堆棧中發現了 Tomcat 核心版本?)
我們應該避免使用易受攻擊的依賴關系。 但是,如果我們發現自己錯誤地使用了易受攻擊的依賴關系,則至少我們不想指出這個錯誤。 即使該依賴關系不是易受攻擊的,也可能是因為還沒有人發現該漏洞。 上一小節中的暴露可以激發攻擊者尋找該特定版本中的漏洞,因為他們現在知道這就是您的系統使用的漏洞。 邀請他們破壞您的系統。 攻擊者經常針對系統使用最小的細節,例如:
Response A:
{
"status": 401,
"error": "Unauthorized",
"message": "Username is not correct",
"path": "/login "
}
Response B:
{
"status": 401,
"error": " Unauthorized",
"message": "Password is not correct",
"path": "/login "
}
在此示例中,響應A和B是調用同一身份驗證終結點的不同結果。 他們似乎沒有透露任何與類設計或系統基礎結構有關的信息,但這些隱藏了另一個問題。 如果消息公開了上下文信息,那么這些信息也可以隱藏漏洞。 基于提供給端點的不同輸入的不同消息可用于理解執行的上下文。 在這種情況下,這些可以用來知道用戶名何時正確但密碼錯誤。 這會使系統更容易受到暴力攻擊。 提供給客戶的回復無助于確定對特定輸入的可能猜測。 在這種情況下,它應該在兩種情況下都提供相同的消息:
{
"status": 401,
"error": " Unauthorized",
"message": "Username or password is not correct",
"path": "/login "
}
這種預防措施看起來很小,但如果不采取措施,在某些上下文中,暴露敏感數據可能成為針對您的系統使用的優秀工具。
缺少什么方法訪問控制?
即使在應用程序級別,也不能只對其中一個層應用授權。有時,必須確保完全不能調用特定用例(例如,如果當前經過身份驗證的用戶的特權不允許調用)。
假設您有一個設計簡單的web應用程序。該應用程序有一個公開端點的控制器。控制器直接調用一個服務,該服務實現了一些邏輯,并使用了通過存儲庫管理的持久化數據(圖1.9)。設想這樣一種情況,授權僅在端點級別完成(假設您可以通過REST端點訪問該方法)。開發人員可能傾向于只在控制器層應用授權規則,如圖1.9所示。
圖 1.9
開發人員在控制器層應用授權規則。但是存儲庫不知道用戶,也不限制數據的檢索。如果服務請求的帳戶不屬于當前經過身份驗證的用戶,則存儲庫將返回這些帳戶。
雖然圖 1.9 所示的情況工作正常,但僅在控制器層應用授權規則可能會留下出錯的空間。在這種情況下,一些未來的實現可能不測試就公開該用例,或者不測試所有授權需求。在圖 1.10 中,您可以看到如果開發人員添加另一個依賴于同一存儲庫的功能會發生什么。
可能會出現這些情況,您可能需要在應用程序的任何層處理這些情況,而不僅僅是在存儲庫中。我們將在后面討論更多與這個主題相關的事情。在那里,您還將了解如何在需要時對每個應用程序層應用限制,以及應該避免這樣做的情況。
圖 1.10
新添加的 TransactionController 在其依賴關系鏈中使用 AccountRepository。開發人員還必須在此控制器中重新應用授權規則。但是,如果存儲庫本身確保不公開不屬于經過身份驗證的用戶的數據,情況就會好得多。
使用已知漏洞的依賴
盡管不一定與 Spring Security 直接相關,但仍然是應用程序級安全的重要方面,但我們需要注意的依賴項。 有時,并非具有漏洞的開發應用程序,而是用于構建功能的依賴項,例如庫或框架。 請始終注意您使用的依賴項,并消除任何已知包含漏洞的版本。
幸運的是,我們可以通過向您的 Maven 或 Gradle 配置中添加插件來快速完成靜態分析的多種可能性。 如今,大多數應用程序都是基于開源技術開發的。 甚至 Spring Security 都是一個開源框架。 這種開發方法很棒,并且可以快速發展,但這也使我們更容易出錯。
開發任何軟件時,我們都必須采取所有必要的措施,以避免使用任何具有已知漏洞的依賴項。 如果發現使用了這種依賴關系,則不僅必須快速糾正此問題,還必須調查應用程序中是否已經利用了該漏洞,然后采取必要的措施。