Skip to content
This repository was archived by the owner on Apr 4, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions python/CWE-089/SqlInjectionAudit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Audit - SQL Injection using format strings

Dynamically generated SQL queries using format strings can cause SQL injection attacks. The following example shows how to use the `sql` package to execute a query with a format string:

## Example

```python
# Format string
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)

# str.format()
query = "SELECT * FROM users WHERE username = '{}'".format(username)
cursor.execute(query)

# "%s" % string
query = "SELECT * FROM users WHERE username = %s" % username
cursor.execute(query)
```
41 changes: 41 additions & 0 deletions python/CWE-089/SqlInjectionAudit.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @name SQL query built from user-controlled sources
* @kind path-problem
* @problem.severity warning
* @security-severity 2.5
* @sub-severity low
* @precision very-low
* @id py/audit/sql-injection
* @tags security
* external/cwe/cwe-089
* audit
*/

import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.Concepts
import semmle.python.dataflow.new.BarrierGuards
import semmle.python.ApiGraphs
import DataFlow::PathGraph
private import semmle.python.security.dataflow.SqlInjectionCustomizations
//
import github.Utils

/**
* A taint-tracking configuration for detecting SQL injection vulnerabilities.
*/
class SqlInjectionHeuristic extends TaintTracking::Configuration {
SqlInjectionHeuristic() { this = "SqlInjectionHeuristic" }

override predicate isSource(DataFlow::Node source) { source instanceof DynamicStrings }

override predicate isSink(DataFlow::Node sink) { sink instanceof SqlInjection::Sink }

override predicate isSanitizer(DataFlow::Node node) { node instanceof SqlInjection::Sanitizer }
}

from SqlInjectionHeuristic config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This SQL query depends on $@.", source.getNode(),
"a user-provided value"
36 changes: 36 additions & 0 deletions python/github/Utils.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import python
private import semmle.python.ApiGraphs
private import semmle.python.Concepts
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.internal.TaintTrackingPrivate

// List of all the format strings
// - python/ql/lib/semmle/python/dataflow/new/internal/TaintTrackingPrivate.qll
class DynamicStrings extends DataFlow::Node {
DynamicStrings() {
(
// s = f"WHERE name = '{input}'"
exists(Fstring fmtstr | this.asExpr() = fmtstr)
or
// "SELECT * FROM users WHERE username = '{}'".format(username)
exists(CallNode format, string methods, ControlFlowNode object |
object = format.getFunction().(AttrNode).getObject(methods)
|
methods = "format" and
this.asExpr() = format.getNode()
)
or
exists(BinaryExpr expr |
(
// q = "WHERE name = %s" % username
expr.getOp() instanceof Mod or
// q = "WHERE name = " + username
expr.getOp() instanceof Add
)
and
expr.getLeft().getParent() = this.asExpr()
)
) and
this.getScope().inSource()
}
}
20 changes: 20 additions & 0 deletions tests/python-tests/CWE-089/audit/SqlInjectionAudit.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
edges
| sqli.py:17:9:17:60 | ControlFlowNode for Fstring | sqli.py:18:16:18:20 | ControlFlowNode for query |
| sqli.py:21:9:21:68 | ControlFlowNode for Attribute() | sqli.py:22:16:22:20 | ControlFlowNode for query |
| sqli.py:25:9:25:60 | ControlFlowNode for BinaryExpr | sqli.py:26:16:26:20 | ControlFlowNode for query |
| sqli.py:30:9:30:58 | ControlFlowNode for BinaryExpr | sqli.py:31:16:31:20 | ControlFlowNode for query |
nodes
| sqli.py:17:9:17:60 | ControlFlowNode for Fstring | semmle.label | ControlFlowNode for Fstring |
| sqli.py:18:16:18:20 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| sqli.py:21:9:21:68 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| sqli.py:22:16:22:20 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| sqli.py:25:9:25:60 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
| sqli.py:26:16:26:20 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| sqli.py:30:9:30:58 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
| sqli.py:31:16:31:20 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
subpaths
#select
| sqli.py:18:16:18:20 | ControlFlowNode for query | sqli.py:17:9:17:60 | ControlFlowNode for Fstring | sqli.py:18:16:18:20 | ControlFlowNode for query | This SQL query depends on $@. | sqli.py:17:9:17:60 | ControlFlowNode for Fstring | a user-provided value |
| sqli.py:22:16:22:20 | ControlFlowNode for query | sqli.py:21:9:21:68 | ControlFlowNode for Attribute() | sqli.py:22:16:22:20 | ControlFlowNode for query | This SQL query depends on $@. | sqli.py:21:9:21:68 | ControlFlowNode for Attribute() | a user-provided value |
| sqli.py:26:16:26:20 | ControlFlowNode for query | sqli.py:25:9:25:60 | ControlFlowNode for BinaryExpr | sqli.py:26:16:26:20 | ControlFlowNode for query | This SQL query depends on $@. | sqli.py:25:9:25:60 | ControlFlowNode for BinaryExpr | a user-provided value |
| sqli.py:31:16:31:20 | ControlFlowNode for query | sqli.py:30:9:30:58 | ControlFlowNode for BinaryExpr | sqli.py:31:16:31:20 | ControlFlowNode for query | This SQL query depends on $@. | sqli.py:30:9:30:58 | ControlFlowNode for BinaryExpr | a user-provided value |
1 change: 1 addition & 0 deletions tests/python-tests/CWE-089/audit/SqlInjectionAudit.qlref
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CWE-089/SqlInjectionAudit.ql
1 change: 1 addition & 0 deletions tests/python-tests/CWE-089/audit/options
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
semmle-extractor-options: --max-import-depth=0
31 changes: 31 additions & 0 deletions tests/python-tests/CWE-089/audit/sqli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

import psycopg2

# input
username = input("Username:")

connection = psycopg2.connect(
user="sysadmin",
password="pynative@#29",
host="127.0.0.1",
port="5432",
database="postgres_db"
)
cursor = connection.cursor()

# test 1 - Format string
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)

# test 2 - str.format()
query = "SELECT * FROM users WHERE username = '{}'".format(username)
cursor.execute(query)

# test 3 - %s
query = "SELECT * FROM users WHERE username = %s" % username
cursor.execute(query)


# test 4 - string + string
query = "SELECT * FROM users WHERE username = " + username
cursor.execute(query)