Bandit [Level 12 to Level 24] – OverTheWire Writeup

This post is the second part of the writeup with the solutions for Bandit from OverTheWire. This second entry will have solutions from level 12 to level 24. This part requires a little bit more knowledge about Linux and some tools.

If you haven’t checked the last post with solution from 0 to 12, you can do it here. You need to know that the level 12 key was get in the last entry.

Table of Contents

Level 12 -> 13

The password for the next level is stored in the file data.txt, which is a hexdump of a file that has been repeatedly compressed.

After logging into the server we just have a file called data.txt which has a hexdump data as the description says.

We can easily revert that data back to a file with xxd -r command and the save it to a file (in this case. inside a folder in /tmp path). From here, we can identify the kind of file with file command and take action depending on the file type.

The first one is a gzip file which can be decompressed with gzip -d command. Then, we get a compressed bzip2 file. What is required to do is to decompress files until we get a plain text file (ASCII).

$ ssh bandit11@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit12@bandit:~$ ls
data.txt
bandit12@bandit:~$ cat data.txt 
00000000: 1f8b 0808 8e0b bf63 0203 6461 7461 322e  .......c..data2.
00000010: 6269 6e00 013c 02c3 fd42 5a68 3931 4159  bin..<...BZh91AY
00000020: 2653 598c b471 f700 0014 ffff fa59 c6c5  &SY..q.......Y..
00000030: af63 cfff af73 ffff bdb7 7c9f b1fb eafa  .c...s....|.....
00000040: bfff fb9f f9fe bdbf ffeb ffef b001 3b2c  ..............;,
00000050: 5900 0341 a064 007a 8003 40d0 6869 a068  Y..A.d.z..@.hi.h
.
*** EDITED ***
.
bandit12@bandit:~$ mkdir /tmp/natryvat
bandit12@bandit:~$ cat data.txt | xxd -r > /tmp/natryvat/file.txt
bandit12@bandit:~$ cd /tmp/natryvat
bandit12@bandit:/tmp/natryvat$ file file.txt 
file.txt: gzip compressed data, was "data2.bin", last modified: Wed Jan 11 19:18:38 2023, max compression, from Unix, original size modulo 2^32 572
bandit12@bandit:/tmp/natryvat$ mv file.txt file.gz
bandit12@bandit:/tmp/natryvat$ gzip -d file.gz
bandit12@bandit:/tmp/natryvat$ ls
file
bandit12@bandit:/tmp/natryvat$ file file 
file: bzip2 compressed data, block size = 900k
bandit12@bandit:/tmp/natryvat$ bzip2 -d file
bzip2: Can't guess original name for file -- using file.out
bandit12@bandit:/tmp/natryvat$ ls
file.out

We can do this manually but I decided to do an script to automate the process.

You can see it in code below. It takes three different type of compressed files which were found in manual testing (you can try it by yourself): gzip, bzip2 and tar. In case of plain text it just print its content.

#!/bin/bash

filename=$1
echo "Filename: "$filename

while true; do
        header=$(file $filename)

        case $header in
                *"gzip"*)
                        echo " *** gzip *** "
                        aux=$(echo $filename | cut -d . -f 1)".gz"
                        mv $filename $aux
                        gzip -d $aux
                        filename=$(echo $aux | cut -d . -f 1)
                ;;
                *"bzip2"*)
                        echo " *** bzip2 *** "
                        bzip2 -d $filename
                        filename=$_".out"
                ;;
                *"tar"*)
                        echo " *** tar *** "
                        aux=$(tar -tf $filename)
                        tar -xf $filename
                        rm $filename
                        filename=$aux
                ;;
                *"ASCII"*)
                        cat $filename
                        exit 0
                ;;
                *)
                        exit 1
                ;;
        esac
#       echo "New file: "$filename
done

Finally, we just need to run the script providing the name of the original file which was extracted from hexdump file:

$ bandit12@bandit:/tmp/natryvat$ cat /home/bandit12/data.txt | xxd -r > /tmp/natryvat/file.txt
bandit12@bandit:/tmp/natryvat$ ./extract.sh file.txt 
Filename: file.txt
 *** gzip *** 
 *** bzip2 *** 
bzip2: Can't guess original name for file -- using file.out
 *** gzip *** 
 *** tar *** 
 *** tar *** 
 *** bzip2 *** 
bzip2: Can't guess original name for data6.bin -- using data6.bin.out
 *** tar *** 
 *** gzip *** 
