移动开发中的SQL注入:什么是它、攻击方法及防护

作者: IT Sectr 发布日期: 2026-04-06 阅读时间: 9 分钟

SQL Injection — 一种针对应用程序数据库的攻击类型,攻击者将恶意SQL代码注入到查询参数中,从而获得对数据的未授权访问或修改数据的能力。根据OWASP (2025)的数据,SQL注入仍然是可能导致数据库完全受损的关键漏洞之一。SQL代码注入允许攻击者读取、修改和删除记录,在某些情况下还可以访问服务器的操作系统。

要点

  • SQL注入 — 通过用户参数注入SQL代码,改变数据库查询逻辑
  • 查询参数化 — 主要防护方法:通过预编译语句将SQL代码与数据分离
  • 攻击类型 — 经典注入(通过WHERE)、盲SQL注入(逻辑推断)、UNION查询(读取其他表)
  • 基于错误的SQL注入 — 通过数据库错误消息提取数据
  • ORM框架 — 正确使用时降低SQL注入风险,但不能完全消除

什么是SQL注入?

SQL注入是一种漏洞,当应用程序通过字符串拼接用户数据来构建SQL查询时产生。攻击者向查询参数发送一个特制的字符串,该字符串改变了SQL命令的结构。输入的字符串不再是数据,而是成为SQL代码的一部分,使攻击者能够对数据库执行任意查询。根据Verizon数据泄露报告(2025),SQL注入存在于所有被调查数据泄露的8%中。

为什么SQL注入仍然存在?

尽管该漏洞广为人知(首次提及是在20世纪90年代末),SQL注入仍然出现在现代应用中。原因是人为因素:开发人员在代码中允许字符串拼接,遗留代码未进行重构,ORM框架使用不当(例如,通过字符串替换值的原始查询)。根据Veracode(2026)的数据,大约14%的已扫描应用至少包含一个SQLi漏洞。

攻击者可以做什么?

成功的SQL注入为攻击者提供了广泛的能力:读取数据库中的任何表,包括密码哈希和用户的个人数据;修改和删除记录;执行管理操作(DROP TABLE, TRUNCATE);在某些配置下——通过xp_cmdshell(MSSQL)或INTO OUTFILE(MySQL)远程执行命令。后果从用户数据泄露到完全丧失对系统的控制。

SQL注入类型

SQL注入根据从数据库提取数据的方式进行分类。方法的选择取决于应用程序如何处理查询结果和错误消息。OWASP分类区分了三种主要类型:In-band(通过同一通道提取数据)、Inferential/Blind(逻辑推断)和Out-of-band(通过另一通道传输数据)。

类型数据提取方式复杂度频率
In-band(经典)直接通过查询结果
盲SQL注入根据服务器响应进行逻辑推断
Out-of-band通过外部通道(DNS、HTTP)

In-band SQL注入(经典)

最常见的类型。攻击者将SQL代码注入到查询参数中,注入结果直接在服务器响应中可见。两个子类型:基于错误的注入(通过数据库错误消息)和UNION查询注入(通过UNION SELECT运算符)。基于错误的注入利用错误消息中的信息,例如可能泄露表名或查询结构的MySQL语法错误。UNION查询注入允许将合法查询的结果与数据库中其他表的数据合并。

盲SQL注入

当应用程序不显示查询结果或错误消息时使用。攻击者通过发送带有逻辑条件的查询并分析服务器响应的差异(例如响应时间或页面内容)来提出“是/否”问题。基于时间的盲SQL注入使用延迟函数(SLEEP, WAITFOR DELAY)来确认条件——如果页面加载时间更长,则条件为真。这种方法非常缓慢——提取一条记录可能需要数小时。

python
# 盲SQL注入示例(基于时间)
# 如果存在SQLi漏洞,SLEEP(2)在条件满足时执行
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

# 如果响应在>2秒后到达——密码的第一个字母是'a'
print(f"Time: {elapsed:.2f}s - First letter is 'a'" if elapsed > 2 else "First letter is not 'a'")

Out-of-band SQL注入

数据不是通过HTTP响应传输,而是通过替代通道:DNS查询、对外部服务器的HTTP请求、SMTP。当应用程序不返回查询结果且不显示错误时使用。MySQL支持LOAD_FILE()函数,该函数可以发起DNS查询,MSSQL支持xp_dirtree函数用于将数据发送到远程SMB服务器。Out-of-band SQL注入有效,但需要在攻击者的服务器上进行额外配置,并且需要特定的数据库功能存在。

