Let's download the file in mentioned the description.
The file must be a file encrypted by the ransonware.
The homepage is a classic ransonware page, there is a timer and a BTC address (bc1q989cy4zp8x9xpxgwpznsxx44u0cxhyjjyp78hj).
On the source of the HTML page we can see this chunk of JavaScript code:
Let's add the domain name in /etc/hosts.
Looking at HTTP headers the application web server is Express (NodeJS) and there is a countdown cookie and the session cookie is containing our IP address encoded in base64.
Let's get back to the base64 encoded session cookie. Since it contains an IP address we can assume there will be some kind of SSRF.
I injected some payloads without success. At this point, I won't wanna lie, I consulted the write-up.
So you add to guess there is a blind XSS on an internal backend and that double quote are filtered.
Note : I noticed none of the third-party write-ups were explaining why they tried a blind XSS payload there or how they figured it out, so I assume they were probably blocked like my and read the author write-up.
Rather than injecting a full script, it will be more flexible to inject an entrypoint loading an external resource that we can modify at will without modifying the initial payload.
Just base64 encode it and replace the session cookie value with ctf-party.
Then start a HTTP server to serve noraj.js.
Then we need a data grabber to extract valuable information.
Note : reading other write-ups I have seen everyone using some XHR. But why would you use the horrible syntax of XMLHttpRequest()? It's so old it was already supported on Chrome and Firefox version 1, yes version 1. Fortunately, anyone with a bit of client-side web knowledge knows that nowadays you can use the nicer fetch() method instead. Do you still use Internet Explorer? No? So please stop using XHR too. It's like continuing to use mono-threaded dirb in 2023 while ffuf exists…
So to (try to) steal cookies, I used this payload in noraj.js:
But fetch wouldn't work here 😡 So I have to use a XHR payload to understand why. 😡
I have my idea but I won't judge before seeing it. So here a payload to grab the user agent.
According to Can I use only IE would not support fetch but the machine must not be a windows server. So the other explanation is that because the driven-browser framework used for the challenge has a quite poor real browser level support. Now we have fetched the User-Agent we know the challenge app is using PhantomJS 2.1 according to https://user-agents.net/s/L2AB. But PhantomJS is terrible, deprecated and abandoned. fetch() is not part of ECMAscript (JavaScript) but of the Web platform API (defined by WHATWG and W3C) like most of the BOM (BrowserObjectModel) and PhantomJS does not supports Promise. Yet another example of poor challenge implementation which make it very far from real life. The author should have used a more robust headless browser control library like Selenium or Puppeteer. And there is no excuse, PhantomJS was already abandoned and lacking of modern features in 2020 when the room was released.
So in real life you would use fetch() but on this CTFfy challenge you are forced to use old XHR. 😡 If you can't learn new things, let's rediscover the past then.
This is the part where you are supposed to create a huge XHR requests, trying to enumerate everything, base64 encode everything, and doing it the hard way because the player is supposed to be a mid-level web hacker so doing it the hard way make you learn stuff. But what if I have already done that shit tons of time and that I bored of it? What if I'm a web expert? I'll just use a Beef hook, because in real life cybercriminals would use a beef-like C2 framework to exploit a blind XSS on a large victim range to be able to scale and automate. An auditor would do the same but to save time. So no, I refuse to do it the stupid way like the author intends.
However, we still have the same issue, the deprecated PhantomJS doesn't seem to support the BeeF hook.
So let's get back to XHR…
Let's make a small web server data grabber:
With PhantomJS the post doesn't work…
With PhantomJS the string interpolation syntax doesn't work…
With PhantomJS string concatenation… works!
We can decode the received request URL with ctf-party:
From the data leak we had earlier, we know a Hasura GraphQL endpoint and the associated admin credentials.
So first I retried [GraphQL Voyager] introspection query, and then I minified it with a CodePen snippet.
Resulting in:
Then we can send the introspection query as an admin, retrieve the result, base64 it and exfiltrate it to our server:
The Hasura endpoint seems to not support the introspection query from GraphQL voyager (or the minifying goes wrong) but testing for something minimal like {__schema{types{name}}} works.
Pseudo minifying manually just remove newline didn't work either. And replacing newlines with \n didn't either. So let's try another introspection query on PATT. Whatever I do it seems not to work
For some obscure reasons not even explained on the author write-ups, the cookie can't contain double quotes. But it seems to be the case SOMETIMES for the script content as well. Many previous XHRs were containing double quotes and working but it seems nobody managed to pass the GraphQL part without encoding the JS payload to base64. Nobody seems to know what they are doing and just recopy the author payload so since it's unrealistic and obscure let's just do that as well, at this point I just want to end that shit.
Valid GraphQL queries are not returning to us because the GET query get too big.
That's why I wanted to use POST in the first place but for it works only on the same host due to CORS so we are forced to use GET for exfiltration.
I assume we could split the answer in many parts and send dozens of GET queries to retrieve a full introspection but that would be an unnecessary pain for a poorly written challenge.
This payloads works and doesn't need encoding despite the double quotes.
Decoded data:
Then your are supposed to use the PATT introspection query but whatever I'm using as web server the results is always too long except when using python -m http.server 9999 --directory public. I recopied the introspection query from the author payload because when I try to format it myself it doesn't work.
I won't paste the full JSON schema because it's way too long but here are some screenshots from GraphQL Voyager.
Queries:
Mutations:
When you have the full schema you are able to identify where the interesting data is stored (victims node) and to make the following query.
The idea was good but was ruined by the implementation (mostly because of PhantomJS). More pain than joy. I didn't learnt much doing it. What attracted me doing the challenge was the GraphQL keyword but in the end GraphQL in this challenged is marginal, it's mostly about blind XSS. There are several steps that won't work if you don't exactly copy the author payload or command, and valid commands that would work on real life that doesn't work on the challenge. I judge the challenge being bad and I wouldn't recommend someone doing it.