The password is ***EDITED***
bandit11@bandit:~$ exit
logout
Connection to bandit.labs.overthewire.org closed.

Level 13 -> 14

The password for the next level is stored in /etc/bandit_pass/bandit14 and can only be read by user bandit14. For this level, you don’t get the next password, but you get a private SSH key that can be used to log into the next level.

On this server we can just find a file with a ssh private key. It’s easy to login via SSH without a password with these kind of files, we just need to provide it with -i flag.

In below code you can see localhost was used instead of domain or server IP, this is because we are already inside server we want to log in. Once we are in, we can read /etc/bandit_pass/bandit14 file:

$ ssh bandit13@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit13@bandit:~$ ls
sshkey.private
bandit13@bandit:~$ ssh bandit14@localhost -p 2220 -i sshkey.private 
The authenticity of host '[localhost]:2220 ([127.0.0.1]:2220)' can't be established.
ED25519 key fingerprint is SHA256:C2ihUBV7ihnV1wUXRb4RrEcLfXC5CXlhmAAM/urerLY.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes

Welcome to OverTheWire!

bandit14@bandit:~$ whoami
bandit14
bandit14@bandit:~$ cat /etc/bandit_pass/bandit14
*** EDITED ***
bandit14@bandit:~$ exit
logout
Connection to localhost closed.
bandit13@bandit:~$ exit
logout
Connection to bandit.labs.overthewire.org closed.

Level 14 -> 15

The password for the next level can be retrieved by submitting the password of the current level to port 30000 on localhost.

From the current session we just have to connect to port 30000, the easiest way is using telnet. The parameters are the host (localhost) and port, then provide current user key:

$ ssh bandit14@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit14@bandit:~$ telnet localhost 30000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
*** EDITED ***
Correct!
*** EDITED ***

Connection closed by foreign host.
bandit14@bandit:~$ exit
logout
Connection to bandit.labs.overthewire.org closed.

Level 15 -> 16

The password for the next level can be retrieved by submitting the password of the current level to port 30001 on localhost using SSL encryption.

Similar to the last one, but this time we have to user an SSL protected connection. This can be done with openssl, although there are other options. command should look as follows:

openssl s_client -connect localhost:30001

After executing it, it will show a long output which we can ignore, then we just need to provided curren user key and new one will be shown:

$ ssh bandit15@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit15@bandit:~$ openssl s_client -connect localhost:30001
CONNECTED(00000003)
Can't use SSL_get_servername
depth=0 CN = localhost
verify error:num=18:self-signed certificate
verify return:1
depth=0 CN = localhost
verify error:num=10:certificate has expired
notAfter=Jan 20 12:22:31 2023 GMT
verify return:1
depth=0 CN = localhost
notAfter=Jan 20 12:22:31 2023 GMT
verify return:1
---
*** EDITED ***
---
read R BLOCK
*** EDITED ***
Correct!
*** EDITED ***

closed
bandit15@bandit:~$ exit
logout
Connection to bandit.labs.overthewire.org closed.

Level 16 -> 17

The credentials for the next level can be retrieved by submitting the password of the current level to a port on localhost in the range 31000 to 32000.

This is an interesting level, it’s pretty similar to last two levels, but we don’t know the exact port nor if the connection uses SSL or not.

Due to this, I preferred to write an script to automate the process. What id does is the following:

  • Saves current user key in key variable.
  • Look for open ports in 31000-32000 range and send stderr to stdout (2>&1).
  • Show just those “succeeded” connections.
  • For each found port test sending key via unencrypted connection and using SSL.
#!/bin/bash

key="*** EDITED ***"

nc -zv localhost 31000-32000 2>&1 | grep succeeded | while read -r port; do
        port=$(echo $port | cut -d " " -f 5)
        echo "Testing Port "$port
        echo $key | nc localhost $port -q 1
        echo $key | openssl s_client -connect localhost:31790 -ign_eof
done

NOTE: openssl command adds -ign_eof to allow non-interactive usage.

Then, we just have to run the bash script:

