// HackTheBox  ·  writeup

Nubilum-1

HackTheBox Medium PythontarzipinfoJohn the Ripperbkcracksqlite3
Cloud

Scenario

The cloud administration team is alerted to potential malicious activity occurring within their Amazon EC2 instances. Unrecognised deployments were discovered by the system administrator, posing a serious threat to the company’s reputation. The lack of preemptive security measures exacerbates the situation, including the unrestricted global access to the critical S3 bucket. The incident responder emphasises the urgency of investigating the misuse of their infrastructure, urging the necessity for immediate collaboration to collect CloudTrail data and initiate a Phase 1 UNIX collection on the implicated EC2 instance. The situation outlines a critical security lapse demanding swift action to protect the company’s reputation.

Executive Summary

A globally readable S3 bucket exposed a backup script with a hard-coded IAM access key pair. The threat actor used those keys from a small block of addresses to call the EC2 API directly, generating key pairs, creating a permissive security group and launching batches of instances. The administrator terminated the first batch, but the actor kept deploying. A CatScale UNIX collection from one of the instances showed a PoshC2 server running, and its project database, locked inside a password-protected archive, listed the machines that had checked in.

Artifacts

Timestamp note: CloudTrail eventTime is UTC. Q5 wants the full ISO form with a trailing Z, Q8 wants YYYY-MM-DD hh:mm.

Collection note: The CatScale tarball contains directories with mode 000. Run chmod -R u+rwX on the extracted tree before browsing it.

Credential Access

