SQL Injection ('SQLi')
ID |
swift.sql_injection |
Severity |
critical |
Resource |
Injection |
Language |
Swift |
Tags |
CWE:89, NIST.SP.800-53, OWASP:2021:A3, PCI-DSS:6.5.1 |
Description
Improper neutralization of special elements in SQL Commands ('SQL Injection' aka 'SQL').
SQL Injection vulnerabilities are predominant in applications that incorporate user input directly into SQL queries without adequate validation or sanitization. This allows malicious users to inject SQL commands that can alter the functionality of the original query.
By launching an SQL injection attack, threat actors may leak sensitive data from the database, alter data for their profit, delete data for denial of service, extract passwords and other credentials for offline dictionary attacks, or gain access to other systems within the network exploiting trust relationships.
Rationale
As with most injection vulnerabilities, inserting external input into SQL code is common practice when dealing with dynamic queries, as the engine may not provide options to code a table or column name(s), values for the IN operator, or dynamic WHERE conditions.
However, the concatenation of unsanitized input into SQL code almost always allows for the modification of the intended query.
In Swift, a susceptible code example might look like this:
import SQLite
class UserRepository {
let db: Connection
init(dbPath: String) throws {
db = try Connection(dbPath)
}
func getUserByUsername(_ username: String) throws -> [Row] {
// String interpolation of untrusted input into SQL
let query = "SELECT * FROM users WHERE username = '\(username)'"
// FLAW - Vulnerable to SQL Injection
return try Array(db.prepare(query))
}
}
In this instance, the input username is interpolated directly into the SQL query string, potentially allowing execution of arbitrary SQL code if the input is not controlled.
Remediation
To prevent SQL Injection vulnerabilities, the recommended approach is to use parameterized queries. These structures inherently separate SQL code from data input, neutralizing the potential for input to alter query logic maliciously.
Furthermore, consider these additional precautions:
-
Implement strong input validation and sanitization to enforce expected data patterns and strip potentially harmful characters.
-
Utilize ORM frameworks that abstract direct SQL query writing, inherently minimizing the risk of injection vulnerabilities.
-
Regularly conduct security testing, including SAST reviews, to detect and address potential vulnerabilities early in the development lifecycle.
-
Educate development teams on secure coding practices to integrate security awareness into the development culture.
Here is the revised, secure code example:
import SQLite
class UserRepository {
let db: Connection
init(dbPath: String) throws {
db = try Connection(dbPath)
}
func getUserByUsername(_ username: String) throws -> [Row] {
// Use parameterized query with placeholders
let usersTable = Table("users")
let usernameColumn = Expression<String>("username")
// FIXED: Use query builder with parameters
let query = usersTable.filter(usernameColumn == username)
return try Array(db.prepare(query))
}
}
The use of SQLite.swift’s query builder with parameter binding ensures that the untrusted input is treated strictly as a value, not as part of the SQL command, preventing injection.
Alternatively, when using raw SQL is necessary, use parameter binding:
import SQLite
func getUserByUsername(_ username: String) throws -> [Row] {
// Use parameter binding with ?
let query = "SELECT * FROM users WHERE username = ?"
// FIXED: Bind parameter safely
let statement = try db.prepare(query)
return try Array(statement.bind(username))
}
When using frameworks like GRDB or Core Data, prefer using their built-in query APIs which provide parameterization by default:
// GRDB example
import GRDB
func getUserByUsername(_ username: String) throws -> [User] {
try dbQueue.read { db in
// FIXED: Uses parameterized queries internally
try User.filter(Column("username") == username).fetchAll(db)
}
}
When column or table names are dynamic (such as for ORDER BY), the allowed values should be carefully whitelisted from a predefined map if they depend on untrusted input.
Configuration
The detector has the following configurable parameters:
-
sources, that indicates the source kinds to check. -
neutralizations, that indicates the neutralization kinds to check.
Unless you need to change the default behavior, you typically do not need to configure this detector.
References
-
CWE-89 : Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').
-
OWASP Top 10 2021 - A03 : Injection.
-
OWASP Cheat Sheets Series: SQL Injection Prevention.
-
SQLite.swift - A type-safe, Swift-language layer over SQLite3.
-
GRDB.swift - A toolkit for SQLite databases, with a focus on application development.