SQL注入如何工作?

SQL注入的机制基于SQL使用引号表示字符串字面量。如果应用程序将用户输入直接替换到SQL查询中而不进行转义,攻击者可以“关闭”字符串并添加任意SQL代码。例如,在查询SELECT * FROM users WHERE name = '$input'中,替换' OR '1'='1将查询变为SELECT * FROM users WHERE name = '' OR '1'='1',这将返回所有用户。

经典示例:绕过身份验证

考虑一个带有查询SELECT * FROM users WHERE username = '$user' AND password = '$pass'的登录表单。如果攻击者在用户名字段中输入admin' -- 并将密码留空,结果查询变为SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''--字符注释掉了查询的其余部分,密码检查条件被禁用。服务器返回管理员用户的记录,攻击者在不知晓密码的情况下进入系统。

python
# SQL注入示例——绕过身份验证
# 脆弱代码:直接字符串拼接
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' --" 取消密码检查
# 安全版本:参数化查询
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

UNION查询注入:读取其他表

UNION SELECT运算符允许合并两个SELECT查询的结果。如果攻击者找到脆弱的参数,他添加一个带有来自其他表的查询的UNION SELECT。例如:' UNION SELECT username, password FROM admins --。成功的条件是——两个查询中的列数必须相同。列数通过ORDER BY确定(替换' ORDER BY 1--,然后23...直到出错)。知道列数后,攻击者替换具有相同字段数的UNION SELECT。

移动应用中的SQL注入

移动应用在服务器端(API)和客户端——本地数据库(SQLite, Realm)中都会遇到SQL注入。虽然移动API中的服务器端SQL注入风险与Web应用类似,但本地数据库创建了额外的攻击向量。如果应用将数据存储在SQLite中并通过字符串拼接执行查询,通过API进入本地数据库的恶意数据可能在后续处理中导致SQL注入。

Android/iOS上的SQLite中的SQL注入

设备上的本地SQLite数据库也容易受到SQL注入的影响,如果应用通过字符串拼接构建查询。Android上的Content Providers和iOS上的Core Data默认使用参数化,但原始查询需要开发人员的注意。SQLite不支持通过分号执行多个查询,这限制了攻击者的能力,但不能防止通过WHERE条件读取数据。在Android上始终使用selectionArgs,在iOS上使用带参数的NSPredicate。

通过移动应用API进行SQL注入

移动应用连接的API与任何Web服务器一样脆弱。移动开发人员通常认为SQLi只是后端的问题,但漏洞出现在接受客户端参数的API端点级别。责任分离不能提供保护:如果后端开发人员忘记参数化查询,用户的移动应用就变成了攻击向量。要求后端使用ORM或预编译语句。

kotlin
// Android本地SQLite中的SQL注入示例
// 脆弱代码:直接拼接
fun getUserVulnerable(db: SQLiteDatabase, userId: String): Cursor {
    return db.rawQuery(
        "SELECT * FROM users WHERE id = $userId", null
    )
}

// 安全版本:通过selectionArgs参数化
fun getUserSecure(db: SQLiteDatabase, userId: String): Cursor {
    return db.rawQuery(
        "SELECT * FROM users WHERE id = ?",
        arrayOf(userId)
    )
}

防止SQL注入的方法

防御SQL注入基于一个简单的原则:永远不要信任SQL查询中的用户输入。唯一可靠的方法是查询参数化(预编译语句),其中SQL代码和数据分开传输。所有其他方法——转义、验证、WAF——是额外的保护层,但不能替代参数化。根据OWASP(2025)的数据,参数化可以防止100%的SQL注入攻击。

预编译语句(参数化查询)

使用预编译语句时,SQL查询首先由数据库服务器编译而不带数据,然后参数值分开传输。数据库将参数视为数据,而不是可执行代码。即使攻击者发送' OR '1'='1,数据库也会将其解释为字符串值,而不是SQL代码。所有现代语言和框架都支持预编译语句:PHP中的PDO、Java中的PreparedStatement、Python中的cursor.execute。

ORM框架和查询构建器

现代ORM(Hibernate, Entity Framework, SQLAlchemy, Room)在执行查询时自动使用参数化,除非开发人员切换到原始查询。然而,ORM不能完全保护:JPA中诸如@Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true)之类的构造需要通过命名参数传递参数,而不是通过拼接。查询构建器(Knex, jOOQ)默认也会参数化查询,如果不使用原始方法的话。

