Spring Security – 攻擊 OAuth 授權

Spring Security
Remote
1
03:44 AM · Nov 30 ,2025

1. 簡介

OAuth 是授權委託的標準行業框架。 制定各種流程需要投入大量精力,但即使如此,仍然存在漏洞。

在本文檔系列中,我們將從理論角度探討對 OAuth 的攻擊,並描述保護應用程序的各種選項。

2. 授權碼流程

授權碼流程 是大多數實現委託授權的應用所使用的默認流程。

在流程開始之前,客户端必須已在授權服務器上進行預註冊,並且在此過程中,它也必須提供一個重定向 URL——即 一個授權服務器可以回調到客户端以獲取授權碼的 URL

讓我們更詳細地瞭解其工作原理以及這些術語的含義。

在授權碼流程中,一個客户端(即請求委託授權的應用程序)會將資源所有者(用户)重定向到授權服務器(例如,使用 Google 登錄)。登錄後,授權服務器 將授權碼回調到客户端

接下來,客户端會向授權服務器的端點發起請求,以提供授權碼來獲取訪問令牌。此時,流程結束,客户端可以使用令牌來訪問授權服務器保護的資源。

現在,OAuth 2.0 框架允許這些客户端是公開的,例如在客户端無法安全地持有客户端密鑰的場景中。讓我們來看看針對公共客户端可能發生的重定向攻擊。

3. 重定向攻擊

3.1. 攻擊前提條件

重定向攻擊依賴於 OAuth 標準並未完全描述重定向 URL 必須指定的程度。 這也是出於設計目的。

這允許某些 OAuth 協議的實現允許部分重定向 URL。

例如,如果我們在以下基於通配符的匹配規則下注冊客户端 ID 和客户端重定向 URL 與授權服務器:

*.cloudapp.net

這將有效用於:

app.cloudapp.net

但也將用於:

evil.cloudapp.net

我們故意選擇了 cloudapp.net 域名,因為這是一個真實的位置,我們可以託管基於 OAuth 的應用程序。 域名是 Microsoft 的 Windows Azure 平台 的一部分,允許任何開發人員在其下託管子域名以進行應用程序測試。 這本身並不是問題,但它是更大漏洞的關鍵組成部分。

這個漏洞的第二部分是允許在回調 URL 上進行通配符匹配的授權服務器。

最後,要實現這個漏洞,應用程序開發人員需要與授權服務器註冊以接受任何在主域名下方的 URL,格式為 *.cloudapp.net

3.2. 攻擊

當滿足這些條件時,攻擊者需要欺騙用户在受其控制的子域名下啓動頁面,例如,通過發送 給用户看起來真實的電子郵件,要求他採取受 OAuth 保護帳户上的操作。 通常,它看起來像 https://evil.cloudapp.net/login。 當用户打開此鏈接並選擇登錄時,他將被重定向到授權服務器,並提交授權請求:

GET /authorize?response_type=code&client_id={apps-client-id}&state={state}&redirect_uri=https%3A%2F%2Fevil.cloudapp.net%2Fcb HTTP/1.1

雖然這看起來很正常,但此 URL 是惡意的。 事實上,在這種情況下,授權服務器收到一個帶有客户端 ID 和指向 evil’s 應用程序的重定向 URL 的修改 URL。

授權服務器將驗證 URL,該 URL 是在指定的主域名下方的子域名。 由於授權服務器認為該請求來自一個有效的來源,因此它將驗證用户的身份,然後請求同意,就像正常一樣。

完成此操作後,它將重定向回 evil.cloudapp.net 子域名,並將授權碼交給攻擊者。

由於攻擊者現在擁有授權碼,他只需要使用授權碼調用授權服務器的令牌端點即可獲得令牌,從而允許他訪問資源所有者的受保護資源。

4. Spring OAuth Authorization Server 漏洞評估

讓我們看一下簡單的 Spring OAuth Authorization Server 配置:

@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
          .withClient("apricot-client-id")
          .authorizedGrantTypes("authorization_code")
          .scopes("scope1", "scope2")
          .redirectUris("https://app.cloudapp.net/oauth");
    }
    // ...
}

我們可以看到,Authorization Server 配置了一個新的客户端,其 id 是 “apricot-client-id”。 客户端密鑰沒有配置,因此這是一個公共客户端。

我們的安全警報應該在此時響起,因為我們現在有兩個條件中的兩個都滿足——邪惡的人可以註冊子域名 並且我們正在使用公共客户端。

但是,請注意,我們正在配置 這裏面的重定向 URL,並且它是絕對的。 通過這樣做,我們可以緩解漏洞。

4.1. 嚴格的

