| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- #
- # Copyright 2014 Hewlett-Packard Development Company, L.P.
- #
- # SPDX-License-Identifier: Apache-2.0
- r"""
- ============================
- B608: Test for SQL injection
- ============================
- An SQL injection attack consists of insertion or "injection" of a SQL query via
- the input data given to an application. It is a very common attack vector. This
- plugin test looks for strings that resemble SQL statements that are involved in
- some form of string building operation. For example:
- - "SELECT %s FROM derp;" % var
- - "SELECT thing FROM " + tab
- - "SELECT " + val + " FROM " + tab + ...
- - "SELECT {} FROM derp;".format(var)
- - f"SELECT foo FROM bar WHERE id = {product}"
- Unless care is taken to sanitize and control the input data when building such
- SQL statement strings, an injection attack becomes possible. If strings of this
- nature are discovered, a LOW confidence issue is reported. In order to boost
- result confidence, this plugin test will also check to see if the discovered
- string is in use with standard Python DBAPI calls `execute` or `executemany`.
- If so, a MEDIUM issue is reported. For example:
- - cursor.execute("SELECT %s FROM derp;" % var)
- :Example:
- .. code-block:: none
- >> Issue: Possible SQL injection vector through string-based query
- construction.
- Severity: Medium Confidence: Low
- CWE: CWE-89 (https://cwe.mitre.org/data/definitions/89.html)
- Location: ./examples/sql_statements.py:4
- 3 query = "DELETE FROM foo WHERE id = '%s'" % identifier
- 4 query = "UPDATE foo SET value = 'b' WHERE id = '%s'" % identifier
- 5
- .. seealso::
- - https://www.owasp.org/index.php/SQL_Injection
- - https://security.openstack.org/guidelines/dg_parameterize-database-queries.html
- - https://cwe.mitre.org/data/definitions/89.html
- .. versionadded:: 0.9.0
- .. versionchanged:: 1.7.3
- CWE information added
- """ # noqa: E501
- import ast
- import re
- import bandit
- from bandit.core import issue
- from bandit.core import test_properties as test
- from bandit.core import utils
- SIMPLE_SQL_RE = re.compile(
- r"(select\s.*from\s|"
- r"delete\s+from\s|"
- r"insert\s+into\s.*values\s|"
- r"update\s.*set\s)",
- re.IGNORECASE | re.DOTALL,
- )
- def _check_string(data):
- return SIMPLE_SQL_RE.search(data) is not None
- def _evaluate_ast(node):
- wrapper = None
- statement = ""
- if isinstance(node._bandit_parent, ast.BinOp):
- out = utils.concat_string(node, node._bandit_parent)
- wrapper = out[0]._bandit_parent
- statement = out[1]
- elif (
- isinstance(node._bandit_parent, ast.Attribute)
- and node._bandit_parent.attr == "format"
- ):
- statement = node.s
- # Hierarchy for "".format() is Wrapper -> Call -> Attribute -> Str
- wrapper = node._bandit_parent._bandit_parent._bandit_parent
- elif hasattr(ast, "JoinedStr") and isinstance(
- node._bandit_parent, ast.JoinedStr
- ):
- substrings = [
- child
- for child in node._bandit_parent.values
- if isinstance(child, ast.Str)
- ]
- # JoinedStr consists of list of Constant and FormattedValue
- # instances. Let's perform one test for the whole string
- # and abandon all parts except the first one to raise one
- # failed test instead of many for the same SQL statement.
- if substrings and node == substrings[0]:
- statement = "".join([str(child.s) for child in substrings])
- wrapper = node._bandit_parent._bandit_parent
- if isinstance(wrapper, ast.Call): # wrapped in "execute" call?
- names = ["execute", "executemany"]
- name = utils.get_called_name(wrapper)
- return (name in names, statement)
- else:
- return (False, statement)
- @test.checks("Str")
- @test.test_id("B608")
- def hardcoded_sql_expressions(context):
- val = _evaluate_ast(context.node)
- if _check_string(val[1]):
- return bandit.Issue(
- severity=bandit.MEDIUM,
- confidence=bandit.MEDIUM if val[0] else bandit.LOW,
- cwe=issue.Cwe.SQL_INJECTION,
- text="Possible SQL injection vector through string-based "
- "query construction.",
- )
|