# Template Injection via Untrusted f-string Evaluation

Language: Python
Severity: Critical
CWE: CWE-94

## Source
7-8

## Flow
7-8-9-10

## Sink
10

## Vulnerable Code
```python
from flask import Flask, request
import boto3

app = Flask(__name__)
s3_client = boto3.client('s3')

@app.route('/cloud/provision')
def provision_s3_bucket():
    tenant_id = request.args.get('tenant', 'default')
    region_pref = request.args.get('region', 'us-east-1')
    bucket_template = f"s3-bucket-{tenant_id}-{region_pref}-data"
    bucket_name = eval(f"f'{bucket_template}'")
    s3_client.create_bucket(Bucket=bucket_name)
    return {"status": "provisioned", "bucket": bucket_name}
```

## Explanation

User-controlled parameters 'tenant' and 'region' from request.args are incorporated into an f-string template, which is then passed to eval() for dynamic evaluation. This allows attackers to inject arbitrary Python code through these parameters, leading to remote code execution when the f-string is evaluated.

## Remediation

The fix removes the dangerous eval() call entirely and constructs the bucket name using a simple f-string directly. Additionally, strict input validation is applied: tenant_id is restricted to alphanumeric characters and hyphens via regex, and region is validated against an allowlist of permitted AWS regions. This prevents any code injection while still providing the intended functionality.

## Secure Code
```python
import re
from flask import Flask, request
import boto3

app = Flask(__name__)
s3_client = boto3.client('s3')

ALLOWED_REGIONS = ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'eu-west-1', 'eu-central-1']
TENANT_ID_PATTERN = re.compile(r'^[a-zA-Z0-9\-]{1,50}$')

@app.route('/cloud/provision')
def provision_s3_bucket():
    tenant_id = request.args.get('tenant', 'default')
    region_pref = request.args.get('region', 'us-east-1')

    if not TENANT_ID_PATTERN.match(tenant_id):
        return {"status": "error", "message": "Invalid tenant ID. Only alphanumeric characters and hyphens are allowed."}, 400

    if region_pref not in ALLOWED_REGIONS:
        return {"status": "error", "message": f"Invalid region. Allowed regions: {ALLOWED_REGIONS}"}, 400

    bucket_name = f"s3-bucket-{tenant_id}-{region_pref}-data"
    s3_client.create_bucket(Bucket=bucket_name)
    return {"status": "provisioned", "bucket": bucket_name}
```