$ ssh bandit16@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit16@bandit:~$ cd /tmp/natryvat
bandit16@bandit:/tmp/natryvat$ ls
bandit17.sh
bandit16@bandit:/tmp/natryvat$ ./bandit17.sh 
Testing Port *** EDITED ***
.
.
.
*** EDITED ***
.
.
.
---
read R BLOCK
Correct!
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvmOkuifmMg6HL2YPIOjon6iWfbp7c3jx34YkYWqUH57SUdyJ
imZzeyGC0gtZPGujUSxiJSWI/oTqexh+cAMTSMlOJf7+BrJObArnxd9Y7YT2bRPQ
.
*** EDITED ***
.
dxviW8+TFVEBl1O4f7HVm6EpTscdDxU+bCXWkfjuRb7Dy9GOtt9JPsX8MBTakzh3
vBgsyi/sN3RqRBcGU40fOoZyfAMT8s1m/uYv52O6IgeuZ/ujbjY=
-----END RSA PRIVATE KEY-----

closed
bandit16@bandit:/tmp/natryvat$ 

The correct attempt shows “Correct!” in output. It doesn’t show any key as usual but a RSA private key. Most probably it’s a SSH private key, so I saved it and tested it with bandit17 user and it worked.

To get the new key, we can just read it from /etc/bandit_pass folder:

bandit16@bandit:/tmp/natryvat$ ls
bandit17.sh  key
bandit16@bandit:/tmp/natryvat$ chmod 600 key
bandit16@bandit:/tmp/natryvat$ ssh bandit17@localhost -i key -p 2220

Welcome to OverTheWire!

bandit17@bandit:~$ cat /etc/bandit_pass/bandit17 
*** EDITED ***
bandit17@bandit:~$ exit
logout
Connection to localhost closed.
bandit16@bandit:/tmp/natryvat$ exit
logout
Connection to bandit.labs.overthewire.org closed.

Level 17 -> 18

There are 2 files in the homedirectory: passwords.old and passwords.new. The password for the next level is in passwords.new and is the only line that has been changed between passwords.old and passwords.new.

This is an easy one. We see just to files, we just have to check for their differences. diff command can be used for it, the needed parameters are the two files to compare:

$ ssh bandit17@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit17@bandit:~$ ls
passwords.new  passwords.old
bandit17@bandit:~$ diff passwords.old passwords.new 
42c42
< 810zq8IK64u5A9Lb2ibdTGBtlcSZsoe8
---
> *** EDITED ***
bandit17@bandit:~$ exit
logout
Connection to bandit.labs.overthewire.org closed.

Level 18 -> 19

The password for the next level is stored in a file readme in the homedirectory. Unfortunately, someone has modified .bashrc to log you out when you log in with SSH.

This level is a little bit tricky. We can login but we are logged out immediately. Fortunately, there is ssh feature which allows to execute a command when logging in, this is provided with -t flag, so we can list files in current folder and even read any file just before we are logged out:

$ ssh bandit18@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

Byebye !
Connection to bandit.labs.overthewire.org closed.
$ ssh bandit18@bandit.labs.overthewire.org -p 2220 -t "ls"
bandit18@bandit.labs.overthewire.org's password: 
readme
Connection to bandit.labs.overthewire.org closed.
$ ssh bandit18@bandit.labs.overthewire.org -p 2220 -t "cat readme"
bandit18@bandit.labs.overthewire.org's password: 
*** EDITED ***
Connection to bandit.labs.overthewire.org closed.

Level 19 -> 20

To gain access to the next level, you should use the setuid binary in the homedirectory.

We have a setuid binary which means we could execute a command as another user (bandit20). So, we can read its key from /etc/bandit_pass:

$ ssh bandit19@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit19@bandit:~$ ls -l
total 16
-rwsr-x--- 1 bandit20 bandit19 14876 Jan 11 19:18 bandit20-do
bandit19@bandit:~$ ./bandit20-do 
Run a command as another user.
  Example: ./bandit20-do id
bandit19@bandit:~$ ./bandit20-do whoami
bandit20
bandit19@bandit:~$ ./bandit20-do cat /etc/bandit_pass/bandit20 
*** EDITED ***
bandit19@bandit:~$ exit
logout
Connection to bandit.labs.overthewire.org closed.

Level 20 -> 21

There is a setuid binary in the homedirectory that does the following: it makes a connection to localhost on the port you specify as a commandline argument. It then reads a line of text from the connection and compares it to the password in the previous level (bandit20).

Now, we have an executable which asks for a port and make a connection to it, when port 22 was specified for testing, the SSH banner was the input to the executable, which was marked as incorrect. The difficult lies on the description of the level. It might should like we have to find a port to read some data from it, but let’s investigate the executable with strace:

