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 권한을 사용하였습니다.

flowchart LR A[개발자] --> B[user_company.com-key] B -->|MFA 인증시| C[AssumeRole 성공, 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 명령 실행이 가능합니다.

AWS CLI execution without MFA

trust policy에 명시했음에도 불구하고 왜 mfa 없이 assume role이 가능한 것일까요?! 🧐

바로 condition에 사용된 BoolIfExists operator 때문입니다.

2. Policy 분석

condition operator: BoolIfExists

https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_IfExists

...IfExists operator는 IAM policy의 condition operator로, Null을 제외한 모든 operator에 추가될 수 있습니다.

예시: boolIfExists, StringlikeIfExists, …

...IfExistscondition 안의 key가 존재하고 true인 경우, 또는 앞의 operator가 존재하지 않는 경우 모두 true를 반환합니다.

따라서 현 상황에서의 trust policy는 다음과 같이 적용됩니다.

BoolIfExists condition behavior

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" 비존재의 경우 막을 수 없었습니다.

https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-multifactorauthpresent

IAM 구조 및 MFA 우회 흐름도

flowchart TD A[개발자] --> B[IAM User
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. 해결방법

키계정에 해당 policy를 추가합니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RequireMFA",
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    }
  ]
}

이 policy의 적용 상황은 다음과 같습니다. Deny policy application

"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"
        }
      }
    }
  ]
}

적용 상황은 다음과 같습니다. Trust policy modification

이는 같은 목적을 달성하지만,

  • 모든 role의 trust policy를 변경해야 하기 때문에 공수가 크고
  • implicit deny이기 때문에 불완전한 deny 정책이다

라는 단점이 있습니다.