Q2 — Where did the attacker locate the hard coded IAM credentials? (don't include forela-storage)

Extract the S3 bucket archives and search them for AWS access key IDs (AKIA...). One file, a backup script, carries an access_key and secret_key pair in plain text. The path inside the bucket, minus the bucket name, is the location.

grep -rn 'AKIA' work/s3
Click to reveal answer /backups/ec2.py
Q1 — Which AWS IAM account was compromised by the TA?

From Q2 the leaked key ID is AKIA52GPOBQCO4SRMOWK. The CloudTrail export is a folder of plain .json files, so merge them into one time-sorted list first, then read the userIdentity.userName on events that used that access key.

python3 -I merge_ct.py ct/ > work/ct/all.json
grep -o '"userName": *"[^"]*"' work/ct/all.json | sort | uniq -c | sort -rn
Click flag to reveal forela-ec2-automation

Initial Access

Q9 — Can we confirm the IP addresses used by the TA to abuse the leaked credentials? (Ascending Order)

Filter the merged CloudTrail data to the compromised user from Q1 and list the distinct sourceIPAddress values. The administrator’s own activity comes from different addresses, so the leaked-key traffic separates cleanly. Four addresses in one /24 remain; sort them ascending as the format asks.

python3 -I ct_ips.py work/ct/all.json forela-ec2-automation
Click flag to reveal 95.181.232.4, 95.181.232.8, 95.181.232.9, 95.181.232.28

Defense Evasion

Q3 — In total how many EC2 hosts were deployed by the TA?

Take the RunInstances events from the Q9 addresses and sum the instances in each response. The batches were 2, 2, 2, 5, 1 and 1. Two further RunInstances calls failed and returned zero instances, so counting event lines instead of instances gives the wrong total.

python3 -I ct_runinstances.py work/ct/all.json

Gotcha: Failed RunInstances calls are still logged. Read responseElements.instancesSet of each event instead of counting lines.

Click flag to reveal 13
Q4 — What is the name of the key pair/s generated by the attacker?

Filter for CreateKeyPair events from the attacker addresses and read requestParameters.keyName. Two key pairs were created. The format mask lists the names as X.key, X, which matches one name with a .key suffix and one without.

python3 -I ct_events.py work/ct/all.json CreateKeyPair
Click to reveal answer 1337.key, 13337
Q5 — What time were the key pair/s generated by the attacker?

Same CreateKeyPair events, this time the eventTime of each. The two timestamps are about thirteen hours apart, which fits the actor returning later to deploy again. Keep the order from Q4 and the ISO format with the trailing Z.

python3 -I ct_events.py work/ct/all.json CreateKeyPair --fields eventTime
Click flag to reveal 2023-01-24T22:48:34Z,2023-01-25T12:01:38Z
Q6 — What are the key pair ID/s of the key/s generated by the attacker?

The CreateKeyPair response carries the keyPairId. The plural wording is misleading: the expected answer is only the ID of 1337.key, the first key created.

python3 -I ct_events.py work/ct/all.json CreateKeyPair --fields keyName,keyPairId

Gotcha: Despite the “ID/s” wording, listing both key pair IDs does not match. Only the first key’s ID does.

Click to reveal answer key-0450dc836eaf2aa37
Q7 — What is the description of the security group created by the attacker?

CreateSecurityGroup from the attacker addresses carries the groupDescription in its request parameters.

python3 -I ct_events.py work/ct/all.json CreateSecurityGroup --fields groupName,groupDescription
Click flag to reveal still here
Q8 — At what time did the Sys Admin terminate the first set of EC2s deployed?

Look for TerminateInstances made by the administrator identity rather than the compromised user. The first call covers the six instances from the first three launches. Truncate the eventTime to the minute for the answer format.

python3 -I ct_events.py work/ct/all.json TerminateInstances --fields eventTime,userIdentity,instanceIds
Click to reveal answer 2023-01-24 23:25

Command and Control

Q10 — In addition to the CloudTrail data and S3 access we have provided artefacts from the endpoint reported by AWS. What is the name of the malicious application installed on the EC2 instance?

Extract the CatScale tarball, fix the directory permissions, and read the process listing. A server process runs out of /var/poshc2/money, which is the PoshC2 install path. The format mask (five characters then a 2) fits the name.

chmod -R u+rwX work/p1/catscale_out
grep -ri poshc2 work/p1/catscale_out
Click to reveal answer PoshC2
Q11 — Please can you provide the hostname and username details of any victims of the C2 server?

The PoshC2 project data sits in unusual-directory.zip, which is password protected. zip2john with rockyou did not crack it and the password is not in the collection. zipinfo shows the archive holds config-template.yml as a stored (uncompressed) entry, and that file is public in the PoshC2 GitHub repository, so a known-plaintext attack with bkcrack recovers the internal keys without the password. It needed a template from the matching era and the plaintext deflated at zlib level 9 to match the compressed bytes. With the keys, rebuild the archive without a password and read the Implants table of PowershellC2.SQLite.

zip2john unusual-directory.zip > zip.hash && john zip.hash --wordlist=/usr/share/dict/rockyou.txt
zipinfo unusual-directory.zip
bkcrack -C unusual-directory.zip -c config-template.yml -p config-template.yml.deflated -x 0
bkcrack -C unusual-directory.zip -k 93d56f14 ff7c6188 48511983 -D dec.zip
sqlite3 work/poshc2x/PowershellC2.SQLite 'select * from Implants;'

Gotcha: Two implants are registered, but only the first (the answer) is accepted. The second, DESKTOP-JGLLJLD\admin, is not part of the expected answer, so listing both victims fails.

Click flag to reveal DESKTOP-R4KM0GJ\Marcus Athony

Attack Summary

PhaseAction
Credential AccessHard-coded IAM keys read from backups/ec2.py in the world-readable forela-storage bucket
Initial AccessKeys for forela-ec2-automation used from four addresses in 95.181.232.0/24
Defense EvasionKey pair 1337.key created, security group created, first batches of EC2 instances launched
ResponseAdministrator terminates the first six instances
Defense EvasionSecond key pair 13337 created and further instances launched, 13 in total
Command and ControlPoshC2 server installed on an instance under /var/poshc2/money
Command and ControlImplants register from victim hosts, recorded in PowershellC2.SQLite

IOCs

TypeValue
IP95.181.232[.]4, 95.181.232[.]8, 95.181.232[.]9, 95.181.232[.]28
AWS access key IDAKIA52GPOBQCO4SRMOWK
IAM userforela-ec2-automation
Key pairs1337.key (key-0450dc836eaf2aa37), 13337
Security group descriptionstill here
Filebackups/ec2.py (S3, forela-storage)
Directory/var/poshc2/money
C2 victimDESKTOP-R4KM0GJ\Marcus Athony

MITRE ATT&CK

TechniqueIDDescription
Unsecured Credentials: Credentials In FilesT1552.001IAM key pair hard-coded in ec2.py
Data from Cloud StorageT1530Script retrieved from the globally readable bucket
Valid Accounts: Cloud AccountsT1078.004Leaked keys used against the EC2 API
Modify Cloud Compute Infrastructure: Create Cloud InstanceT1578.00213 EC2 instances launched with attacker key pairs
Impair Defenses: Disable or Modify Cloud FirewallT1562.007Attacker-created security group
Application Layer Protocol: Web ProtocolsT1071.001PoshC2 implants beaconing to the hosted server

Defender Takeaways

Never put access keys in files, and never in a world-readable bucket. The whole intrusion started with ec2.py holding a key pair in plain text. Block public access on the bucket at account level, scan stored scripts for AKIA patterns, and use instance roles instead of static keys.

Scope automation credentials tightly. The leaked user could create key pairs, security groups and launch instances from anywhere. Limit it with IAM conditions on source IP and a permission boundary, and alert on RunInstances from addresses outside the known automation range.

Alert on EC2 API calls from new source IPs. All the abuse is in CloudTrail: CreateKeyPair, CreateSecurityGroup and RunInstances from a single /24 never seen before. A rule on those three events from a new IP would have caught the first batch before the administrator spotted it.

Terminating the instances is not remediation. The administrator removed the first set, yet the keys stayed valid and the actor launched more. Deactivate the access key as the first containment step, then rotate and review.

Collect before you destroy. The CatScale triage held the process listing and the PoshC2 project database, which named the victim machines. Preserve instance data before termination so C2 infrastructure can be mapped. A ZipCrypto archive is also weak protection when a public file inside it can serve as known plaintext.

Conclusion

A single hard-coded key in a public bucket gave the actor full EC2 control and a place to host a PoshC2 server. CloudTrail recorded every step, so the weakness was never visibility: it was a credential that should not have been readable by anyone.