HTB Cyber Apocalypse 2022 – Amidst Us (Web)


@h1riluk

Challenge

This challenge is categorized under Web challenge. The web page simulates a dark room and the mouse pointer as a flashlight.

If you move the mouse pointer around, you can find an image upload mechanism around the UFO block.

The image upload will replace the default among us sprite images. There is nothing special here, so let’s move to inspecting the network process in the background. Using DevTools in Chrome, open the Network tab, then try to upload the image again from the webpage.

When inspecting the data transfer, there are 3 requests being sent to the server. There are 2 image requests (replacement for the two sprite images that are shown on the webpage) and a request to /alphafy. The request payload is a JSON with a key background (integer array with a size of 3) and a key image (base64 encoded image).

The challenge also includes a source code for the application. There are two files that will be the keys to solving this challenge. The first file is requirement.txt which contains the required modules for the application.

The second is the util.py which handles requests to /alphafy.

You can see the function make_alpha using ImageMath.eval. If you search around for Pillow eval vulnerability, you will find some CVE detail about it. One example is this CVE-2022-22817.

PIL.ImageMath.eval in Pillow before 9.0.0 allows evaluation of arbitrary expressions, such as ones that use the Python exec method. A lambda expression could also be used.

The function make_alpha uses the value of the background and image from the request. There is no input filtering. The value of color is appended directly into the python f-string for evaluation using eval.

Let’s take example line 6 from the image above, color[0] using default value will return 255. The statement on line 6 will be “difference1(red_band, 255),” now if we want to inject a lambda expression into it, we have to make sure that the lambda returns an integer value. You can read more about multiline lambda and lambda with return values here.

After understanding this, exploit code can be included in the request payload. The value of background in the payload will be :

"background": ["(lambda: (command1, 255))()[1]", 255, 255]

The (lambda: (command1, 255)) is still a pointer, that is why “()” is added at the end of the lambda declaration to actually call the lambda. The lambda will return a tuple (return-value-of-command1, 255). An integer value is needed to complete the statement in line 6. Choose the “255” from the tuple returned by adding “[1]” to the exploit code.

The Dockerfile shows that flag.txt is being copied into /flag.txt. The command1 in the lambda can be used to copy the content of /flag.txt to a directory that its content is accessible such as /app/application/static/.

Import statement in lambda is not allowed, but the call of __import__() function is allowed. Thus import and the module imported can be used by chaining __import__() with the desired function of the imported module.

The final payload will be like this:

"background" : ["(lambda: (__import__('os').system('cat /flag.txt > /app/application/static/flag.txt'), 255))()[1]", 255, 255]

After sending this payload (use postman, Burpsuit, etc), you can get the flag by accessing /static/flag.txt.

The flag I captured during the competition is HTB{i_slept_my_way_to_rce}.