字符串转义(不推荐作为主要方法)

转义特殊字符(mysql_real_escape_string)——一种过时的方法,不能防御所有类型的SQL注入。问题:转义依赖于编码,可以通过使用多字节编码绕过(例如,亚洲系统中的GBK)。仅在不允许参数化的遗留代码中使用转义,并且始终与严格的数据类型验证结合使用。

方法有效性建议
预编译语句100%所有查询必须使用
ORM(正确使用)99%推荐
字符串转义70%(取决于编码)仅遗留代码
输入验证(白名单)50%(仅适用于数字)辅助
WAF(Web应用防火墙)60%辅助

数据库最小权限原则

应用程序账户应具有最低必要权限:SELECT、INSERT、UPDATE、DELETE——仅限应用程序实际需要的表。禁止应用程序账户使用DROP、TRUNCATE、CREATE。这将限制即使成功SQL注入造成的损害:攻击者无法删除表或执行管理操作。

检测SQL注入的工具

定期进行SQL注入测试应成为安全开发流水线的一部分。静态分析、动态扫描和手动渗透测试的结合能提供最佳结果。根据Synopsys网络安全报告(2025),自动扫描器可以发现高达70%的SQLi漏洞,但复杂的盲注攻击需要手动测试。

  • sqlmap — 用于自动检测和利用SQL注入的最流行工具
  • OWASP ZAP — 免费DAST扫描器,带有主动SQLi扫描模块
  • Burp Suite Scanner — 专业工具,可自动检测SQL注入
  • SonarQube — 对漏洞进行静态代码分析,包括SQLi模式
  • CodeQL — 语义代码分析,用于在源代码中搜索SQL注入

对于移动应用,本地SQLite分析也很重要:检查所有rawQuery调用、ContentProvider查询以及带有rawQuery的Room查询。工具:Android Studio Lint(检测SQLite中的SQLi),MobSF(移动安全框架)用于APK/IPA的自动静态和动态分析。还建议通过带有移动应用流量代理拦截的sqlmap测试API端点。

常见问题

SQL注入和NoSQL注入有什么区别?

SQL注入通过SQL查询攻击关系型数据库。NoSQL注入通过查询操作符($gte, $ne, $where)影响非关系型数据库(MongoDB, Couchbase)。在MongoDB中,如果应用程序从JSON字符串构建BSON文档,则可能发生注入。保护机制类似:参数化和类型验证。

如何自己检测代码中的SQL注入?

找到所有通过字符串拼接用户数据构建SQL查询的地方。寻找像"SELECT ... WHERE id = " + userIdf"UPDATE ... SET name = '{name}'"这样的模式。每个这样的字符串都是潜在的SQL注入。全部替换为参数化查询或预编译语句。

ORM能自动防御SQL注入吗?

ORM框架只有在使用它们的查询构建器方法命名参数时才能自动保护。如果使用原始查询(JPA中的nativeQuery,Room中的rawQuery),保护无效——必须通过准备好的表达式传递参数,而不是通过字符串拼接。

什么是二次SQL注入?

二次SQL注入是一种攻击,其中恶意数据作为安全数据存储在数据库中,但随后在另一个查询中未经转义就被使用。例如,攻击者使用用户名' OR '1'='1注册。数据作为字符串存储——注册时没有攻击。但如果另一个查询将用户名替换到SQL中而不进行参数化,注入就会被激活。

NoSQL注入可能比SQL注入更危险吗?

NoSQL注入由于开发人员意识较低而可能更危险。开发人员了解SQL注入,大多数人使用ORM,但很少有人知道NoSQL注入。在MongoDB中,格式错误的查询可能返回集合中的所有文档。保护——相同的预编译语句(BSON参数化)和严格的输入数据验证。

总结

  • SQL注入 — 通过在数据库查询中未转义的用户参数注入SQL代码的攻击
  • 主要类型 — In-band(经典)、盲注入(基于时间)、Out-of-band(通过外部通道)
  • 查询参数化 — 唯一100%可靠的防御SQL注入方法
  • 关于ORM的误解 — ORM只能在使用内置方法时保护,带拼接的原始查询仍然脆弱
  • 最小权限原则 — 限制数据库账户权限,最小化成功攻击时的损害
  • 定期测试 — sqlmap、OWASP ZAP、SonarQube应成为CI/CD流水线的一部分
  • 本地SQLite — 移动应用也应参数化对本地数据库的查询

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读