搜索功能是Web应用中最容易出问题的入口之一,因为它天然需要接收用户输入并拼接查询条件。很多开发者在处理搜索参数时习惯直接拼SQL字符串,比如把用户输入的关键词直接塞进WHERE子句,这就给SQL注入留下了可乘之机。攻击者可以在搜索框提交精心构造的payload,绕过登录、拖取整表数据甚至控制数据库服务器。要加固搜索模块,核心思路是把“数据”和“代码”彻底分离,同时配合白名单校验、参数化查询、最小权限和异常信息脱敏。
先看一个典型的危险写法。假设后端使用Java的JDBC直接拼接语句:
String keyword = request.getParameter("keyword");
String sql = "SELECT * FROM products WHERE name LIKE '%" + keyword + "%'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
攻击者在搜索框输入:
' OR '1'='1' --
最终执行的SQL会变成:
SELECT * FROM products WHERE name LIKE '%' OR '1'='1' -- %'
这条语句会返回products表中的全部记录,搜索条件被完全绕过。如果攻击者进一步使用UNION SELECT、报错注入或时间盲注,就能逐步摸清数据库结构并窃取敏感数据。搜索模块之所以高危,是因为它往往不要求登录,且返回结果会直接展示在页面上,攻击者可以快速验证注入是否成功。
参数化查询是根本防线参数化查询(Prepared Statement)的原理是让数据库在解析SQL语句时就把结构固定下来,用户输入只作为参数值传入,数据库不会把参数内容当作SQL代码执行。以JDBC为例,正确的写法是:
String keyword = request.getParameter("keyword");
String sql = "SELECT * FROM products WHERE name LIKE ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "%" + keyword + "%");
ResultSet rs = pstmt.executeQuery();
这里的问号是占位符,keyword无论包含什么字符,都只会被当作一个普通的字符串值处理。即使攻击者输入单引号、OR、注释符等,也不会改变SQL语句的结构。PHP的PDO、Python的PyMySQL、.NET的SqlCommand等主流数据库访问接口都支持参数化查询,应作为搜索功能的第一选择。需要注意的是,参数化查询不能用于表名、字段名、排序方向这类标识符,如果搜索模块允许用户选择排序字段或排序方式,必须使用白名单映射,不能直接把用户输入拼进去。
LIKE查询的特殊处理搜索功能大量使用LIKE进行模糊匹配,而LIKE本身有通配符%和_。如果不过滤,用户输入%会匹配所有记录,输入_会匹配任意单字符,这在某些场景下可能被用来探测数据长度或绕过搜索限制。更严重的是,如果搜索关键词直接拼进LIKE后面的字符串,注入风险会进一步放大。建议在参数化查询的基础上,对用户输入中的通配符进行转义。不同数据库的转义方式略有差异,以MySQL为例,可以在应用层把用户输入中的反斜杠、百分号和下划线进行转义:
String escapedKeyword = keyword
.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_");
pstmt.setString(1, "%" + escapedKeyword + "%");
同时需要在SQL中指定ESCAPE子句:
SELECT * FROM products WHERE name LIKE ? ESCAPE '\\'
这样用户输入的%和_就只作为普通字符参与匹配,不会触发LIKE的通配符语义。如果业务上允许用户使用通配符进行高级搜索,可以设计独立的“高级搜索”选项,并明确告知用户通配符的使用规则,同时在服务端对通配符数量做限制,防止用户输入大量%导致全表扫描或性能问题。
输入校验与长度限制参数化查询解决了注入问题,但输入校验仍然不能省略。搜索关键词通常有合理的长度范围,比如商品名称搜索一般不超过100个字符,文章标题搜索不超过200个字符。在服务端对搜索参数做严格校验,可以提前拦截异常输入,减少数据库压力,也能防御一些基于超长字符串的拒绝服务攻击。校验规则应根据业务场景设计,例如:只允许中英文、数字、空格和少量标点符号,禁止控制字符和SQL关键字。但要注意,黑名单过滤SQL关键字不是可靠的防注入手段,因为攻击者可以通过编码、大小写混写、注释插入等方式绕过。白名单校验才是更可靠的做法,即只允许明确合法的字符集通过。
// 示例:只允许字母、数字、中文、空格和连字符
if (!keyword.matches("[\\w\\u4e00-\\u9fa5\\s-]{1,100}")) {
throw new IllegalArgumentException("搜索关键词包含非法字符");
}
如果搜索模块需要支持特殊符号,比如书名号、括号等,可以在白名单中显式加入。关键是明确“允许什么”,而不是“禁止什么”。
数据库权限最小化搜索功能使用的数据库账号不应该拥有过高权限。很多应用为了方便,直接使用root或具有ALL PRIVILEGES的账号连接数据库,一旦搜索模块被注入,攻击者就能执行任意SQL语句,包括读写文件、执行系统命令(在特定数据库配置下)。正确的做法是为搜索功能创建独立的数据库账号,只授予对应表的SELECT权限,不授予INSERT、UPDATE、DELETE、DROP、CREATE等权限。如果搜索涉及多表关联,也只授予这些表的只读权限。这样即使注入发生,攻击者也无法修改数据或执行危险操作。对于MySQL,可以这样创建最小权限账号:
CREATE USER 'search_user'@'localhost' IDENTIFIED BY 'strong_password'; GRANT SELECT ON app_db.products TO 'search_user'@'localhost'; GRANT SELECT ON app_db.categories TO 'search_user'@'localhost'; FLUSH PRIVILEGES;
同时,数据库账号的连接来源应限制为应用服务器所在的IP或主机名,不要开放远程任意主机连接。应用服务器与数据库服务器之间应通过内网通信,数据库端口不对公网开放。
错误信息脱敏与日志监控搜索模块在发生SQL错误时,不应把数据库的原始错误信息返回给前端。攻击者经常利用数据库报错信息来推断表结构、字段名和注入是否成功。应用层应捕获SQL异常,记录详细错误到服务端日志,但只向前端返回统一的友好提示,例如“搜索服务暂时不可用,请稍后再试”。日志中需要记录完整的请求参数、用户标识、时间戳和异常堆栈,便于后续分析和溯源。
同时,建议对搜索接口的请求频率做限制,防止攻击者使用自动化工具进行批量注入尝试。可以在应用层或网关层实现简单的限流,比如同一IP每分钟最多请求30次搜索接口,超过则返回429状态码。对于登录用户,可以结合账号维度进行限流。限流不能完全阻止注入攻击,但能显著提高攻击成本,并为监控告警争取时间。
ORM框架的正确使用很多项目使用Hibernate、MyBatis、Entity Framework等ORM框架,但ORM并不能自动防止SQL注入,关键在于使用方式。以MyBatis为例,如果使用${}进行字符串拼接,同样存在注入风险:
SELECT * FROM products WHERE name LIKE '%${keyword}%'
这种写法必须改为#{}参数占位符:
SELECT * FROM products WHERE name LIKE CONCAT('%', #{keyword}, '%')
Hibernate的HQL和Criteria API在参数绑定方面相对安全,但原生SQL查询(createNativeQuery)如果拼接字符串,同样会引入注入漏洞。使用ORM时,应优先使用框架提供的参数绑定机制,避免在查询语句中直接拼接用户输入。对于动态排序、动态表名等场景,必须通过白名单映射或枚举来限制可选值。
搜索引擎与数据库分离对于数据量大、搜索逻辑复杂的业务,可以考虑将搜索功能迁移到专门的搜索引擎,如Elasticsearch、OpenSearch或Solr。搜索引擎本身也需要防范注入,但其查询语法与SQL不同,攻击面相对更可控。搜索引擎通常提供结构化的查询DSL,应用层通过JSON构建查询条件,天然避免了SQL字符串拼接的问题。不过,搜索引擎同样需要做输入校验、权限控制和错误信息脱敏,不能因为换了存储引擎就放松警惕。如果业务规模较小,暂时无法引入搜索引擎,至少应确保数据库查询全部参数化,并对搜索字段建立合适的索引,避免全表扫描带来的性能问题。
安全测试与持续加固搜索模块的加固不是一次性的工作,需要在开发流程中持续进行。建议在代码审查中重点检查所有涉及用户输入的SQL语句,确认没有字符串拼接。同时,使用自动化安全扫描工具对搜索接口进行定期测试,模拟常见的SQL注入payload,验证防护是否有效。测试用例应覆盖:单引号闭合、OR/AND注入、UNION注入、注释符截断、LIKE通配符滥用、超长输入、编码绕过等场景。对于发现的漏洞,要追溯到代码层面修复,而不是仅靠WAF或网关规则拦截。
搜索功能防SQL注入的核心可以总结为:参数化查询是底线,输入校验是辅助,最小权限是纵深防御,错误脱敏和日志监控是最后一道防线。每一层都做到位,搜索模块的安全性才能有实质提升。攻击者往往从最薄弱的环节突破,因此任何一层缺失都可能让整体防护失效。
