Skip to content
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
28 changes: 28 additions & 0 deletions java/src/audit/CWE-089/SqlInjectionAudit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Audit - SQL Injection (any source, partial flow)

Dynamically generated SQL queries built from unvalidated data can cause SQL injection attacks.
This audit query does not restrict itself to recognized remote/user-controlled sources (like
`RemoteFlowSource`). Instead, it performs a partial (backwards) taint-tracking exploration from
every known SQL injection sink, up to a limited number of steps, so that it can surface data that
flows into a SQL query even when the originating source is not (yet) modeled as user input. This
makes it useful for triaging new or unusual sources of tainted data that reach a SQL injection
sink.

Because any node is considered a potential source, this query is very low precision and is
intended for manual audit, not for inclusion in a default security suite.

## Example

```java
import java.sql.Connection;
import java.sql.Statement;

public class Example {
void run(Connection connection, String username) throws Exception {
Statement statement = connection.createStatement();
// Building a query by concatenating an unsanitized value is a SQL injection sink,
// regardless of whether `username` is recognized as user input.
statement.executeQuery("SELECT * FROM users WHERE username = '" + username + "'");
}
}
```
43 changes: 43 additions & 0 deletions java/src/audit/CWE-089/SqlInjectionAudit.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @name SQL query built from user-controlled sources
* @description Building a SQL query from user-controlled sources is vulnerable to insertion of
* malicious SQL code by the user. This audit query reports partial flows (any
* source, not just recognized remote/user input) into a known SQL injection sink,
* which is useful for surfacing sources that are not yet modeled.
* @kind path-problem
* @problem.severity warning
* @security-severity 2.5
* @sub-severity low
* @precision very-low
* @id githubsecuritylab/audit/sql-injection
* @tags security
* external/cwe/cwe-089
* audit
*/

import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.QueryInjection

private module SqlInjectionAuditConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { any() }

predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink }
}

private module SqlInjectionAuditFlow = TaintTracking::Global<SqlInjectionAuditConfig>;

int explorationLimit() { result = 10 }

private module SqlInjectionAuditPartialFlow =
SqlInjectionAuditFlow::FlowExplorationRev<explorationLimit/0>;

import SqlInjectionAuditPartialFlow::PartialPathGraph

from
SqlInjectionAuditPartialFlow::PartialPathNode source,
SqlInjectionAuditPartialFlow::PartialPathNode sink
where SqlInjectionAuditPartialFlow::partialFlow(source, sink, _)
select sink.getNode(), source, sink, "This SQL query depends on a $@.", source.getNode(),
"user-provided value"
24 changes: 24 additions & 0 deletions java/test/audit/CWE-089/SqlInjectionAudit.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#select
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
edges
| SqlInjectionAuditTest.java:6:37:6:51 | username | SqlInjectionAuditTest.java:8:63:8:70 | username |
| SqlInjectionAuditTest.java:8:20:8:59 | "SELECT * FROM users WHERE username = '" | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... |
| SqlInjectionAuditTest.java:8:20:8:59 | "SELECT * FROM users WHERE username = '" [Ext] | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... |
| SqlInjectionAuditTest.java:8:20:8:70 | ... + ... | SqlInjectionAuditTest.java:8:20:8:76 | ... + ... |
| SqlInjectionAuditTest.java:8:20:8:70 | ... + ... [Ext] | SqlInjectionAuditTest.java:8:20:8:76 | ... + ... |
| SqlInjectionAuditTest.java:8:20:8:76 | ... + ... | SqlInjectionAuditTest.java:9:28:9:32 | query |
| SqlInjectionAuditTest.java:8:63:8:70 | username | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... |
| SqlInjectionAuditTest.java:8:63:8:70 | username [Ext] | SqlInjectionAuditTest.java:8:20:8:70 | ... + ... |
| SqlInjectionAuditTest.java:8:74:8:76 | "'" | SqlInjectionAuditTest.java:8:20:8:76 | ... + ... |
| SqlInjectionAuditTest.java:8:74:8:76 | "'" [Ext] | SqlInjectionAuditTest.java:8:20:8:76 | ... + ... |
| SqlInjectionAuditTest.java:13:24:13:44 | "SELECT * FROM users" | SqlInjectionAuditTest.java:15:28:15:36 | hardcoded |
2 changes: 2 additions & 0 deletions java/test/audit/CWE-089/SqlInjectionAudit.qlref
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
query: audit/CWE-089/SqlInjectionAudit.ql
postprocess: TestUtilities/PrettyPrintModels.ql
17 changes: 17 additions & 0 deletions java/test/audit/CWE-089/SqlInjectionAuditTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class SqlInjectionAuditTest {
void test1(Connection connection, String username) throws SQLException {
Statement statement = connection.createStatement();
Comment thread
felickz marked this conversation as resolved.
Dismissed
String query = "SELECT * FROM users WHERE username = '" + username + "'";
statement.executeQuery(query);
Comment thread
felickz marked this conversation as resolved.
Dismissed
}

void test2(Connection connection) throws SQLException {
String hardcoded = "SELECT * FROM users";
Statement statement = connection.createStatement();
Comment thread
felickz marked this conversation as resolved.
Dismissed
statement.executeQuery(hardcoded);
Comment thread
felickz marked this conversation as resolved.
Dismissed
}
}