python.flask.security.injection.csv-writer-injection.csv-writer-injection

profile photo of semgrepsemgrep
Author
unknown
Download Count*

Detected user input into a generated CSV file using the built-in csv module. If user data is used to generate the data in this file, it is possible that an attacker could inject a formula when the CSV is imported into a spreadsheet application that runs an attacker script, which could steal data from the importing user or, at worst, install malware on the user's computer. defusedcsv is a drop-in replacement with the same API that will attempt to mitigate formula injection attempts. You can use defusedcsv instead of csv to safely generate CSVs.

Run Locally

Run in CI

Defintion

rules:
  - id: csv-writer-injection
    languages:
      - python
    message: Detected user input into a generated CSV file using the built-in `csv`
      module. If user data is used to generate the data in this file, it is
      possible that an attacker could inject a formula when the CSV is imported
      into a spreadsheet application that runs an attacker script, which could
      steal data from the importing user or, at worst, install malware on the
      user's computer. `defusedcsv` is a drop-in replacement with the same API
      that will attempt to mitigate formula injection attempts. You can use
      `defusedcsv` instead of `csv` to safely generate CSVs.
    metadata:
      category: security
      confidence: MEDIUM
      cwe:
        - "CWE-1236: Improper Neutralization of Formula Elements in a CSV File"
      owasp:
        - A01:2017 - Injection
        - A03:2021 - Injection
      references:
        - https://github.com/raphaelm/defusedcsv
        - https://owasp.org/www-community/attacks/CSV_Injection
        - https://web.archive.org/web/20220516052229/https://www.contextis.com/us/blog/comma-separated-vulnerabilities
      technology:
        - python
        - flask
      subcategory:
        - vuln
      impact: MEDIUM
      likelihood: MEDIUM
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Improper Validation
    mode: taint
    pattern-sinks:
      - patterns:
          - pattern-inside: |
              $WRITER = csv.writer(...)

              ...

              $WRITER.$WRITE(...)
          - pattern: $WRITER.$WRITE(...)
          - metavariable-regex:
              metavariable: $WRITE
              regex: ^(writerow|writerows|writeheader)$
    pattern-sources:
      - patterns:
          - pattern-either:
              - patterns:
                  - pattern-either:
                      - pattern: flask.request.form.get(...)
                      - pattern: flask.request.form[...]
                      - pattern: flask.request.args.get(...)
                      - pattern: flask.request.args[...]
                      - pattern: flask.request.values.get(...)
                      - pattern: flask.request.values[...]
                      - pattern: flask.request.cookies.get(...)
                      - pattern: flask.request.cookies[...]
                      - pattern: flask.request.stream
                      - pattern: flask.request.headers.get(...)
                      - pattern: flask.request.headers[...]
                      - pattern: flask.request.data
                      - pattern: flask.request.full_path
                      - pattern: flask.request.url
                      - pattern: flask.request.json
                      - pattern: flask.request.get_json()
                      - pattern: flask.request.view_args.get(...)
                      - pattern: flask.request.view_args[...]
              - patterns:
                  - pattern-inside: |
                      @$APP.route($ROUTE, ...)
                      def $FUNC(..., $ROUTEVAR, ...):
                        ...
                  - focus-metavariable: $ROUTEVAR
    severity: ERROR

Examples

csv-writer-injection.py

import csv
import flask
import io

from data import get_data
from util import chroot

app = flask.Flask(__name__)

@app.route("a/<title>")
def a(title):
    stream = io.StringIO()
    writer = csv.writer(stream)
    data = get_data()
    title_row = title + ("," * len(data[0]) - 1)
    # ruleid: csv-writer-injection
    writer.writerow(title_row)
    writer.writerows(data)
    stream.flush()
    stream.seek(0)
    return stream.read()

@app.route("ok")
def ok():
    with open("data.csv") as fin:
        # ok: csv-writer-injection
        reader = csv.reader(fin)
        lines = [line for line in reader]
    return '\n'.join(lines)