IAM role MFA 강제하는 Policy의 결함 조치
0. 개요
SSO 전환 이전, 구 IAM 사용자 체계에서 있었던 MFA 강제정책의 결함 발견과 해결 과정을 소개합니다.
TLDR
- 대상 독자: 클라우드 보안 담당자, 인프라 담당자, 권한 정책 담당자
- 얻을 수 있는 점: IAM policy의 BoolIfExists 연산자 특성으로 인한 MFA 우회 취약점 이해 및 실효성 있는 보안 정책 수립 방법
1. IAM 엑세스키 이용자의 상황
SSO 전환 이전 IAM 체계에서, 개발자들은 CLI, IDE에서 활용가능하도록 엑세스키를 생성할 수 있는 IAM 계정 (user@company.com-key
)이 분리되어 있었고, 각 조직의 IAM Role을 위임하여 필요한 AWS 권한을 사용하였습니다.
해당 IAM role에서 MFA를 사용하는 키계정 IAM user만 role을 assume하도록 강제하는 방법은
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::123456789012:user/user@company.com-key"
]
},
"Action": "sts:AssumeRole",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
위 policy를 role의 trust relationships에 추가함으로써 이루어졌었습니다.
정책의 결함이 보이시나요?
이제 다음 .aws/config 상황에서 dev-awsudo
profile을 통해 aws cli 명령을 직접 실행해 봅시다.
dev-awsudo
profile은 awsudo-devsecops
role을 가리키고 있으며, 어디에도 mfa_serial이 설정되어 있지 않습니다.
# DEV
[profile aws-coinfra]
region=ap-northeast-2
output=json
[profile dev-awsudo]
source_profile=aws-coinfra
role_arn=arn:aws:iam::123456789012:role/awsudo-devsecops
region=ap-northeast-2
output=json
실행 결과 다음과 같이 mfa 입력 없이도 assume된 role을 통해 aws cli 명령 실행이 가능합니다.
trust policy에 명시했음에도 불구하고 왜 mfa 없이 assume role이 가능한 것일까요?! 🧐
바로 condition에 사용된 BoolIfExists
operator 때문입니다.
2. Policy 분석
condition operator: BoolIfExists
...IfExists
operator는 IAM policy의 condition operator로, Null
을 제외한 모든 operator에 추가될 수 있습니다.
예시: boolIfExists
, StringlikeIfExists
, …
...IfExists
는 condition 안의 key가 존재하고 true인 경우, 또는 앞의 operator가 존재하지 않는 경우 모두 true를 반환합니다.
따라서 현 상황에서의 trust policy는 다음과 같이 적용됩니다.
request context에 "aws:MultiFactorAuthPresent"
가 애초에 존재하지 않을 경우 allow하게 됩니다.
Context Key: aws:MultiFactorAuthPresent
"aws:MultiFactorAuthPresent"
context key는 temporary credential의 경우에만 존재합니다.
키계정의 경우 IAM user이기 때문에 그 자체로는 long term credential로 "aws:MultiFactorAuthPresent"
가 존재하지 않지만,
get-session-token으로 세션을 얻을 경우 temporary credential이기 때문에 "aws:MultiFactorAuthPresent"
이 추가됩니다.
awsudo나 aws-vault의 경우 config에서 mfa를 추가하면 보통 get-session-token → assume-role의 순서로 진행되기 때문에 "aws:MultiFactorAuthPresent"
이 context에 존재했고,
기존의 정책으로도 막을 수 있었습니다.
하지만 키계정 IAM user가 곧바로 assume-role을 할 경우, 즉 "aws:MultiFactorAuthPresent"
비존재의 경우 막을 수 없었습니다.
IAM 구조 및 MFA 우회 흐름도
user_company.com-key] B --> C{MFA 설정 여부} C -->|MFA 없음| D[직접 AssumeRole 시도] C -->|MFA 있음| E[GetSessionToken
with MFA] D --> F[IAM Role
Trust Policy 검증] E --> G[Temporary Credentials
aws:MultiFactorAuthPresent=true] G --> H[AssumeRole 시도] H --> F F --> I{BoolIfExists 조건} I -->|aws:MultiFactorAuthPresent
존재하지 않음| J[✅ ALLOW
취약점 발생!] I -->|aws:MultiFactorAuthPresent
=true| K[✅ ALLOW
정상 접근] I -->|aws:MultiFactorAuthPresent
=false| L[❌ DENY] J --> M[AWS 리소스 접근] K --> M style J fill:#ff9999 style K fill:#99ff99 style L fill:#ffcccc
3. 해결방법
1. 키계정에 Deny policy 추가 (recommended)
키계정에 해당 policy를 추가합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RequireMFA",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
이 policy의 적용 상황은 다음과 같습니다.
"aws:MultiFactorAuthPresent"
가 존재하지 않는 경우에도 explicit deny되기 때문에
키계정을 통한 직접 assume 시나리오를 막을 수 있습니다.
mfa를 추가하여 "aws:MultiFactorAuthPresent" == "true"
인 경우에는
assume 하려는 role의 trust policy가 있기 때문에 allow됩니다.
2. role들의 trust policy 변경
trust policy를 다음과 같이 변경합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::123456789012:user/user@company.com-key"
]
},
"Action": "sts:AssumeRole",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
적용 상황은 다음과 같습니다.
이는 같은 목적을 달성하지만,
- 모든 role의 trust policy를 변경해야 하기 때문에 공수가 크고
- implicit deny이기 때문에 불완전한 deny 정책이다
라는 단점이 있습니다.