Skip to content

Commit bbfde1b

Browse files
Copilotfelickz
andauthored
Add Java CWE-089 SQL injection audit query with tests and docs (#198)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: felickz <1760475+felickz@users.noreply.github.com>
1 parent a3b74da commit bbfde1b

5 files changed

Lines changed: 114 additions & 0 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Audit - SQL Injection (any source, partial flow)
2+
3+
Dynamically generated SQL queries built from unvalidated data can cause SQL injection attacks.
4+
This audit query does not restrict itself to recognized remote/user-controlled sources (like
5+
`RemoteFlowSource`). Instead, it performs a partial (backwards) taint-tracking exploration from
6+
every known SQL injection sink, up to a limited number of steps, so that it can surface data that
7+
flows into a SQL query even when the originating source is not (yet) modeled as user input. This
8+
makes it useful for triaging new or unusual sources of tainted data that reach a SQL injection
9+
sink.
10+
11+
Because any node is considered a potential source, this query is very low precision and is
12+
intended for manual audit, not for inclusion in a default security suite.
13+
14+
## Example
15+
16+
```java
17+
import java.sql.Connection;
18+
import java.sql.Statement;
19+
20+
public class Example {
21+
void run(Connection connection, String username) throws Exception {
22+
Statement statement = connection.createStatement();
23+
// Building a query by concatenating an unsanitized value is a SQL injection sink,
24+
// regardless of whether `username` is recognized as user input.
25+
statement.executeQuery("SELECT * FROM users WHERE username = '" + username + "'");
26+
}
27+
}
28+
```
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* @name SQL query built from user-controlled sources
3+
* @description Building a SQL query from user-controlled sources is vulnerable to insertion of
4+
* malicious SQL code by the user. This audit query reports partial flows (any
5+
* source, not just recognized remote/user input) into a known SQL injection sink,
6+
* which is useful for surfacing sources that are not yet modeled.
7+
* @kind path-problem
8+
* @problem.severity warning
9+
* @security-severity 2.5
10+
* @sub-severity low
11+
* @precision very-low
12+
* @id githubsecuritylab/audit/sql-injection
13+
* @tags security
14+
* external/cwe/cwe-089
15+
* audit
16+
*/
17+
18+
import java
19+
import semmle.code.java.dataflow.DataFlow
20+
import semmle.code.java.dataflow.TaintTracking
21+
import semmle.code.java.security.QueryInjection
22+
23+
private module SqlInjectionAuditConfig implements DataFlow::ConfigSig {
24+
predicate isSource(DataFlow::Node source) { any() }
25+
26+
predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink }
27+
}
28+
29+
private module SqlInjectionAuditFlow = TaintTracking::Global<SqlInjectionAuditConfig>;
30+
31+
int explorationLimit() { result = 10 }
32+
33+
private module SqlInjectionAuditPartialFlow =
34+
SqlInjectionAuditFlow::FlowExplorationRev<explorationLimit/0>;
35+
36+
import SqlInjectionAuditPartialFlow::PartialPathGraph
37+
38+
from
39+
SqlInjectionAuditPartialFlow::PartialPathNode source,
40+
SqlInjectionAuditPartialFlow::PartialPathNode sink
41+
where SqlInjectionAuditPartialFlow::partialFlow(source, sink, _)
42+
select sink.getNode(), source, sink, "This SQL query depends on a $@.", source.getNode(),
43+
"user-provided value"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#select
2+
| SqlInjectionAuditTest.java:9:28:9:32 | query | SqlInjectionAuditTest.java:6:37:6:51 | username | SqlInjectionAuditTest.java:9:28:9:32 | query | This SQL query depends on a $@. | SqlInjectionAuditTest.java:6:37:6:51 | username | user-provided value |
3+
| SqlInjectionAuditTest.java:9:28:9:32 | query | SqlInjectionAuditTest.java:8:20:8:59 | "SELECT * FROM users WHERE username = '" | SqlInjectionAuditTest.java:9:28:9:32 | query | This SQL query depends on a $@. | SqlInjectionAuditTest.java:8:20:8:59 | "SELECT * FROM users WHERE username = '" | user-provided value |
4+
| SqlInjectionAuditTest.java:9:28:9:32 | query | SqlInjectionAuditTest.java:8:20:8:59 | "SELECT * FROM users WHERE username = '" [Ext] | SqlInjectionAuditTest.java:9:28:9:32 | query | This SQL query depends on a $@. | SqlInjectionAuditTest.java:8:20:8:59 | "SELECT * FROM users WHERE username = '" | user-provided value |
5+
| SqlInjectionAuditTest.java:9:28:9:32 | query | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... | SqlInjectionAuditTest.java:9:28:9:32 | query | This SQL query depends on a $@. | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... | user-provided value |
6+
| SqlInjectionAuditTest.java:9:28:9:32 | query | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... [Ext] | SqlInjectionAuditTest.java:9:28:9:32 | query | This SQL query depends on a $@. | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... | user-provided value |
7+
| SqlInjectionAuditTest.java:9:28:9:32 | query | SqlInjectionAuditTest.java:8:20:8:76 | ... + ... | SqlInjectionAuditTest.java:9:28:9:32 | query | This SQL query depends on a $@. | SqlInjectionAuditTest.java:8:20:8:76 | ... + ... | user-provided value |
8+
| SqlInjectionAuditTest.java:9:28:9:32 | query | SqlInjectionAuditTest.java:8:63:8:70 | username | SqlInjectionAuditTest.java:9:28:9:32 | query | This SQL query depends on a $@. | SqlInjectionAuditTest.java:8:63:8:70 | username | user-provided value |
9+
| SqlInjectionAuditTest.java:9:28:9:32 | query | SqlInjectionAuditTest.java:8:63:8:70 | username [Ext] | SqlInjectionAuditTest.java:9:28:9:32 | query | This SQL query depends on a $@. | SqlInjectionAuditTest.java:8:63:8:70 | username | user-provided value |
10+
| SqlInjectionAuditTest.java:9:28:9:32 | query | SqlInjectionAuditTest.java:8:74:8:76 | "'" | SqlInjectionAuditTest.java:9:28:9:32 | query | This SQL query depends on a $@. | SqlInjectionAuditTest.java:8:74:8:76 | "'" | user-provided value |
11+
| SqlInjectionAuditTest.java:9:28:9:32 | query | SqlInjectionAuditTest.java:8:74:8:76 | "'" [Ext] | SqlInjectionAuditTest.java:9:28:9:32 | query | This SQL query depends on a $@. | SqlInjectionAuditTest.java:8:74:8:76 | "'" | user-provided value |
12+
| SqlInjectionAuditTest.java:15:28:15:36 | hardcoded | SqlInjectionAuditTest.java:13:24:13:44 | "SELECT * FROM users" | SqlInjectionAuditTest.java:15:28:15:36 | hardcoded | This SQL query depends on a $@. | SqlInjectionAuditTest.java:13:24:13:44 | "SELECT * FROM users" | user-provided value |
13+
edges
14+
| SqlInjectionAuditTest.java:6:37:6:51 | username | SqlInjectionAuditTest.java:8:63:8:70 | username |
15+
| SqlInjectionAuditTest.java:8:20:8:59 | "SELECT * FROM users WHERE username = '" | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... |
16+
| SqlInjectionAuditTest.java:8:20:8:59 | "SELECT * FROM users WHERE username = '" [Ext] | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... |
17+
| SqlInjectionAuditTest.java:8:20:8:70 | ... + ... | SqlInjectionAuditTest.java:8:20:8:76 | ... + ... |
18+
| SqlInjectionAuditTest.java:8:20:8:70 | ... + ... [Ext] | SqlInjectionAuditTest.java:8:20:8:76 | ... + ... |
19+
| SqlInjectionAuditTest.java:8:20:8:76 | ... + ... | SqlInjectionAuditTest.java:9:28:9:32 | query |
20+
| SqlInjectionAuditTest.java:8:63:8:70 | username | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... |
21+
| SqlInjectionAuditTest.java:8:63:8:70 | username [Ext] | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... |
22+
| SqlInjectionAuditTest.java:8:74:8:76 | "'" | SqlInjectionAuditTest.java:8:20:8:76 | ... + ... |
23+
| SqlInjectionAuditTest.java:8:74:8:76 | "'" [Ext] | SqlInjectionAuditTest.java:8:20:8:76 | ... + ... |
24+
| SqlInjectionAuditTest.java:13:24:13:44 | "SELECT * FROM users" | SqlInjectionAuditTest.java:15:28:15:36 | hardcoded |
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
query: audit/CWE-089/SqlInjectionAudit.ql
2+
postprocess: TestUtilities/PrettyPrintModels.ql
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import java.sql.Connection;
2+
import java.sql.SQLException;
3+
import java.sql.Statement;
4+
5+
public class SqlInjectionAuditTest {
6+
void test1(Connection connection, String username) throws SQLException {
7+
Statement statement = connection.createStatement();
8+
String query = "SELECT * FROM users WHERE username = '" + username + "'";
9+
statement.executeQuery(query);
10+
}
11+
12+
void test2(Connection connection) throws SQLException {
13+
String hardcoded = "SELECT * FROM users";
14+
Statement statement = connection.createStatement();
15+
statement.executeQuery(hardcoded);
16+
}
17+
}

0 commit comments

Comments
 (0)