Playing Hacks and Stuffs!
Good day, here I’ll be giving the writeup to the few challenges I was able to solve during the ctf
Just the standard “sanity check” challenge, going over to their discord challenge in the “announcement” group gives the flag
Flag: openECSC{ldepywBS5XUBYHLeVDo6+mK7iFHFhwhwY0+LjR3R9EI=}
We are given a pcap file and after downloading the attachment I opened it up in Wireshark
The first noticable thing is that this contains http requests
And on looking at the protocol hierachy shows it contains mostly http request
Following the tcp stream I saw that it’s basically solving a web based maze challenge where direction start
initializes the game and gives us a session cookie and up, down, left & right
are the paths used to move around the maze
At the last http request that is after solving the maze finish the flag is shown to us
Apparently this didn’t involve us solving it at our end and it turned out to be an unintended solution so they released a revenge
sequel of the challenge
Flag: openECSC{i_found_a_map_e1871a60}
It’s very identical to the previous one but this time around the flag isn’t in the pcap file meaning we would need to solve it
During the process of trying to solve it I spent quite some time on it because i didn’t bother to understand how the maze worked
Here’s what I did:
This didn’t work then I started debugging
From looking through various packets I concluded that when a user sends a path to the server there’s a possibility of it being not processed which then the user needs to resend the path
Here’s how that failed chance looks here
So if we get that we then need to resend the path as shown here
With that said my initial path which I extracted contained repetitive paths i.e the path which are valid and the ones which are repeated again due to failure
In order to extract just the valid path I checked for the case where by the path given fails
With that said it’s then simple to solve
Another thing is that we can get the “failed” message multiple times but that can be easily fixed by just resending the path till it works
Here’s the script I used to extract the paths from the pcap file: extract
import re
def extract_paths(content):
pattern = r'Last Move: (.+?)</h4>'
paths = re.findall(pattern, content)
return paths
def filter_successful_paths(paths):
return [path for path in paths if "FAILED" not in path]
file = ["response1.txt", "response2.txt", "response3.txt"]
exp = []
for f in file:
with open(f, "r") as file:
content = file.read()
path = extract_paths(content)
filtered = filter_successful_paths(path)
exp += filtered
exp.append('right') # --> it kinda missed this part which was the last value of the maze path
with open("direction.txt", "w") as f:
for line in exp:
f.write(line + '\n')
And finally the solve script
import requests
import tqdm
import time
path = []
with open('direction.txt', 'r') as f:
path = [line.strip() for line in f]
url = 'http://blindmazerevenge.challs.open.ecsc2024.it/maze?direction='
print(path)
with requests.Session() as session:
response = session.get(url + 'start')
for value in tqdm.tqdm(path):
response = session.get(url + value)
if 'Last Move: FAILED because the maze was busy. Try the move again!' in response.text:
while 'Last Move: FAILED because the maze was busy. Try the move again!' in response.text:
response = session.get(url + value)
time.sleep(0.5)
print(response.text)
Running the script works and we get the flag
Flag: openECSC{flag_inside_the_attachment_yes_we_like_it_bb01b0d5}
We are given an executable file and running it to get an overview of what it does shows this
This seems like we would need to find the expected input inorder to get the right output
Throwing it into a decompiler which in my case IDA gave something like this
Note that’s the decompilation from using dogbolt
IDA didn’t decompile it well and from looking at the control flow graph i saw it had just so many branches
And looking at the disassembly in gdb shows lots of nops
& add
instruction
I suppose this is preventing us from reversing it as the challenge name implies
In order to get around this I first thought of patching the nops & add
with a ret
but then I remember this can actually be quite the job for angr
So yeah I just grabbed a sample template from here
In our case the win condition is if we get “Correct!” and we would like to avoid any path that would lead to us getting “Wrong!”
Here’s the solve script
Running it gives the flag
Flag: openECSC{f4nCy_n0p5!_745fb2f2}
We are given a web instance to connect to and on going there shows this
We can’t view anything as we are not authenticated
Since there’s a login button at the left edge i clicked on it
It surprisingly worked without even asking for any form of authentication 🤔
We can view the availble laundries or amenities
I didn’t see anything of interest here
Checking the session storage shows this
The value of admin
is set to 0
we probably want it to be 1
inorder to view other things?
As of now changing it from the client side won’t really do much at the server side so let’s take a look at the request handling the login
I logged out then relogin while intercepting the request
First it does a GET
request to the creds
api endpoint
Intercepting the response to the request shows the client_id & client_secret
The next request shows this
This is making a connection via openid
which is an interoperable authentication protocol based on the OAuth 2.0 framework src
And looking at the parameters passed as the query I saw this
scope=openid laundry amenities
We can assume that this scope determines what we as an authenticated user would have access to
I appended admin
to it since that happens to be a key
value in the session storage
The request seems to work because it was able to generate the access token bearer
But after doing that I still noticed the admin
value in the session is 0
Weird.
So i decided to look for endpoint that might be useful
I viewed the page source and saw it’s importing a js file
./_app/immutable/entry/app.Ck9duSk9.js
I accessed it from the browser but the result looks bad
We can easily js beautifier it
Scrolling down the js file shows a new endpoint
const ae = [() => v(() => import("../nodes/0.DwINNMcl.js"), __vite__mapDeps([0, 1, 2, 3, 4, 5, 6, 7, 8]), import.meta.url), () => v(() => import("../nodes/1.COoiP6uJ.js"), __vite__mapDeps([9, 1, 2, 10, 6]), import.meta.url), () => v(() => import("../nodes/2.CngkQmog.js"), __vite__mapDeps([11, 1, 2, 4, 12, 6, 7]), import.meta.url), () => v(() => import("../nodes/3.D75S8dhM.js"), __vite__mapDeps([13, 1, 2, 3, 4]), import.meta.url), () => v(() => import("../nodes/4.LaKRB8Xm.js"), __vite__mapDeps([14, 1, 2, 12, 4, 15, 5]), import.meta.url), () => v(() => import("../nodes/5.BpDOC5ki.js"), __vite__mapDeps([16, 1, 2, 12, 4, 15, 5]), import.meta.url)],
le = [],
fe = {
"/": [2],
"/admin": [3],
"/amenities": [4],
"/laundry": [5]
},
ce = {
handleError: ({
error: a
}) => {
console.error(a)
},
reroute: () => {}
};
export {
fe as dictionary, ce as hooks, re as matchers, ae as nodes, oe as root, le as server_loads
};
Let’s try accessing /admin
It shows a generate button
I confirmed if this is accessible from a normal user and it is but when clicking the button nothing happens
On the other hand since we have admin as part of the scope it works
But from the content of what’s generated we would like to maybe change it
Looking at the request that handles the pdf generation shows this
Unfortunately the body isn’t in the request and we would like to maybe edit it?
I started searching through various js files
And eventually I found a js file that handles the pdf generation
I can’t find it again and i’m lazy to start searching through the whole js files (yes i did this manually 💀)
The body expected was:
{
"requiredBy": "John Doe"
}
I tried this and it worked
At this point we need to exfiltrate the flag
I was familiar with a technique which can be used then all I needed was just to search it up
From searching it I found this
In my case I used the iframe
tag
<iframe src=file:///flag.txt></iframe>
Using that works and I got the flag
Flag: openECSC{On3_l4uNdrY_70_ruL3_7h3m_4l1!_d208a530}
Thanks for reading!