深入剖析HashiCorp Vault中的身份验证漏洞(上篇)

安全 漏洞
在这篇文章中,我们将为读者深入讲解HashiCorp Vault中的两个身份验证漏洞。

[[346414]]

简介

在这篇文章中,我们将为读者深入讲解HashiCorp Vault中的两个身份验证漏洞。实际上,我们不仅会介绍这两个漏洞的利用方法,同时,还会演示如何在“云原生”软件中找到这种类型的安全漏洞。这两个漏洞(CVE-2020-16250/16251)均已得到了HashiCorp公司的妥善处理,并在8月份发布的1.2.5,1.3.8,1.4.4和1.5.1版本Vault中进行了修复。

Vault是一种广泛使用的工具,用于安全地存储、生成和访问API密钥、密码或证书等机密信息。尽管它也能够用作人类用户的共享密码管理器,但是它的功能却主要是针对基于API的访问进行优化的。Vault的应用场景包括为某些服务(Web服务器、数据库或第三方资源(如AWS S3 bucket)等)提供临时的登录凭据。

使用像Vault这样的中心化机密信息存储设施能够带来许多安全优势,例如集中审计,强制凭证轮换或加密数据存储。然而,对于攻击者来说,中心化的机密信息存储也是一个非常值得关注的攻击目标——一旦得手,攻击者就能访问各种重要的机密信息,从而可以访问大部分的目标基础设施。

在深入研究这些漏洞的技术细节之前,下一节将概述Vault的身份验证架构及其与云提供商集成的方式。熟悉Vault的读者可以跳过本节。

基于Vault的身份验证架构

与Vault进行交互时,首先需要进行身份验证;Vault支持基于角色的访问控制,以管理对存储的机密信息的访问权限。在身份验证方面,它支持可插拔的auth方法,范围从静态凭证、LDAP或Radius到完全集成到第三方OpenID Connect (OIDC)提供商或云身份访问管理(IAM)平台。对于在支持的云提供商上运行的基础设施来说,使用云提供商的IAM平台进行身份验证是一个非常合乎逻辑的选择。

下面,我们将以AWS为例进行介绍。我们知道,几乎每一个在AWS中运行的工作负载都是以特定的AWS IAM用户的身份来执行的。通过启用和配置aws auth方法,您可以在某些IAM用户或角色与Vault角色之间创建相应的映射。

想象一下,如果您有一个AWS Lambda函数,并希望让它访问存储在Vault中的数据库密码。Vault管理员可以使用vault CLI为Lambda函数的执行角色分配一个vault角色,而不是在函数代码中存储硬编码的凭证。

  1. vault write auth/aws/role/dbclient auth_type=iam \ 
  2.   
  3. bound_iam_principal_arn=arn:aws:iam::123456789012:role/lambda-role policies=prod,dev max_ttl=10m 

这将在名为dbclient的vault角色和AWS IAM角色lambda-role之间创建一个映射。这样,就可以通过vault策略来授予dbclient角色对数据库秘密的访问权了。

当lambda函数执行时,它通过向/v1/auth/aws/login API端点发送请求,以通过Vault进行身份验证。我将在后面介绍这个请求的具体结构,但现在只是假设该请求允许Vault验证调用者的AWS IAM角色。如果验证成功,Vault会将dbclient角色的临时API令牌返回给lambda函数。现在,就可以使用该令牌从Vault获取数据库密码了。根据数据库后端的不同,这个密码可以是一个静态的用户密码组合,一个临时的客户端证书,甚至是一个动态创建的证书对

以这种方式使用Vault有一些不错的安全优势:lambda函数本身不需要包含引导凭证,而且每次访问数据库的凭证都是可以审计的。轮换旧的或被破坏的数据库凭证非常简单,并且可以集中执行。

然而,这种操作上的简单性,完全是将复杂性隐藏在AWS iam auth方法中结果。那么,/v1/auth/aws/login API端点究竟是如何工作的,未经认证的攻击者是否有办法冒充随机的AWS IAM角色呢?

sts:GetCallerIdentity

在其内部,Vault的aws auth方法支持两种不同的认证机制:iam和ec2。在这里,我们感兴趣的是iam,我们之前的Lambda示例中曾用过该机制。Iam认证机制是建立在名为GetCallerIdentity的AWS API方法之上的,它是AWS安全令牌服务(STS)的一部分。

顾名思义,GetCallerIdentity将返回IAM角色或用户的详细信息,其凭证被用于调用API。要了解Vault如何使用该方法对客户进行身份验证,我们需要了解AWS API如何进行身份验证的。