默認情況下,Spring OAuth 允許在重定向 URL 匹配中具有一定的靈活性。

例如,DefaultRedirectResolver 支持子域名匹配。

讓我們只使用我們需要的。 如果我們能夠精確匹配重定向 URL,我們應該這樣做:

@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    //...

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.redirectResolver(new ExactMatchRedirectResolver());
    }
}

在這種情況下,我們已經使用 ExactMatchRedirectResolver 來處理重定向 URL。 這個解析器進行精確的字符串匹配,不以任何方式解析重定向 URL。 這使得它的行為更加安全和確定。

4.2. 寬鬆的

我們可以找到處理重定向 URL 匹配的默認代碼在 Spring Security OAuth 源代碼:

/**
Whether the requested redirect URI "matches" the specified redirect URI. For a URL, this implementation tests if
the user requested redirect starts with the registered redirect, so it would have the same host and root path if
it is an HTTP URL. The port, userinfo, query params also matched. Request redirect uri path can include
additional parameters which are ignored for the match
<p>
For other (non-URL) cases, such as for some implicit clients, the redirect_uri must be an exact match.
@param requestedRedirect The requested redirect URI.
@param redirectUri The registered redirect URI.
@return Whether the requested redirect URI "matches" the specified redirect URI.
*/
protected boolean redirectMatches(String requestedRedirect, String redirectUri) {
   UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
   UriComponents registeredRedirectUri = UriComponentsBuilder.fromUriString(redirectUri).build();
   boolean schemeMatch = isEqual(registeredRedirectUri.getScheme(), requestedRedirectUri.getScheme());
   boolean userInfoMatch = isEqual(registeredRedirectUri.getUserInfo(), requestedRedirectUri.getUserInfo());
   boolean hostMatch = hostMatches(registeredRedirectUri.getHost(), requestedRedirectUri.getHost());
   boolean portMatch = matchPorts ? registeredRedirectUri.getPort() == requestedRedirectUri.getPort() : true;
   boolean pathMatch = isEqual(registeredRedirectUri.getPath(),
     StringUtils.cleanPath(requestedRedirectUri.getPath()));
   boolean queryParamMatch = matchQueryParams(registeredRedirectUri.getQueryParams(),
     requestedRedirectUri.getQueryParams());

   return schemeMatch && userInfoMatch && hostMatch && portMatch && pathMatch && queryParamMatch;
}

我們可以看到,重定向 URL 的匹配是通過解析傳入的重定向 URL 的各個組成部分進行的。 由於其多個功能,這非常複雜,例如端口、子域名和查詢參數是否應該匹配。 選擇允許子域名匹配也是需要仔細考慮的事情。

當然,這種靈活性是存在的,如果我們需要它——我們只需要謹慎地使用它。

5. 隱式流重定向攻擊

值得注意的是,隱式流不建議使用 使用帶有PKCE附加安全提供的授權碼流程更好。 儘管如此,我們還是來看一下重定向攻擊如何出現在隱式流中。

針對隱式流的重定向攻擊將遵循我們上面看到的相同基本流程。 主要區別在於,攻擊者會立即獲得令牌,因為沒有授權碼交換步驟。

如前所述,絕對匹配重定向 URL 可以緩解此類攻擊。

此外,我們發現隱式流還包含另一個相關漏洞。攻擊者可以使用客户端作為開放重定向器,並使其重新附加片段

攻擊始於之前的方式,攻擊者讓用户訪問其控制下的頁面,例如https://evil.cloudapp.net/info。 該頁面設計為像之前一樣發起授權請求。 但是,它現在包括一個重定向 URL:

GET /authorize?response_type=token&client_id=ABCD&state=xyz&redirect_uri=https%3A%2F%2Fapp.cloudapp.net%2Fcb%26redirect_to
%253Dhttps%253A%252F%252Fevil.cloudapp.net%252Fcb HTTP/1.1

redirect_to https://evil.cloudapp.net 正在設置授權端點將令牌重定向到攻擊者控制的域:

Location: https://app.cloudapp.net/cb?redirect_to%3Dhttps%3A%253A%252F%252Fevil.cloudapp.net%252Fcb#access_token=LdKgJIfEWR34aslkf&...

當此請求到達開放重定向器時,它將提取重定向 URL evil.cloudapp.net,然後重定向到攻擊者網站:

https://evil.cloudapp.net/cb#access_token=LdKgJIfEWR34aslkf&...

絕對 URL 匹配也會緩解此攻擊。

6. 總結

在本文中,我們討論了針對OAuth協議的一類基於重定向URL的攻擊。

雖然這種攻擊可能造成嚴重後果,但使用授權服務器上的絕對URL匹配可以緩解這種攻擊。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.