webfreehard

Troubles Installment

alfactf

Task: Flask credit scoring app with ML-based loan approval; ULTRA-LOW-RISK product reveals flag but requires ML model approval. Solution: path traversal in document download to extract sklearn model, reverse engineer threshold and special categorical values, craft application that passes ML scoring.

$ ls tags/ techniques/
path_traversal_via_db_fieldml_model_extractionsklearn_model_reverse_engineeringfeature_bruteforcethreshold_bypass

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

Troubles Installment — AlfaCTF

Description

A credit scoring web application "Karabin Capital" where users can submit loan applications. The goal is to get an approved "ULTRA-LOW-RISK" loan which reveals the flag.

The application is a Flask web service with PostgreSQL backend. Users register, submit loan applications with personal and financial data, and receive approval decisions. Applications with amount <= 10000 are auto-approved as MICRO-CASH. Larger amounts go through an ML scoring model (sklearn RandomForestClassifier). The ULTRA-LOW-RISK product requires amount >= 500000 AND ML model approval (probability >= threshold), and reveals the flag upon approval.

Analysis

Application Architecture

  • Flask web application with PostgreSQL database
  • ReportLab for PDF document generation
  • sklearn RandomForestClassifier for credit scoring
  • Two-tier approval logic:
    • amount <= 10000: auto-approved as MICRO-CASH
    • amount > 10000: requires ML model scoring

Vulnerability Discovery

The vulnerability is in the download_document function (app.py lines 820-864):

@app.get("/api/applications/<application_id>/document") def download_document(application_id: str) -> Response: ... base_dir = STORAGE_ROOT / user["public_uuid"] / user["login"] unsafe_path = base_dir / row["name"] # row["name"] = application_name from DB safe_path = safe_document_path(user, row["name"]) ... target_path = unsafe_path if unsafe_path.exists() else safe_path file_bytes = target_path.read_bytes() # Reads arbitrary file!

The application_name field from the database is used directly in file path construction without sanitization. When creating an application, the user controls the application_name field, which gets stored in the database and later used to construct the file path for document download.

Path Traversal Calculation

  • Base path: /srv/karabin/data/storage/{uuid}/{login}/
  • Target file: /srv/karabin/secrets/karabin-ratebook (the ML model)
  • Traversal payload: ../../../../secrets/karabin-ratebook

Solution

Step 1: Extract the ML Model via Path Traversal

import requests BASE_URL = 'https://capital-xxx.alfactf.ru' session = requests.Session() # Register a new user session.post(f'{BASE_URL}/api/register', json={ 'login': 'attacker01', 'password': 'password123' }) ...

$ grep --similar

Similar writeups