+
+
+
+If a database query (such as a SQL query) is built from user-provided data without sufficient sanitization, a user may be able to run malicious database queries. An attacker can craft the part of the query they control to change the overall meaning of the query.
+
+
+
+
+
+
+Most database connector libraries offer a way to safely embed untrusted data into a query using query parameters or prepared statements. You should use these features to build queries, rather than string concatenation or similar methods. You can also escape (sanitize) user-controlled strings so that they can be included directly in an SQL command. A library function should be used for escaping, because this approach is only safe if the escaping function is robust against all possible inputs.
+
+
+
+
+
+In the following examples, an SQL query is prepared using string interpolation to directly include a user-controlled value userControlledString in the query. An attacker could craft userControlledString to change the overall meaning of the SQL query.
+
+
+
+
+A better way to do this is with a prepared statement, binding userControlledString to a parameter of that statement. An attacker who controls userControlledString now cannot change the overall meaning of the query.
+
+
+
+
+
+
+
+Wikipedia: SQL injection.
+OWASP: SQL Injection Prevention Cheat Sheet.
+
+
+
diff --git a/unified/ql/src/queries/security/CWE-089/SqlInjection.ql b/unified/ql/src/queries/security/CWE-089/SqlInjection.ql
new file mode 100644
index 000000000000..62872779069d
--- /dev/null
+++ b/unified/ql/src/queries/security/CWE-089/SqlInjection.ql
@@ -0,0 +1,76 @@
+/**
+ * @name Database query built from user-controlled sources
+ * @description Building a database query from user-controlled sources is vulnerable to insertion of malicious code by the user.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 8.8
+ * @precision high
+ * @id unified/swift/sql-injection
+ * @tags security
+ * external/cwe/cwe-089
+ */
+
+import unified
+private import codeql.unified.internal.typeinference.TypeInference as TypeInference
+
+private predicate sqliteSwiftSink(DataFlow::Node node) {
+ // TODO: Use MaD once legacy selectors distinguish Connection methods from Statement bindings.
+ exists(CallExpr call, FunctionDeclaration target |
+ target = TypeInference::resolveCallTarget(call) and
+ target.getEnclosingClass().getName() = "Connection" and
+ target.getName() = ["run", "scalar"] and
+ node.asExpr() = call.getArgument(0).getValue()
+ )
+}
+
+/**
+ * An SQL injection sink that is determined by imprecise methods.
+ */
+private predicate heuristicSink(DataFlow::Node node) {
+ exists(Argument arg |
+ node.asExpr() = arg.getValue() and
+ (
+ // by argument name
+ arg.getName() = "sql"
+ or
+ // by parameter name
+ // TODO: make a helper predicate `Argument.getParameter` for this?
+ exists(CallExpr call, int index, Parameter param |
+ arg = call.getArgument(index) and
+ param = TypeInference::resolveCallTarget(call).getParameter(index) and
+ param.getPattern().(Identifier).getValue() = "sql"
+ )
+ )
+ )
+}
+
+/**
+ * A taint configuration for tainted data that reaches a SQL sink.
+ */
+module SqlInjectionConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node node) { Models::isSource(node, _) }
+
+ predicate isSink(DataFlow::Node node) {
+ Models::isSink(node, "sql-injection")
+ or
+ sqliteSwiftSink(node)
+ or
+ heuristicSink(node)
+ }
+
+ predicate isBarrier(DataFlow::Node node) {
+ // TODO: Port the numeric-type barrier from original Swift's SqlInjectionExtensions.qll.
+ none()
+ }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
+}
+
+module SqlInjectionFlow = TaintTracking::Global