$ ssh bandit20@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit20@bandit:~$ ls
suconnect
bandit20@bandit:~$ ./suconnect 
Usage: ./suconnect <portnumber>
This program will connect to the given port on localhost using TCP. If it receives the correct password from the other side, the next password is transmitted back.
bandit20@bandit:~$ ./suconnect 22
Read: SSH-2.0-OpenSSH_8.9p1
ERROR: This doesn't match the current password!
bandit20@bandit:~$ strace ./suconnect 1111
execve("./suconnect", ["./suconnect", "1111"], 0x7fffffffe4e8 /* 25 vars */) = 0
[ Process PID=4183835 runs in 32 bit mode. ]
access("/etc/suid-debug", F_OK)         = -1 ENOENT (No such file or directory)
.
.
.
*** EDITED ***
.
.
.
close(3)                                = 0
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(1111), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
recv(3, ^C0xffffd3f0, 100, 0)             = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
strace: Process 4183835 detached

As we can see almost at the end of the output, the executable tries to do a connection to localhost in the specified port with connect function, which could be any.

What we have to do is to open a port by ourselves with the required text (current user key). I opened 2222 (it could be any other) port with netcat (nc) and ran the command as asynchronous (&) so I could run more commands. Then, I just executed the binary providing the same port as parameter (2222) and got the next key:

bandit20@bandit:~$ echo "*** EDITED ***" | nc -l -p 2222 &
[1] 4192214
bandit20@bandit:~$ ./suconnect 2222
Read: *** EDITED ***
Password matches, sending next password
*** EDITED ***
[1]+  Done                    echo "*** EDITED ***" | nc -l -p 2222
bandit20@bandit:~$ exit
logout
Connection to bandit.labs.overthewire.org closed.

Level 21 -> 22

A program is running automatically at regular intervals from cron, the time-based job scheduler. Look in /etc/cron.d/ for the configuration and see what command is being executed.

When talking about automated tasks on Linux, we are talking about cron. As per task description we should check on /etc/cron.d/, we can see different files but we need the one related to bandit22 user.

File cronjob_bandit22 describes the execution of /usr/bin/cronjob_bandit22.sh file each minute. Now, that script read bandit22 key and stores it in a file in tmp directory. So, we can just read it from there:

$ ssh bandit21@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit21@bandit:~$ ls
bandit21@bandit:~$ cd /etc/cron.d
bandit21@bandit:/etc/cron.d$ ls
cronjob_bandit15_root  cronjob_bandit22  cronjob_bandit24       e2scrub_all  sysstat
cronjob_bandit17_root  cronjob_bandit23  cronjob_bandit25_root  otw-tmp-dir
bandit21@bandit:/etc/cron.d$ cat cronjob_bandit22 
@reboot bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
* * * * * bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
bandit21@bandit:/etc/cron.d$ ls -la /usr/bin/cronjob_bandit22.sh 
-rwxr-x--- 1 bandit22 bandit21 130 Jan 11 19:18 /usr/bin/cronjob_bandit22.sh
bandit21@bandit:/etc/cron.d$ cat /usr/bin/cronjob_bandit22.sh 
#!/bin/bash
chmod 644 /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
cat /etc/bandit_pass/bandit22 > /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
bandit21@bandit:/etc/cron.d$ cat /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
*** EDITED ***
bandit21@bandit:/etc/cron.d$ exit
logout
Connection to bandit.labs.overthewire.org closed.

Level 22 -> 23

A program is running automatically at regular intervals from cron, the time-based job scheduler. Look in /etc/cron.d/ for the configuration and see what command is being executed.

This one is similar to last one, but we need to read files related to bandit23 user.

We don’t have the destination file in clear text, but we have to investigate it by ourselves reading the script. The easiest way is to replicate commands in the CLI and get destination filename, the only change we have to made manually is to set “myname” variable to “bandit23“, then just print “mytarget” value to console (echo $myvalue). We now know the destination file and we can read it:

$ ssh bandit22@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit22@bandit:~$ cd /etc/cron.d/
bandit22@bandit:/etc/cron.d$ ls
cronjob_bandit15_root  cronjob_bandit22  cronjob_bandit24       e2scrub_all  sysstat
cronjob_bandit17_root  cronjob_bandit23  cronjob_bandit25_root  otw-tmp-dir
bandit22@bandit:/etc/cron.d$ cat cronjob_bandit23 
@reboot bandit23 /usr/bin/cronjob_bandit23.sh  &> /dev/null
* * * * * bandit23 /usr/bin/cronjob_bandit23.sh  &> /dev/null
bandit22@bandit:/etc/cron.d$ cat /usr/bin/cronjob_bandit23.sh 
#!/bin/bash