AWS不是将某种形式的身份验证令牌或凭据附加到API请求中,而是要求客户端使用调用者的秘密访问密钥为(规范化的)请求计算HMAC签名,并将此签名附加到请求中。这种机制使得预先对请求进行签名并将其转发给另一方,从而实现一定程度的身份冒充成为可能。一个流行的用例是,赋予客户端S3的文件上传权限,而无需授予他们访问具有写权限的凭据的权限。

实际上,Vault aws认证机制就是这种技术的一个简单变体。 

客户端向STS GetCallerIdentity方法预先对一个HTTP请求进行签名,并将其序列化版本发送给Vault服务器。Vault服务器将预签名的请求发送到STS主机,并从结果中提取AWS IAM信息。这个流程的服务器端部分是由builtin/credential/aws/path_login.go文件的pathLoginUpdate函数实现的。

  1. func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 
  2.   
  3.     method := data.Get("iam_http_request_method").(string) 
  4.   
  5.     ... 
  6.   
  7.     // In the future, might consider supporting GET 
  8.   
  9.     if method != "POST" { 
  10.   
  11.             return logical.ErrorResponse(...), nil 
  12.   
  13.     } 
  14.   
  15.     rawUrlB64 := data.Get("iam_request_url").(string) 
  16.   
  17.     ... 
  18.   
  19.     rawUrl, err := base64.StdEncoding.DecodeString(rawUrlB64) 
  20.   
  21.     ... 
  22.   
  23.     parsedUrl, err := url.Parse(string(rawUrl)) 
  24.   
  25.     if err != nil { 
  26.   
  27.             return logical.ErrorResponse(...), nil 
  28.   
  29.     } 
  30.   
  31.     bodyB64 := data.Get("iam_request_body").(string) 
  32.   
  33.     ... 
  34.   
  35.     bodyRaw, err := base64.StdEncoding.DecodeString(bodyB64) 
  36.   
  37.     ...        
  38.   
  39.     body := string(bodyRaw) 
  40.   
  41.     headers := data.Get("iam_request_headers").(http.Header) 
  42.   
  43.     
  44.   
  45.     endpoint := "https://sts.amazonaws.com" 
  46.   
  47.     ... 
  48.   
  49.     callerID, err := submitCallerIdentityRequest(ctx, maxRetries, method, endpoint, parsedUrl, body, headers) 

