Skip to content
This repository was archived by the owner on Apr 4, 2025. It is now read-only.

Commit cbaadee

Browse files
authored
Merge pull request #86 from advanced-security/py/audit-sql
Adding SQL Injection audit query
2 parents cb34688 + ac8c8b5 commit cbaadee

File tree

7 files changed

+149
-0
lines changed

7 files changed

+149
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Audit - SQL Injection using format strings
2+
3+
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:
4+
5+
## Example
6+
7+
```python
8+
# Format string
9+
query = f"SELECT * FROM users WHERE username = '{username}'"
10+
cursor.execute(query)
11+
12+
# str.format()
13+
query = "SELECT * FROM users WHERE username = '{}'".format(username)
14+
cursor.execute(query)
15+
16+
# "%s" % string
17+
query = "SELECT * FROM users WHERE username = %s" % username
18+
cursor.execute(query)
19+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @name SQL query built from user-controlled sources
3+
* @kind path-problem
4+
* @problem.severity warning
5+
* @security-severity 2.5
6+
* @sub-severity low
7+
* @precision very-low
8+
* @id py/audit/sql-injection
9+
* @tags security
10+
* external/cwe/cwe-089
11+
* audit
12+
*/
13+
14+
import python
15+
import semmle.python.dataflow.new.DataFlow
16+
import semmle.python.dataflow.new.TaintTracking
17+
import semmle.python.Concepts
18+
import semmle.python.dataflow.new.BarrierGuards
19+
import semmle.python.ApiGraphs
20+
import DataFlow::PathGraph
21+
private import semmle.python.security.dataflow.SqlInjectionCustomizations
22+
//
23+
import github.Utils
24+
25+
/**
26+
* A taint-tracking configuration for detecting SQL injection vulnerabilities.
27+
*/
28+
class SqlInjectionHeuristic extends TaintTracking::Configuration {
29+
SqlInjectionHeuristic() { this = "SqlInjectionHeuristic" }
30+
31+
override predicate isSource(DataFlow::Node source) { source instanceof DynamicStrings }
32+
33+
override predicate isSink(DataFlow::Node sink) { sink instanceof SqlInjection::Sink }
34+
35+
override predicate isSanitizer(DataFlow::Node node) { node instanceof SqlInjection::Sanitizer }
36+
}
37+
38+
from SqlInjectionHeuristic config, DataFlow::PathNode source, DataFlow::PathNode sink
39+
where config.hasFlowPath(source, sink)
40+
select sink.getNode(), source, sink, "This SQL query depends on $@.", source.getNode(),
41+
"a user-provided value"

python/github/Utils.qll

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import python
2+
private import semmle.python.ApiGraphs
3+
private import semmle.python.Concepts
4+
private import semmle.python.dataflow.new.DataFlow
5+
private import semmle.python.dataflow.new.internal.TaintTrackingPrivate
6+
7+
// List of all the format strings
8+
// - python/ql/lib/semmle/python/dataflow/new/internal/TaintTrackingPrivate.qll
9+
class DynamicStrings extends DataFlow::Node {
10+
DynamicStrings() {
11+
(
12+
// s = f"WHERE name = '{input}'"
13+
exists(Fstring fmtstr | this.asExpr() = fmtstr)
14+
or
15+
// "SELECT * FROM users WHERE username = '{}'".format(username)
16+
exists(CallNode format, string methods, ControlFlowNode object |
17+
object = format.getFunction().(AttrNode).getObject(methods)
18+
|
19+
methods = "format" and
20+
this.asExpr() = format.getNode()
21+
)
22+
or
23+
exists(BinaryExpr expr |
24+
(
25+
// q = "WHERE name = %s" % username
26+
expr.getOp() instanceof Mod or
27+
// q = "WHERE name = " + username
28+
expr.getOp() instanceof Add
29+
)
30+
and
31+
expr.getLeft().getParent() = this.asExpr()
32+
)
33+
) and
34+
this.getScope().inSource()
35+
}
36+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
edges
2+
| sqli.py:17:9:17:60 | ControlFlowNode for Fstring | sqli.py:18:16:18:20 | ControlFlowNode for query |
3+
| sqli.py:21:9:21:68 | ControlFlowNode for Attribute() | sqli.py:22:16:22:20 | ControlFlowNode for query |
4+
| sqli.py:25:9:25:60 | ControlFlowNode for BinaryExpr | sqli.py:26:16:26:20 | ControlFlowNode for query |
5+
| sqli.py:30:9:30:58 | ControlFlowNode for BinaryExpr | sqli.py:31:16:31:20 | ControlFlowNode for query |
6+
nodes
7+
| sqli.py:17:9:17:60 | ControlFlowNode for Fstring | semmle.label | ControlFlowNode for Fstring |
8+
| sqli.py:18:16:18:20 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
9+
| sqli.py:21:9:21:68 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
10+
| sqli.py:22:16:22:20 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
11+
| sqli.py:25:9:25:60 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
12+
| sqli.py:26:16:26:20 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
13+
| sqli.py:30:9:30:58 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
14+
| sqli.py:31:16:31:20 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
15+
subpaths
16+
#select
17+
| 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 |
18+
| 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 |
19+
| 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 |
20+
| 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 |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CWE-089/SqlInjectionAudit.ql
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
semmle-extractor-options: --max-import-depth=0
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
import psycopg2
3+
4+
# input
5+
username = input("Username:")
6+
7+
connection = psycopg2.connect(
8+
user="sysadmin",
9+
password="pynative@#29",
10+
host="127.0.0.1",
11+
port="5432",
12+
database="postgres_db"
13+
)
14+
cursor = connection.cursor()
15+
16+
# test 1 - Format string
17+
query = f"SELECT * FROM users WHERE username = '{username}'"
18+
cursor.execute(query)
19+
20+
# test 2 - str.format()
21+
query = "SELECT * FROM users WHERE username = '{}'".format(username)
22+
cursor.execute(query)
23+
24+
# test 3 - %s
25+
query = "SELECT * FROM users WHERE username = %s" % username
26+
cursor.execute(query)
27+
28+
29+
# test 4 - string + string
30+
query = "SELECT * FROM users WHERE username = " + username
31+
cursor.execute(query)

0 commit comments

Comments
 (0)