myname=$(whoami)
mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1)

echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget"

cat /etc/bandit_pass/$myname > /tmp/$mytarget
bandit22@bandit:/etc/cron.d$ myname="bandit23"
bandit22@bandit:/etc/cron.d$ mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1)
bandit22@bandit:/etc/cron.d$ echo $mytarget
8ca319486bfbbc3663ea0fbe81326349
bandit22@bandit:/etc/cron.d$ cat /tmp/8ca319486bfbbc3663ea0fbe81326349
*** EDITED ***
bandit22@bandit:/etc/cron.d$ exit
logout
Connection to bandit.labs.overthewire.org closed.

Level 23 -> 24

A program is running automatically at regular intervals from cron, the time-based job scheduler. Look in /etc/cron.d/ for the configuration and see what command is being executed.

Once again, we have to check for cron tasks, now from bandi24 user. This time, we got a more advanced script. It does the following:

  • It moves to /var/spool/bandit24/foo folder.
  • It will iterate over all files inside the folder.
  • It continues if file is not current or parent directory.
  • It saves the owner of the file and checks check if it belongs to bandit23 (our current user).
  • File is executed between second 9 and 60, (for cron file it should execute each minute).
  • Deletes the file.
$ ssh bandit23@bandit.labs.overthewire.org -p 2220
bandit11@bandit.labs.overthewire.org's password: 

Welcome to OverTheWire!

bandit23@bandit:~$ cd /etc/cron.d/
bandit23@bandit:/etc/cron.d$ ls
cronjob_bandit15_root  cronjob_bandit22  cronjob_bandit24       e2scrub_all  sysstat
cronjob_bandit17_root  cronjob_bandit23  cronjob_bandit25_root  otw-tmp-dir
bandit23@bandit:/etc/cron.d$ cat cronjob_bandit24 
@reboot bandit24 /usr/bin/cronjob_bandit24.sh &> /dev/null
* * * * * bandit24 /usr/bin/cronjob_bandit24.sh &> /dev/null
bandit23@bandit:/etc/cron.d$ cat /usr/bin/cronjob_bandit24.sh 
#!/bin/bash

myname=$(whoami)

cd /var/spool/$myname/foo
echo "Executing and deleting all scripts in /var/spool/$myname/foo:"
for i in * .*;
do
    if [ "$i" != "." -a "$i" != ".." ];
    then
        echo "Handling $i"
        owner="$(stat --format "%U" ./$i)"
        if [ "${owner}" = "bandit23" ]; then
            timeout -s 9 60 ./$i
        fi
        rm -f ./$i
    fi
done
bandit23@bandit:/etc/cron.d$ ls -ld /var/spool/bandit24/foo/
drwxrwx-wx 4 root bandit24 4096 Jan 22 08:12 /var/spool/bandit24/foo/

As in previous tasks, the easiest way to get next key is to copy it from /etc/bandit_pass/ folder to any file under /tmp. As checked anyone can write files into /var/spool/bandit24/foo/ and as per script any file will be executed, so it can have any name. To get flag and based in lasts tasks I wrote following script:

#!/bin/bash

cat /etc/bandit_pass/bandit24 > /tmp/natryvat_bandit24

After creating file and make sure to provide executing permission (chmod +x <file>), we just need to wait one minute (as per cron) and then just read the created file under /tmp:

bandit23@bandit:/etc/cron.d$ nano /var/spool/bandit24/foo/foo.txt
Unable to create directory /home/bandit23/.local/share/nano/: No such file or directory
It is required for saving/loading search history or cursor positions.

bandit23@bandit:/etc/cron.d$ chmod +x /var/spool/bandit24/foo/foo.txt
bandit23@bandit:/etc/cron.d$ ls -la /tmp/natryvat_bandit24
ls: cannot access '/tmp/natryvat_bandit24': No such file or directory
bandit23@bandit:/etc/cron.d$ ls -la /tmp/natryvat_bandit24
-rw-rw-r-- 1 bandit24 bandit24 33 Jan 22 08:49 /tmp/natryvat_bandit24
bandit23@bandit:/etc/cron.d$ cat /tmp/natryvat_bandit24
*** EDITED ***
bandit23@bandit:/etc/cron.d$ exit
logout
Connection to bandit.labs.overthewire.org closed.

As of might realized, the levels were more difficult than last post entry. Please wait for the next and last Bandit levels solutions in an upcoming blog entry.