该函数从存储在数据中的请求正文中提取HTTP方法、URL、正文和标头。然后调用submitCallerIdentity将请求转发到STS服务器,并利用ParseGetCallerIdentityResponse来获取和解析结果:

  1. func submitCallerIdentityRequest(ctx context.Context, maxRetries int, method, endpoint string, parsedUrl *url.URL, body string, headers http.Header) (*GetCallerIdentityResult, error) { 
  2.   
  3.     ... 
  4.   
  5.     request := buildHttpRequest(method, endpoint, parsedUrl, body, headers) 
  6.   
  7.     retryableReq, err := retryablehttp.FromRequest(request) 
  8.   
  9.     ... 
  10.   
  11.     response, err := retryingClient.Do(retryableReq) 
  12.   
  13.     responseBody, err := ioutil.ReadAll(response.Body) 
  14.   
  15.     ... 
  16.   
  17.     if response.StatusCode != 200 { 
  18.   
  19.             return nil, fmt.Errorf(..) 
  20.   
  21.     } 
  22.   
  23.     callerIdentityResponse, err := parseGetCallerIdentityResponse(string(responseBody)) 
  24.   
  25.     if err != nil { 
  26.   
  27.             return nil, fmt.Errorf("error parsing STS response"
  28.   
  29.     } 
  30.   
  31.     return &callerIdentityResponse.GetCallerIdentityResult[0], nil 
  32.   
  33.   
  34.   
  35.   
  36. func buildHttpRequest(method, endpoint string, parsedUrl *url.URL, body string, headers http.Header) *http.Request { 
  37.   
  38.     ... 
  39.   
  40.     targetUrl := fmt.Sprintf("%s/%s", endpoint, parsedUrl.RequestURI()) 
  41.   
  42.     request, err := http.NewRequest(method, targetUrl, strings.NewReader(body)) 
  43.   
  44.     ... 
  45.   
  46.     request.Host = parsedUrl.Host 
  47.   
  48.     for k, vals := range headers { 
  49.   
  50.             for _, val := range vals { 
  51.   
  52.                     request.Header.Add(k, val) 
  53.   
  54.             } 
  55.   
  56.     } 
  57.   
  58.     return request 
  59.   

buildHttpRequest函数会根据用户提供的参数创建一个http.Request对象,并使用硬编码常量https://sts.amazonaws.com来构建目标URL。

如果没有这个限制,我们可以简单地触发对我们控制的服务器的请求,并返回调用者身份。

然而,由于完全缺乏对URL路径、查询、POST正文和HTTP标头的验证,所以这看起来仍然是一个非常有希望的攻击面。下一节将介绍如何将这个安全缺陷变成一个认证绕过漏洞。

STS(调用方)身份盗用

我们的目标是欺骗Vault的submitCallerIdentityRequest函数,使其返回一个攻击者控制的调用方身份。实现这个目标的方法之一是操纵Vault服务器,使其向我们控制的主机发送请求,从而绕过硬编码的端点主机。通过查看buildHttpRequest方法的源代码,我想到了两种方法:

· 用于计算targetUrl的代码,即targetUrl := fmt.Sprintf("%s/%s", endpoint, parsedUrl.RequestURI()) ,看起来在URL解析问题方面并不是很健壮。但是,嵌入伪造的用户信息(https://sts.amazonaws.com/:foo@example.com/test)之类的技巧和类似的想法对健壮的Go URL解析器是行不通的。

· 即使Vault将始终创建一个指向硬编码端点的HTTPS请求,攻击者也可以完全控制Host http标头(request.Host = parsedUrl.Host)。如果STS API前面的负载平衡器根据Host标头做出路由决策的话,这可能就是一个问题,但针对STS主机的盲测并没有取得成功。

在排除了简单的方法后,我们还有另一种方法可以使用。Vault并没有限制URL查询参数。这意味着,我们不仅可以创建GetCallerIdentity的预签名请求,还可以对STS API的任何操作创建请求。STS支持8个不同的操作,但没有一个操作能让我们完全控制响应。这时,我开始感到沮丧,于是决定看看Vault的响应解析代码。

  1. func parseGetCallerIdentityResponse(response string) (GetCallerIdentityResponse, error) { 
  2.   
  3.         decoder := xml.NewDecoder(strings.NewReader(response)) 
  4.   
  5.         result := GetCallerIdentityResponse{} 
  6.   
  7.         err := decoder.Decode(&result) 
  8.   
  9.         return result, err 
  10.   
  11.   
  12. type GetCallerIdentityResponse struct { 
  13.   
  14.  XMLName                 xml.Name                 `xml:"GetCallerIdentityResponse"
  15.   
  16.  GetCallerIdentityResult []GetCallerIdentityResult `xml:"GetCallerIdentityResult"
  17.   
  18.  ResponseMetadata        []ResponseMetadata        `xml:"ResponseMetadata"
  19.   

我们可以看到,只要状态代码为200,就会对从STS接收到的每个响应调用parseGetCeller IdentityResponse。该函数将使用Golang标准XML库将XML响应解码成GetCallerIdentityResponse结构,如果解码失败则返回错误。

这个代码有一个容易被忽略的问题:Vault从未强制验证STS响应是否为XML编码。虽然STS响应在默认情况下是XML编码的,但是对于发送Accept:Application/json HTTP标头的客户端来说,它也能够支持JSON编码。

但是对于Vault来说,这就变成了一个安全问题,因为go XML解码器有一个惊人的特性:解码器会悄悄地忽略预期的XML根之前和之后的非XML内容。这意味着使用(JSON编码的)服务器响应(如‘{“abc” : “xzy}’)调用parseGetCallIdentityResponse函数将会成功,并返回一个(空的)CallIdentityResponse结构。

小结

在本文中,我们为读者介绍了Vault的身份验证架构,以及冒用调用方身份的方法,在下一篇文章中,我们将继续为读者介绍利用Vault-on-GCP的漏洞的过程。

本文翻译自:https://googleprojectzero.blogspot.com/2020/10/enter-the-vault-auth-issues-hashicorp-vault.html如若转载,请注明原文地址。

 

责任编辑:姜华 来源: 嘶吼网
相关推荐

2020-10-17 10:44:24

漏洞

2021-07-19 10:10:15

身份验证漏洞Windows Hel

2022-05-19 14:39:41

VMware漏洞恶意攻击

2014-09-12 09:58:45

2012-02-20 09:55:41

ibmdw

2010-09-06 11:24:47

CHAP验证PPP身份验证

2010-01-07 17:41:19

VB.NET验证LDA

2023-11-30 13:13:14

2010-07-17 00:57:52

Telnet身份验证

2014-10-30 09:14:28

2012-04-10 09:36:58

2013-07-21 18:32:13

iOS开发ASIHTTPRequ

2011-02-21 10:54:45

2022-11-14 08:17:56

2021-10-06 14:36:36

身份验证漏洞黑客

2024-08-06 16:00:06

2012-10-23 16:12:35

2022-06-05 00:15:31

验证身份网络

2022-10-31 10:00:00

2010-07-19 17:30:47

点赞
收藏

51CTO技术栈公众号