Bypassing Python eval() Filters: ASCII Encoding Attack in picoCTF 2025
Challenge Info:
- Platform: picoCTF 2025 - 3v@l
- Category: Web Exploitation
- Difficulty: Medium
- Vulnerability: Command Injection / Insecure
eval()Implementation
Understanding the Challenge
So ABC Bank has this "Loan Calculator" web app. Looks innocent enough - you type in math like 500 * 12 and it spits out the answer.
But here's the thing: under the hood, they're using Python's eval() function. And if you know anything about eval(), you know that's a massive red flag. This function doesn't just do math - it runs any Python code you throw at it.
Our goal: Get this calculator to read a secret file called flag.txt sitting on the server.



Why eval() is Dangerous
Full System Access
When people say "eval() runs code," they don't usually explain just how bad that is. Here's what actually happens:
- Full Access: Your code runs in the same space as the web app. Everything the app can see, you can see. Every module it imported, you can use.
- Same Privileges: Your code has the exact same permissions as the server. Can read files? You can too. Can delete stuff? Yep, you can do that.
- Total Control: It's not a calculator anymore - it's a direct line to the server's operating system.
The Blacklist Filter
The devs knew eval() was risky, so they added a filter. Think of it like a bouncer checking what you're trying to send in.
The filter blocks:
- Quotes (
'or"): Without quotes, you supposedly can't make strings like"flag.txt"or"os". - Keywords: Things like
import,os,system, andflagare straight-up banned.
Classic blacklist approach - block everything bad. Problem is, you can't list every possible way to say something.
The ASCII Bypass Technique
Computers don't read letters - they read numbers. Every character has an ASCII value.
'a'is number97'b'is number98
Python has chr() that turns numbers into letters:
chr(97)gives you'a'
Why the filter can't block chr():
Because it's a totally normal function! People use it for legitimate stuff. Blocking it or blocking numbers would break the calculator completely.
The trick: Instead of typing 'flag' (blocked), we type chr(102) + chr(108) + chr(97) + chr(103). The filter sees math and functions (allowed), but when eval() actually runs it, boom - it becomes 'flag'.
Reconnaissance: Testing the Environment
Let's figure out what we're working with.
Test 1: Confirming Python
Try len('abc') - asking Python for the length of 'abc'.
- Result: Returns
3 - Conclusion: Yep, definitely Python.
Test 2: Testing the Filter
Try the classic: __import__('os').system('ls')
- Result: "Forbidden" error
- Conclusion: Filter's active. Gotta be sneaky.


Test 3: Validating chr()
chr(65) # Should give us 'A'
Result: Success! Got A back.

Test 4: Testing Concatenation
chr(102)+chr(108) # Should give us 'fl'
Result: Works! Got fl.

Planning the Exploit
To read flag.txt, we need Python code like:
open('/flag.txt').read()
Translation: "Open that file and give me what's inside."
Problem: Can't type /flag.txt - has quotes and the word "flag".
Solution: Build it character by character with chr().
ASCII Mapping
/is 47fis 102lis 108ais 97gis 103.is 46tis 116xis 120tis 116

Building the Payload
Time to put it all together.
Step 1: Construct the Filename
Chain all those numbers with +:
# This spells out '/flag.txt' without using quotes!
chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)
Step 2: Embed in File Operation
open(chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)).read()
Looks wild to us, but to the computer it's totally valid. And the filter? It just sees functions and numbers - nothing forbidden.
Executing the Attack
Copy that payload into the calculator and hit "Calculate".
Server-Side Execution Flow
- Filter checks our input. Sees
open,chr,+, and numbers. Nothing's blacklisted. Green light. eval()runs our code.- All those
chr(..)calls turn into letters:/flag.txt - Executes
open('/flag.txt').read() - Reads the secret file
- Displays it like it's just another math answer
Flag: picoCTF{D0nt_Use_Unsecure_f@nctions463f23d8}

Key Takeaways
This whole challenge shows why blacklists are a bad idea.
The devs tried to lock down eval() by blocking specific words. But code is flexible - there's almost always another way to say the same thing (like using ASCII numbers instead of letters).
The Real Solution
The real fix? Ditch eval() completely. Use a proper math parser that only understands numbers and operators, not arbitrary code.
Security Lessons
- Blacklist filters can always be bypassed
eval()should never accept user input- Use whitelisting and proper input validation
- Consider safe alternatives like
ast.literal_eval()for simple use cases