Chris Long

10 minute read

Question 21 (220)

AWS access keys consist of two parts: an access key ID (e.g., AKIAIOSFODNN7EXAMPLE) and a secret access key (e.g., wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY). What is the secret access key of the key that was leaked to the external code repository?

No SPL needed here. Answer this question by following the link to the Github commit mentioned in the email from Question 20.

Question 21 answer:

Question 22 (221)

Using the leaked key, the adversary makes an unauthorized attempt to create a key for a specific resource. What is the name of that resource? Answer guidance: One word.

NOTE: Contains spoilers for question 19!

We know the access key ID from the email, and it’s likely that creating a key for a resource is done using the IAM service. Let’s craft a query:

index=botsv3 earliest=0 sourcetype="aws:cloudtrail" AKIAJOGCDXJ5NW5PXUPA

We can look at the eventName field and see that that there is 1 CreateAccessKey event. Let’s add that to the filter:

index=botsv3 earliest=0 sourcetype="aws:cloudtrail" AKIAJOGCDXJ5NW5PXUPA eventName=CreateAccessKey

The answer is in the errorMessage field.

Question 22 answer:

Question 23 (222)

Using the leaked key, the adversary makes an unauthorized attempt to describe an account. What is the full user agent string of the application that originated the request?

This is very similar to the last question. We’re not looking for a CreateAccessKey event, but describing an account still falls under IAM. GetUser seems like it’s probably related to descibing an account, let’s look there:

index=botsv3 earliest=0 sourcetype="aws:cloudtrail" AKIAJOGCDXJ5NW5PXUPA eventName=GetUser

The answer is in the userAgent field.

Question 23 answer:

Question 24 (223)

The adversary attempts to launch an Ubuntu cloud image as the compromised IAM user. What is the codename for that operating system version in the first attempt? Answer guidance: Two words.

The email from AWS contained the username of the compromised user, but it’s also listed in the event from Question 23: web_admin. Launching a cloud image is going to fall under the EC2 service. Let’s craft a new query using both the access key id and username:

index=botsv3 earliest=0 sourcetype="aws:cloudtrail" (AKIAJOGCDXJ5NW5PXUPA OR web_admin)

I initially tried adding “Ubuntu” to that query, but it predictably didn’t return anything. These events don’t contain formation about the OS, but they do contain the ID of the EC2 AMI being launched.

I initially looked at the requestParameters.instancesSet.items{}.imageId field and saw 15 different values. That’s not helpful! However, I also happened to notice the region field also had 15 different values. It looks like our attacker tried to spin up AMIs in 15 different regions, and even the same OS will have a different AMI-ID in different regions!

You can solve this one using some Google-fu, or to get a more precise answer, use the AWS-CLI. I decided to drill down on the eu-west-1 region, which gave a resulting AMI-ID of ami-4d46d534. We can query the details about this AMI:

$ aws ec2 describe-images --image-ids ami-4d46d534 --region=eu-west-1

"Description": "Canonical, Ubuntu, 16.04 LTS, amd64 xenial image build on 2018-01-09"

Almost there, we just need to look up the two-part codename of the Xenial build. I used Google for this.

Question 24 answer:
Xenial Xerus

Question 25 (224)

Frothly uses Amazon Route 53 for their DNS web service. What is the average length of the distinct third-level subdomains in the queries to Answer guidance: Round to two decimal places. (Example: The third-level subdomain for is example.)

I can already tell this is going to be a great question to demonstrate the power of SPL. I don’t know where Route 53 logs go off the top of my head, so I read up on it a bit.

It looks like they go into cloudwatch, and I see a lambda:dns source inside of the aws:cloudwatchlogs sourcetype. I’m going to operate on that dataset with the assumption that’s the correct one.

I’m using this query for now: index=botsv3 earliest=0 source=lambda:dns *

Now we need to isolate the third-level subdomain field. Unfortunately for us, there is no pre-existing field for the domain, so we’ll just write one with regex:

index=botsv3 earliest=0 source=lambda:dns * 
| rex field=_raw "Z149R7NEBZTKPN\s(?<query>[^\s]+)" 

I verify that all of the fields in this source contain the string “Z149R7NEBZTKPN”, so I know it’s safe to use while building my regex. In the regex, I’m telling Splunk the string will begin with Z149R7NEBZTKPN\s (but not to include that in my field) and to create a new field (query) that consists of all characters except a whitespace character (\s). No domains contain spaces, so we know once we hit a space, we’re done parsing the domain. I spot check the results in a table just to verify:

