SQL Injection is a type of database attack where an attacker injects malicious SQL code into query parameters, gaining unauthorized access to data or the ability to modify it. According to OWASP (2025), SQL Injection remains one of the critical vulnerabilities capable of leading to complete database compromise. SQL code injection allows an attacker to read, modify, and delete records, and in some cases, gain access to the server's operating system.
Key Takeaways
SQL Injection is a vulnerability that occurs when an application builds SQL queries by concatenating strings with user data. An attacker passes a specially crafted string into a query parameter that changes the structure of the SQL command. Instead of being data, the injected string becomes part of the SQL code, allowing the attacker to execute arbitrary queries against the database. According to the Verizon Data Breach Report (2025), SQL Injection is present in 8% of all investigated data breaches.
Despite being a well-known vulnerability (first mentioned in the late 1990s), SQL Injection is still found in modern applications. The reason is human error: developers write code with string concatenation, legacy code is not refactored, and ORM frameworks are used incorrectly (e.g., raw queries with string interpolation). According to Veracode (2026), about 14% of all scanned applications contain at least one SQLi vulnerability.
A successful SQL Injection gives the attacker a wide range of capabilities: reading any database tables, including password hashes and personal user data; modifying and deleting records; executing administrative operations (DROP TABLE, TRUNCATE); and in some configurations, remote command execution via xp_cmdshell (MSSQL) or INTO OUTFILE (MySQL). Consequences range from user data leakage to complete loss of system control.
SQL injections are classified by how data is extracted from the database. The choice of method depends on how the application handles query results and error messages. The OWASP classification identifies three main types: In-band (data extracted through the same channel), Inferential/Blind (logical inferences), and Out-of-band (data transmitted through another channel).
| Type | Data Extraction Method | Complexity | Frequency |
|---|---|---|---|
| In-band (classic) | Directly through the query result | Low | High |
| Blind SQLi | Logical inferences from server responses | High | Medium |
| Out-of-band | Through an external channel (DNS, HTTP) | Medium | Low |
The most common type. An attacker injects SQL code into a query parameter, and the result of the injection is directly visible in the server response. Two subtypes: Error-based (through DB error messages) and UNION-based (through the UNION SELECT operator). Error-based uses information from error messages, such as a MySQL syntax error that may reveal the table name or query structure. UNION-based allows combining legitimate query results with data from other database tables.
Used when the application does not display query results or error messages. An attacker asks yes/no questions by sending queries with logical conditions and analyzing differences in server responses (e.g., response time or page content). Time-based Blind SQLi uses delay functions (SLEEP, WAITFOR DELAY) to confirm conditions — if the page loads longer, the condition is true. This method is very slow — extracting a single record can take hours.
# Example of Blind SQL Injection (time-based)
# If SQLi is vulnerable, SLEEP(2) executes if condition is met
import requests
import time
payload = "' OR IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',SLEEP(2),0) -- "
url = "http://example.com/user?id=1" + payload
start = time.time()
response = requests.get(url)
elapsed = time.time() - start
# If response arrives after >2 seconds — the first password letter is 'a'
print(f"Time: {elapsed:.2f}s - First letter is 'a'" if elapsed > 2 else "First letter is not 'a'")
Data is transmitted not via HTTP response but through alternative channels: DNS queries, HTTP requests to an external server, SMTP. Used when the application does not return query results or display errors. MySQL supports the LOAD_FILE() function, which can trigger a DNS request, and MSSQL has xp_dirtree for sending data to a remote SMB server. Out-of-band SQL Injection is effective but requires additional attacker server setup and specific DB functions.
The SQL Injection mechanism is based on SQL using quotes for string literals. If an application inserts user input directly into an SQL query without escaping, an attacker can “close” the string and add arbitrary SQL code. For example, in the query SELECT * FROM users WHERE name = '$input', inserting ' OR '1'='1 transforms it into SELECT * FROM users WHERE name = '' OR '1'='1', which returns all users.
Consider a login form with the query SELECT * FROM users WHERE username = '$user' AND password = '$pass'. If an attacker enters admin' -- in the username field and leaves the password blank, the resulting query becomes SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''. The -- characters comment out the rest of the query, disabling the password check. The server returns the admin user record, and the attacker logs in without knowing the password.
# Example of SQL Injection — Authentication Bypass
# VULNERABLE CODE: direct string concatenation
def login_vulnerable(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
return cursor.fetchone() is not None
# username = "admin' --" nullifies the password check
# SAFE VERSION: parameterized query
def login_secure(username, password):
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
return cursor.fetchone() is not None
The UNION SELECT operator allows combining results from two SELECT queries. If an attacker finds a vulnerable parameter, they add UNION SELECT with a query from another table. For example: ' UNION SELECT username, password FROM admins --. The success condition is that the number of columns in both queries must match. The column count is determined through ORDER BY (inserting ' ORDER BY 1--, then 2, 3... until an error). Knowing the column count, the attacker inserts UNION SELECT with the same number of fields.
Mobile applications face SQL Injection both on the server side (API) and on the client side — in local databases (SQLite, Realm). While server-side SQLi in mobile APIs is similar to web applications, local databases create an additional vector. If an application stores data in SQLite and executes queries with string concatenation, malicious data entering the local database via API can trigger SQLi during subsequent processing.
The local SQLite database on a device is also vulnerable to SQL Injection if the application builds queries by concatenating strings. Content Providers on Android and Core Data on iOS use parameterization by default, but raw queries require developer attention. SQLite does not support multiple queries separated by semicolons, which limits the attacker's options but does not protect against data reading via WHERE conditions. Always use selectionArgs in Android and NSPredicate with parameters in iOS.
The API that a mobile application communicates with is vulnerable just like any web server. Mobile developers often assume SQLi is only a backend issue, but the vulnerability occurs at the API endpoint that accepts parameters from the client. Separation of responsibilities does not protect: if the backend developer forgot to parameterize the query, the user's mobile app becomes an attack vector. Require the backend to use ORM or prepared statements.
// Example of SQL Injection in Local SQLite on Android
// VULNERABLE CODE: direct concatenation
fun getUserVulnerable(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = $userId", null
)
}
// SAFE VERSION: parameterization via selectionArgs
fun getUserSecure(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = ?",
arrayOf(userId)
)
}
Protection against SQL Injection is based on a simple principle: never trust user input in SQL queries. The only reliable method is query parameterization (prepared statements), where SQL code and data are passed separately. All other methods — escaping, validation, WAF — are additional security layers but do not replace parameterization. According to OWASP (2025), parameterization prevents 100% of SQL Injection attacks.
When using prepared statements, the SQL query is first compiled by the DB server without data, and then parameter values are passed separately. The database treats parameters as data, not as executable code. Even if an attacker passes ' OR '1'='1, the database interprets it as a string value, not as SQL code. Prepared statements are supported by all modern languages and frameworks: PDO in PHP, PreparedStatement in Java, cursor.execute in Python.
Modern ORMs (Hibernate, Entity Framework, SQLAlchemy, Room) automatically use parameterization when executing queries, unless the developer switches to raw queries. However, ORMs do not fully protect: constructs like @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) in JPA require passing parameters via named parameters, not concatenation. Query Builders (Knex, jOOQ) also parameterize queries by default if raw methods are not used.
Escaping special characters (mysql_real_escape_string) is an outdated method that does not protect against all types of SQL Injection. The problem: escaping depends on encoding and can be bypassed when using multi-byte encodings (e.g., GBK in Asian systems). Use escaping only in legacy code where parameterization is impossible, and always in combination with strict input type validation.
| Method | Effectiveness | Recommendation |
|---|---|---|
| Prepared Statements | 100% | Mandatory for all queries |
| ORM (correct usage) | 99% | Recommended |
| String Escaping | 70% (depends on encoding) | Legacy only |
| Input Validation (white-list) | 50% (numbers only) | Additional |
| WAF (Web Application Firewall) | 60% | Additional |
The application account should have minimum necessary privileges: SELECT, INSERT, UPDATE, DELETE — only on tables that the application actually requires. Prohibit the use of DROP, TRUNCATE, CREATE for the application account. This limits damage even in case of a successful SQL Injection: the attacker will not be able to delete tables or execute administrative operations.
Regular SQL Injection testing should be part of the secure development pipeline. A combination of static analysis, dynamic scanning, and manual penetration testing yields the best results. According to the Synopsys Cybersecurity Report (2025), automated scanners find up to 70% of SQLi vulnerabilities, but complex Blind attacks require manual testing.
For mobile applications, local SQLite analysis is also important: check all rawQuery calls, ContentProvider queries, and Room queries with rawQuery. Tools: Android Studio Lint (detects SQLi in SQLite), MobSF (Mobile Security Framework) for automatic static and dynamic APK/IPA analysis. It is also recommended to test API endpoints using sqlmap with proxy interception of the mobile application's traffic.
Frequently Asked Questions
SQL Injection attacks relational databases through SQL queries. NoSQL Injection targets non-relational databases (MongoDB, Couchbase) through their query operators ($gte, $ne, $where). In MongoDB, injection is possible if the application builds a BSON document from a JSON string. Protection mechanisms are similar: parameterization and type validation.
Find all places where SQL queries are formed by string concatenation with user data. Look for patterns like "SELECT ... WHERE id = " + userId or f"UPDATE ... SET name = '{name}'". Each such line is a potential SQL injection. Replace all of them with parameterized queries or prepared statements.
ORM frameworks automatically protect only if you use their Query Builder methods and named parameters. If you use raw queries (nativeQuery in JPA, rawQuery in Room), the protection does not work — you must pass parameters through prepared expressions, not through string concatenation.
Second-Order SQL Injection is an attack where malicious data is stored in the database as safe, but then used in another query without escaping. For example, an attacker registers with a username like ' OR '1'='1. The data is saved as a string — there is no attack at registration. But if another query uses the username in SQL without parameterization, the injection triggers.
NoSQL Injection can be potentially more dangerous due to lower developer awareness. Developers know about SQL Injection and most use ORMs, but few know about NoSQL Injection. In MongoDB, an improperly formed query can return all documents in a collection. Protection is the same — prepared statements (BSON parameterization) and strict input validation.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also