It seems our precious technology has been leaked to the threat actor. Our head Elf, PixelPepermint, seems to think that there were some hard-coded sensitive URLs within the technology sent. Please audit our Sparky Cloud logs and confirm if anything was stolen! PS - Santa likes his answers in UTC…
A binary carrying a hard-coded S3 location led the threat actor to a publicly readable bucket of internal scripts. The bucket was emptied in seconds by a scripted Python client, and the repository’s git history gave up AWS access keys that had been committed and later removed. Those keys belonged to an IAM user, and a second external address used them to list and read a private bucket, retrieving a VPN profile. Everything after the first lead is visible in CloudTrail and in the repository the actor downloaded.
all.json with Python, then filtered on eventName, sourceIPAddress, userAgent and userIdentitygrep and git log -pTimestamp note: CloudTrail
eventTimeis already UTC, which is what Santa asks for. Reformat toYYYY-MM-DD hh:mm:ss.
Nothing in this case’s evidence answers this: CloudTrail records API calls, not the file that told the actor where to look, and a search for hashes in the merged log comes back empty. The binary is the malware from the phishing emails in the preceding case, OpTinselTrace-1. Its strings contain the papa-noel bucket URL, which is how the actor learned the bucket’s location, and its MD5 is the answer.
grep -i md5 all.json
Gotcha: This one is a cross-case pivot. Hunting for the hash inside the Sparky Cloud logs goes nowhere, so carry the sample over from the earlier Sherlock.
Merge the CloudTrail files into a single JSON document, then filter GetObject events against the papa-noel bucket. One external IP, 191.101.31.57, issues them back to back with a python-requests user agent. The earliest event in that run marks the start of the automated pull.
python3 -I work/all.py
grep GetObject all.json | grep papa-noel
Same filter as Q2, read from the other end of the run. The last event is the GetObject for santa-list.csv, nine seconds after the first.
grep GetObject all.json | grep papa-noel
The userAgent on every bulk GetObject is python-requests/2.25.1, the default header of the Python requests library. A person in a browser or the AWS CLI would present a different agent, which is why the uniform value fits a script.
grep -o 'python-requests/[0-9.]*' all.json | sort | uniq -c
Hint: The format mask
*****nis the language name, six letters ending inn.
Search the downloaded NPoleScripts repository for AWS access key IDs, then walk the history. The credentials were removed from one script in a later commit, so the old values are still readable in the diff.
grep -ri 'AKIA' NPoleScripts/
git log -p
Gotcha: The working tree is clean. The keys only show up in the git history, where the commit that removed them still holds the old values.
Pivot on the IAM user whose keys leaked and group its events by source address. Two addresses issue ListObjects and GetObject calls as elfadmin, and they are the answer. The logs hold more external IPs, but they are scanners that touched the public bucket and have no tie to the stolen keys or the bulk download.
python3 -I -c "import json,collections; d=json.load(open('all.json')); print(collections.Counter(e['sourceIPAddress'] for e in d if e.get('userIdentity',{}).get('userName')=='elfadmin'))"
Gotcha: The mask shows exactly two addresses. Padding the list with scanner IPs, or with any other external address in the logs, is wrong however suspicious it looks.
From Q6, filter GetObject against north-pole-private. The call comes from 45.133.193.41 as elfadmin and names the VPN profile in the request parameters. Its eventTime is the retrieval time.
grep GetObject all.json | grep north-pole-private
The userIdentity.userName field on the private bucket calls from Q7 names the IAM user whose keys leaked in Q5. Counting the field across the log shows it is the only named user behind those calls.
grep -o '"userName": "[a-z]*"' all.json | sort | uniq -c
Hint: The mask
*******nis eight letters ending inn.
The bucket to lock down is the public one that held the git repository and started the chain, not the private bucket, which only fell to the leaked keys. Take the name from the Q2 events and write it as an S3 ARN. This was reasoned from the attack path rather than read from an artifact, and HTB accepted it.
grep GetObject all.json | grep papa-noel
Hint: An S3 bucket ARN has no region or account ID, which matches the 22-character mask.
| Phase | Action |
|---|---|
| Discovery | Actor learns the bucket location from strings in a binary |
| Collection | Scripted Python client downloads the whole public bucket in nine seconds |
| Credential Access | AWS keys recovered from the git history of one script |
| Discovery | Stolen IAM user lists objects in the private bucket from a second IP |
| Collection | VPN profile retrieved from the private bucket |
| Type | Value |
|---|---|
| IP | 45.133.193[.]41 |
| IP | 191.101.31[.]57 |
| User agent | python-requests/2.25.1 |
| IAM user | elfadmin |
| File | bytesparkle.ovpn |
| Hash (MD5) | 62d5c1f1f9020c98f97d8085b9456b05 |
| Technique | ID | Description |
|---|---|---|
| Data from Cloud Storage | T1530 | Bulk download of the public bucket and the VPN profile |
| Cloud Storage Object Discovery | T1619 | ListObjects against the private bucket |
| Unsecured Credentials: Credentials In Files | T1552.001 | AWS keys committed in a script |
| Valid Accounts: Cloud Accounts | T1078.004 | Leaked IAM keys used from a second IP |
| Automated Collection | T1119 | Scripted retrieval of every object in the bucket |
A public bucket should never hold source. The repository was readable by anyone and was fetched in nine seconds. Block public access at the account level and alert on GetObject bursts from a single external IP.
Deleting a secret from a script does not remove it. The AWS keys were gone from the current file but intact in git history. Rotate any key that was ever committed and scan history with a secret scanner before publishing a repo.
Scope IAM keys tightly. One leaked user reached a private bucket from an unfamiliar IP. Restrict keys by source IP or role, and alert on a long-lived user key used from a new address.
Hard-coded locations in binaries leak your cloud layout. The bucket name sat in the strings of a malicious binary, so anything shipped or received carries it. Keep bucket names out of distributed tooling, and keep samples alongside CloudTrail so the first lead can be closed.
A public bucket leaked a repository, the repository leaked keys, and the keys opened a private bucket holding a VPN profile. One exposed bucket was enough to start the whole chain.