CTF Writeup: 3v@l

CTF Writeup: 3v@l (Challenge #484)

1. Initial Analysis & Reconnaissance

The challenge provides a web application with a simple “Bank-Loan Calculator”. The description and a quick look at the page’s source code (view-source) immediately reveal the core vulnerability and the defenses we need to overcome.

  • Vulnerability: The backend is a Python Flask application that uses the dangerous eval() function to process user input from a form. This is a classic Remote Code Execution (RCE) vulnerability.

  • Goal: Exploit the RCE to read a flag file on the server.

  • Defenses: The developers attempted to secure the eval() function by implementing two blacklists, helpfully detailed in the HTML comments:

    1. Keyword Blacklist: Blocks common malicious strings like os, eval, exec, ls, cat, shell, etc.
    2. Regex Blacklist: r'0x...|\u...|%...|\.[A-Za-z0-9]{1,3}\b|[\\\/]|\.\.'
      • This blocks hexadecimal, unicode, and URL-encoded characters.
      • Crucially, it blocks file extensions (e.g., .py, .txt), forward/backward slashes (/, \), and directory traversal (..).

2. Step-by-Step Exploitation

The exploitation process was a multi-stage discovery, requiring us to bypass each filter systematically.

Step 2.1: Achieving Basic Code Execution

Our first goal was to confirm we could run code. The standard approach for RCE in Python is to use the os module.

  • Bypassing the Keyword Blacklist: The string os is blocked. We can bypass this by constructing the string using concatenation ('o'+'s') or by using the __import__() function, which is not blacklisted.
  • Initial Payload: __import__('o'+'s').system('l'+'s')
  • Problem: This payload executed successfully (returning an exit code of 0), but os.system() does not return the command’s output, only its exit status. We couldn’t see the result of our ls command.
  • Solution: We switched to os.popen().read(), which executes a command and returns its standard output as a string.
Step 2.2: Listing Files and Hitting a Dead End

Using our improved technique, we listed the files in the current directory (/app).

  • Payload: __import__('o'+'s').popen('l'+'s').read()
  • Result: A list of files including app.py, static, and templates.
  • Hypothesis: The flag might be hardcoded in the application’s source code, app.py.
Step 2.3: Bypassing the File Extension Filter

We tried to read app.py, but the regex filter \.[A-Za-z0-9]{1,3}\b blocked any payload containing .py.

  • Bypass: We used a shell wildcard (*). The web server’s shell expands app* to app.py after our payload has passed the filter.
  • Payload: __import__('o'+'s').popen('grep . app*').read() (using grep . to ensure the whole file is read).
  • Result (Red Herring): We successfully exfiltrated the entire source code of app.py. However, after careful review, we confirmed the flag was not in the source code.
Step 2.4: Bypassing the Path Filter and Finding the Flag

Since the flag wasn’t in the local directory, the source code, or in environment variables (which we also checked), the next logical step was to look in other directories, starting with the root directory (/).

  • Bypass: The regex filter [\\\/] blocks the / character. We bypassed this by generating the character dynamically within our Python payload using its ASCII code. The function chr(47) produces the / character.

  • Discovery Payload: __import__('o'+'s').popen('l'+'s '+chr(47)).read()

  • Result (Success!): This command listed the contents of the root directory, and among the system folders, we found flag.txt.

    app
    bin
    ...
    flag.txt
    ...
    var
    

3. The Final Payload

Now that we knew the flag’s location was /flag.txt, we had to craft a final payload that combined all of our bypass techniques to read it.

  • Command needed: more /flag.txt (using more since cat is blacklisted).
  • Bypass for /: chr(47)
  • Bypass for .txt: flag*

This led to the final, successful payload:

__import__('o'+'s').popen('more ' + chr(47) + 'flag*').read()

Executing this payload returned the contents of /flag.txt, revealing the flag and solving the challenge.

Comments