index=botsv3 earliest=0 source=lambda:dns * 
| rex field=_raw "Z149R7NEBZTKPN\s(?<query>[^\s]+)" 
| table query

Looks good. Now we only care about the third-level subdomains, so let’s craft a regex for those and put it into a new field:

  1. Operate off of the query field we just created
  2. There may or may not be a leading period (.) in the domain, so we’ll make the period an optional by adding ?.
  3. We’ll create a new field called “third_level_subdomain” and we’ll capture the contents by grabbing everything inbetween the periods before “”.
index=botsv3 earliest=0 source=lambda:dns * 
| rex field=_raw "Z149R7NEBZTKPN\s(?<query>[^\s]+)" 
| rex field=query "\.?(?<third_level_subdomain>[^\.]+)" 

Now we need the length of each domain, which we can do using eval and the len() function:

index=botsv3 earliest=0 source=lambda:dns * 
| rex field=_raw "Z149R7NEBZTKPN\s(?<query>[^\s]+)" 
| rex field=query "\.?(?<third_level_subdomain>[^\.]+)" 
| eval subdomain_length=len(third_level_subdomain)

And lastly, we want the average length of those fields:

index=botsv3 earliest=0 source=lambda:dns * 
| rex field=_raw "Z149R7NEBZTKPN\s(?<query>[^\s]+)" 
| rex field=query "\.?(?<third_level_subdomain>[^\.]+)" 
| eval subdomain_length=len(third_level_subdomain)
| stats avg(subdomain_length)

I check my answer, and WRONG. I re-read the question again and notice something I initially glossed over “the distinct third-level subdomains”. Gah! No worries, this is an easy fix. To get the distinct third level domains, we’ll just use the dedup command after we extract the third_level_subdomain field:

