# CSV Formula Injection via Untrusted Exported Fields

Language: Python
Severity: High
CWE: CWE-1236

## Source
6

## Flow
6-7-10-11

## Sink
11

## Vulnerable Code
```python
import csv
from flask import request, send_file

@app.route('/export_iot_telemetry', methods=['POST'])
def export_device_metrics():
    device_id = request.json.get('device_id')
    sensor_readings = db.query(f"SELECT timestamp, sensor_name, value, unit FROM telemetry WHERE device={device_id}")
    output_path = f'/tmp/device_{device_id}_report.csv'
    with open(output_path, 'w', newline='') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(['Timestamp', 'Sensor', 'Reading', 'Unit'])
        for row in sensor_readings:
            writer.writerow([row[0], row[1], row[2], row[3]])
    return send_file(output_path, as_attachment=True, download_name=f'telemetry_{device_id}.csv')
```

## Explanation

The application retrieves sensor telemetry data from the database without sanitizing values that may contain CSV formula injection payloads (formulas starting with =, +, -, @). When these untrusted values are written directly to CSV via writer.writerow(), Excel or other spreadsheet applications will execute the formulas, potentially leading to remote code execution or data exfiltration.

## Remediation

The fix introduces a sanitize_csv_value() function that prefixes cell values starting with dangerous characters (=, +, -, @, tab, carriage return, newline) with a single quote, preventing spreadsheet applications from interpreting them as formulas. Additionally, the SQL query was changed from an f-string to a parameterized query to also address the SQL injection vulnerability present in the original code.

## Secure Code
```python
import csv
import re
from flask import request, send_file


def sanitize_csv_value(value):
    """Sanitize a value to prevent CSV formula injection.
    
    Prefixes dangerous characters with a single quote to prevent
    spreadsheet applications from interpreting them as formulas.
    """
    if value is None:
        return ''
    str_value = str(value)
    # Characters that can trigger formula execution in spreadsheet applications
    dangerous_prefixes = ('=', '+', '-', '@', '\t', '\r', '\n')
    if str_value.startswith(dangerous_prefixes):
        return "'" + str_value
    return str_value


@app.route('/export_iot_telemetry', methods=['POST'])
def export_device_metrics():
    device_id = request.json.get('device_id')
    # Use parameterized query to prevent SQL injection
    sensor_readings = db.query(
        "SELECT timestamp, sensor_name, value, unit FROM telemetry WHERE device = :device_id",
        {'device_id': device_id}
    )
    output_path = f'/tmp/device_{device_id}_report.csv'
    with open(output_path, 'w', newline='') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(['Timestamp', 'Sensor', 'Reading', 'Unit'])
        for row in sensor_readings:
            writer.writerow([
                sanitize_csv_value(row[0]),
                sanitize_csv_value(row[1]),
                sanitize_csv_value(row[2]),
                sanitize_csv_value(row[3])
            ])
    return send_file(output_path, as_attachment=True, download_name=f'telemetry_{device_id}.csv')
```
