python.flask.security.injection.tainted-sql-string.tainted-sql-string

profile photo of semgrepsemgrep
Author
unknown
Download Count*

Detected user input used to manually construct a SQL string. This is usually bad practice because manual construction could accidentally result in a SQL injection. An attacker could use a SQL injection to steal or modify contents of the database. Instead, use a parameterized query which is available by default in most database engines. Alternatively, consider using an object-relational mapper (ORM) such as SQLAlchemy which will protect your queries.

Run Locally

Run in CI

Defintion

rules:
  - id: tainted-sql-string
    message: Detected user input used to manually construct a SQL string. This is
      usually bad practice because manual construction could accidentally result
      in a SQL injection. An attacker could use a SQL injection to steal or
      modify contents of the database. Instead, use a parameterized query which
      is available by default in most database engines. Alternatively, consider
      using an object-relational mapper (ORM) such as SQLAlchemy which will
      protect your queries.
    metadata:
      cwe:
        - "CWE-704: Incorrect Type Conversion or Cast"
      owasp:
        - A01:2017 - Injection
        - A03:2021 - Injection
      references:
        - https://docs.sqlalchemy.org/en/14/core/tutorial.html#using-textual-sql
        - https://www.tutorialspoint.com/sqlalchemy/sqlalchemy_quick_guide.htm
        - https://docs.sqlalchemy.org/en/14/core/tutorial.html#using-more-specific-text-with-table-expression-literal-column-and-expression-column
      category: security
      technology:
        - sqlalchemy
        - flask
      subcategory:
        - vuln
      impact: MEDIUM
      likelihood: MEDIUM
      confidence: MEDIUM
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Improper Validation
    severity: ERROR
    languages:
      - python
    mode: taint
    pattern-sources:
      - patterns:
          - pattern-either:
              - pattern: flask.request.$ANYTHING
              - patterns:
                  - pattern-inside: |
                      @$APP.route(...)
                      def $FUNC(..., $ROUTEVAR, ...):
                        ...
                  - pattern: $ROUTEVAR
    pattern-sinks:
      - patterns:
          - pattern-either:
              - pattern: |
                  "$SQLSTR" + ...
              - pattern: |
                  "$SQLSTR" % ...
              - pattern: |
                  "$SQLSTR".format(...)
              - pattern: |
                  f"$SQLSTR{...}..."
          - metavariable-regex:
              metavariable: $SQLSTR
              regex: \s*(?i)(select|delete|insert|create|update|alter|drop)\b.*

Examples

tainted-sql-string.py

import os
import flask
import hashlib
import requests
from flask_sqlalchemy import SQLAlchemy

app = flask.Flask(__name__)
engine = SQLAlchemy()

@app.route("/insert/person")
def insert_person():
    name = flask.request.args.get("name")
    lastname = "you don't get to pick >:)"

    # String concatenation using + operator
    # ruleid: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('" + name + "')")

    # ruleid: tainted-sql-string
    engine.execute("INSERT INTO person (firstname, lastname) VALUES ('" + name + "','" + lastname + "')")

    # ok: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('" + lastname +"')")

    # Format strings with %
    # ruleid: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('%s')" % (name))

    # ruleid: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('%s')" % (flask.request.args.get("name")))

    # ok: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('%s')" % (lastname))

    # Format strings with .format
    # ruleid: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('{}')".format(name))

    # Format strings  using fstrings
    # ruleid: tainted-sql-string
    engine.execute(f"SELECT FROM person WHERE name='{name}'")

    # Query without concatenation
    # ok: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('Frodon Sacquet')")

    # Query using prepared statement with named parameters
    # ok: tainted-sql-string
    stmt = text("INSERT INTO table (name) VALUES(:name)")
    engine.execute(stmt, name=name)

    # SQL Composition and prepared statement
    # ok: tainted-sql-string
    query = select(literal_column("users.fullname", String) + ', ' + literal_column("addresses.email_address").label("title")).where(and_(literal_column("users.id") == literal_column("addresses.user_id"), text("users.name BETWEEN 'm' AND 'z'"), text("(addresses.email_address LIKE :x OR addresses.email_address LIKE :y)"))).select_from(table('users')).select_from(table('addresses'))
    engine.execute(query, {"x":"%@aol.com", "y":name}).fetchall()

@app.route("/insert/person/path")
def insert_person(path):
    name = path
    lastname = "you don't get to pick >:)"

    # String concatenation using + operator
    # ruleid: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('" + name + "')")

    # ruleid: tainted-sql-string
    engine.execute("INSERT INTO person (firstname, lastname) VALUES ('" + name + "','" + lastname + "')")

    # ok: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('" + lastname +"')")

    # Format strings with %
    # ruleid: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('%s')" % (name))

    # ruleid: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('%s')" % (flask.request.args.get("name")))

    # ok: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('%s')" % (lastname))

    # Format strings with .format
    # ruleid: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('{}')".format(name))

    # Format strings  using fstrings
    # ruleid: tainted-sql-string
    engine.execute(f"SELECT FROM person WHERE name='{name}'")

    # Query without concatenation
    # ok: tainted-sql-string
    engine.execute("INSERT INTO person (name) VALUES ('Frodon Sacquet')")

    # Query using prepared statement with named parameters
    # ok: tainted-sql-string
    stmt = text("INSERT INTO table (name) VALUES(:name)")
    connection.execute(stmt, name=name)

    # SQL Composition and prepared statement
    # ok: tainted-sql-string
    query = select(literal_column("users.fullname", String) + ', ' + literal_column("addresses.email_address").label("title")).where(and_(literal_column("users.id") == literal_column("addresses.user_id"), text("users.name BETWEEN 'm' AND 'z'"), text("(addresses.email_address LIKE :x OR addresses.email_address LIKE :y)"))).select_from(table('users')).select_from(table('addresses'))
    engine.execute(query, {"x":"%@aol.com", "y":name}).fetchall()