index=botsv3 earliest=0 source=lambda:dns * 
| rex field=_raw "Z149R7NEBZTKPN\s(?<query>[^\s]+)" 
| rex field=query "\.?(?<third_level_subdomain>[^\.]+)" 
| dedup third_level_subdomain
| eval subdomain_length=len(third_level_subdomain)
| stats avg(subdomain_length)`

After I round to two decimal places, my answer is now correct!

Question 25 answer:

Question 26 (225)

Using the payload data found in the memcached attack, what is the name of the .jpeg file that is used by Taedonggang to deface other brewery websites? Answer guidance: Include the file extension.

Ok, a lot to unpack here.

the memcached attack

Uh, which memcached attack? I remember a lot about it being used for DDoS amplification in 2018…



deface other brewery websites

That might explain the miner hosted on, but is that the same incident as the defacement?

Time to start swinging at fences with string searches: index=botsv3 earliest=0 memcached OR taedonggang

After some basic filtering of sourcetypes, we see some references to memcache configs: index=botsv3 earliest=0 memcached OR taedonggang sourcetype!=ps sourcetype!=top sourcetype!=lsof

I didn’t get too far with this route, so I started following up on some of the email threads hoping they might contain references to the .jpeg referenced in the question. In the stream:smtp, I find an email with a subject of “Postmortem on our issue with brewertalk” and use the same technique to base64 decode the body content as question 18. I find some very interesting clues, namely:

Well, I messed up! I had a “open public bucket” accidentally. It looks like some malicious people got into our code and added in something called a “coinminer” which made it so that anyone browsing to our site would generate some cryptocurrency. That’s why some browsers were hitting 100% CPU! I closed down the bucket and cleaned up the code, and will bring brewertalk back on line soon.

Hey Frothlies! I just added some great improvements to to better handle forum threads and allow for posts of multi-media kinds of files rather than just photos. And it’s all running in our new swanky AWS environment (“the cloud” for those of you not sure what that is!) I think you’ll be impressed enough to maybe buy me a beer! Let me know.

It sounds like in addition to Bud adding this feature, he probably also introduced a unrestricted file upload vulnerability.

So, now we know that the miner ended up on the website due to the open bucket, but none of this has anything to do with memcached.

I keep searching and find reference to a cloudtrail RevokeSecurityGroupIngress event where the description is Fixing access to memcached. This leads me to think that maybe memcached was inadvertently exposed at some point. I decide to pivot off of port 11211 because searches for memcache and memcached aren’t turning up anything obvious.

index=botsv3 earliest=0 11211

The first event stuns me:

It definitely looks like someone is messing around with memcache based on the traffic we’re seeing from the stream:udp sourcetype traffic!

index=botsv3 earliest=0 11211 
| table src_ip, src_content, dest_content 
| search src_content=* OR dest_content=*

I pull up a memcached cheatsheet because I know next to nothing about memcache. However, it looks like someone just poked around to look at server status and create some keys for defacement. This likely had no impact on the website itself.

I try pivoting off the attacker IP (, but nothing but UDP traffic shows up.

I came up empty here. I honestly have no idea.

Question 26 answer:

Question 27 (300)

What is the full user agent string that uploaded the malicious link file to OneDrive?

I remember seeing references to azure sourcetypes, so maybe it got virus scanned during the upload and we can find an alert.

index=botsv3 earliest=0 onedrive (malicious OR virus)

Woot! 3 hits.

Looking at the event in the symantec:ep:risk:file shows the filename is C:\Users\BruceGist\OneDrive - Frothly\Birthday Pictures\Bruce Birthday Happy Hour Pics.lnk and was detected as Backdoor.PsEmpire. Let’s see if there’s an audit trail of it being uploaded:

index=botsv3 earliest=0 "Bruce Birthday Happy Hour Pics.lnk" | reverse

I use reverse here so the event that occurred first will be displayed at the top. Sure enough, there’s a FileUploaded operation and the UserAgent is in the event as well.

Question 27 answer:
Mozilla/5.0 (X11; U; Linux i686; ko-KP; rv: 19.1br) Gecko/20130508 Fedora/1.9.1-2.5.rs3.0 NaenaraBrowser/3.5b4

Question 28 (301)

What external client IP address is able to initiate successful logins to Frothly using an expired user account?

Yikes, what service allows users with expired user accounts to login (unless the expiration gets modified?). Let’s dig in on that clue.

index=botsv3 earliest=0 (expired OR expiration)

I think I got a little bit lucky on this one. I saw the single event in the ms:aad:signin sourcetype that contained the string expired and just checked if that ipAddress was the correct answer. However, the event clearly says loginStatus: Failure, so I’m not quite sure what happened here.

Question 28 answer:

Question 29 (302)

According to Symantec’s website, what is the discovery date of the malware identified in the macro-enabled file? Answer guidance: Provide the US date format MM/DD/YY. (Example: January 1, 2019 should be provided as 01/01/19)

Another Google-fu question. Let’s find the macro, first! I chose to do a massively broad search, and it took awhile to run, but it was worth it!

index=botsv3 earliest=0 *macro*

Eventually I stumbled across an email with the following content in the body:

Here is a financial model we can use for FY2019 planning. For the worksheet to operate properly, you will need to enable macros.

The attachment name was “Malware Alert Text.txt” and decoding the base64 in the content field shows us the filename and threat:

$ echo 'TWFsd2FyZSB3YXMgZGV0ZWN0ZWQgaW4gb25lIG9yIG1vcmUgYXR0YWNobWVudHMgaW5jbHVkZWQg
> d2l0aCB0aGlzIGVtYWlsIG1lc3NhZ2UuIA0KQWN0aW9uOiBBbGwgYXR0YWNobWVudHMgaGF2ZSBi
> ZWVuIHJlbW92ZWQuDQpGcm90aGx5LUJyZXdlcnktRmluYW5jaWFsLVBsYW5uaW5nLUZZMjAxOS1E
> cmFmdC54bHNtCSBXOTdNLkVtcHN0YWdlDQo=' | base64 -d
Malware was detected in one or more attachments included with this email message.
Action: All attachments have been removed.
Frothly-Brewery-Financial-Planning-FY2019-Draft.xlsm	 W97M.Empstage

Time for some Googling about this threat. Ironically, I find the page and their webserver is throwing errors!

An error occurred while processing your request.
Reference #97.456cd317.1593676418.7941a2e

Great. No Google cache for this page, either. However, I discover you can guess near the date the article was published and get the right answer :)

Question 29 answer:

Question 30 (303)

What is the password for the user that was successfully created by the user “root” on the on-premises Linux system?

Talk about a lucky first query!

index=botsv3 earliest=0  (useradd OR adduser) (root OR uid=0)

This host has osquery process auditing enabled, which means all commandline arguments will be captured. I knew that the most common way to add a user was via the useradd and adduser commands, and we know this command was launched by root, so searching for root or uid=0 should help narrow it down.

Question 30 answer:
comments powered by Disqus