PostgreSQL全文搜索中的注入风险主要集中在tsquery输入处理和自定义词典配置环节。直接使用未经处理的用户输入构建tsquery查询会导致操作符注入,攻击者可能通过tsquery语法读取敏感数据或执行未授权操作。例如,用户在搜索框中输入"apple | (SELECT * FROM users)",如果后端直接拼接成tsquery,会触发SQL注入。解决方法是对所有用户输入进行严格的词素化处理和转义,使用plainto_tsquery()或phraseto_tsquery()函数而非字符串拼接。
PostgreSQL全文搜索注入的三种攻击向量第一种是tsquery操作符注入。PostgreSQL的tsquery语法支持AND(&)、OR(|)、NOT(!)等操作符,攻击者可通过注入这些操作符改变搜索逻辑。比如输入"apple | (SELECT * FROM users)"中的竖杠会扩展查询范围。更危险的是注入FOLLOWED BY操作符,可能触发子查询执行。
第二种是词典函数注入。当使用自定义词典配置时,如果词典函数调用未经验证的用户输入,可能通过CREATE TEXT SEARCH CONFIGURATION或ALTER TEXT SEARCH CONFIGURATION语句实现权限提升。例如攻击者注入包含分号的词典参数,可能终止原语句执行后续恶意命令。
第三种是ts_headline函数XSS攻击。该函数用于高亮显示搜索结果中的匹配词,如果返回内容直接输出到网页且未转义HTML特殊字符,可能造成跨站脚本攻击。虽然这不属于SQL注入范畴,但同样是全文搜索功能常见的安全漏洞。
实际攻击场景演示假设有一个简单的全文搜索实现:
-- 危险示例:直接拼接用户输入
SELECT * FROM articles
WHERE to_tsvector('english', content) @@ to_tsquery('english', '''' || user_input || '''');
当user_input值为"apple') || (SELECT current_user) || ('test"时,实际执行的查询变为:
SELECT * FROM articles
WHERE to_tsvector('english', content) @@
to_tsquery('english', 'apple') || (SELECT current_user) || ('test');
这将执行子查询SELECT current_user,泄露数据库当前用户信息。更严重的攻击可能通过CASE语句逐字符提取敏感数据。
五层防御策略第一层:输入预处理。对所有用户输入进行词素化转换,只保留有效的搜索词汇。使用PostgreSQL内置的plainto_tsquery()函数自动处理特殊字符:
-- 安全用法
SELECT * FROM articles
WHERE to_tsvector('english', content) @@ plainto_tsquery('english', user_input);
该函数会将用户输入中的操作符转义为普通词汇,比如"apple | orange"会被处理为"apple"和"orange"的AND查询。
第二层:参数化查询。即使使用全文搜索函数,也应通过参数化查询传递用户输入,避免任何形式的字符串拼接:
-- 使用参数化查询
PREPARE search_plan (text) AS
SELECT * FROM articles
WHERE to_tsvector('english', content) @@ plainto_tsquery('english', $1);
EXECUTE search_plan(user_input);
第三层:权限最小化。创建专门用于全文搜索的数据库角色,仅授予必要的权限:
CREATE ROLE search_user; GRANT CONNECT ON DATABASE mydb TO search_user; GRANT SELECT ON articles TO search_user; REVOKE EXECUTE ON FUNCTION pg_read_file FROM search_user; REVOKE EXECUTE ON FUNCTION pg_ls_dir FROM search_user;
第四层:输出过滤。对ts_headline()函数的返回结果进行HTML编码,防止XSS攻击。在应用层对高亮文本进行转义,或使用安全的模板引擎自动转义。
第五层:词典安全配置。审核所有自定义词典函数,确保不执行动态SQL。检查text search configuration的权限设置:
-- 查看全文搜索配置权限 SELECT cfgname, nspname, cfgowner::regrole FROM pg_ts_config JOIN pg_namespace ON pg_ts_config.cfgnamespace = pg_namespace.oid;高级防护:自定义验证函数
对于需要保留部分操作符的高级搜索场景,可创建白名单验证函数:
CREATE OR REPLACE FUNCTION sanitize_tsquery(input_text text)
RETURNS text AS $$
DECLARE
cleaned_text text;
BEGIN
-- 移除所有非字母数字和允许的操作符
cleaned_text := regexp_replace(input_text, '[^a-zA-Z0-9\s&|!<>():*]', '', 'g');
-- 验证括号匹配
IF (length(regexp_replace(cleaned_text, '[^()]', '', 'g')) % 2) != 0 THEN
RAISE EXCEPTION 'Unbalanced parentheses in search query';
END IF;
-- 限制操作符连续出现
IF cleaned_text ~ '(&|\|){2,}' THEN
RAISE EXCEPTION 'Invalid operator sequence';
END IF;
RETURN cleaned_text;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
监控与审计方案
启用PostgreSQL的日志记录功能,监控异常的全文搜索查询:
-- 在postgresql.conf中配置
log_statement = 'all'
log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a '
log_min_duration_statement = 0
-- 创建专用审计表
CREATE TABLE tsquery_audit (
id serial PRIMARY KEY,
search_query text,
search_time timestamptz DEFAULT now(),
client_ip inet,
user_agent text
);
部署实时监控规则,检测包含敏感关键词的查询模式。例如,当tsquery中出现"pg_"、"user"、"password"等系统表名或函数名时触发警报。同时定期审计text search configuration的变更,记录所有ALTER TEXT SEARCH CONFIGURATION操作。
企业级部署建议在生产环境中,建议采用四层架构:第一层是应用输入验证,使用正则表达式过滤非法字符;第二层是数据库代理,解析所有SQL语句并拦截可疑的tsquery结构;第三层是数据库内置防护,通过扩展如pg_sanitize实现自动转义;第四层是网络层防护,使用WAF规则检测全文搜索注入特征。
对于高安全需求场景,可考虑将全文搜索功能迁移到专用搜索服务,如使用Elasticsearch或专用搜索中间件,实现应用层与数据层的完全分离。这样即使搜索功能被注入,攻击者也无法直接访问主数据库。
版本差异与兼容性PostgreSQL 9.6之前版本存在更多注入风险,因为tsquery语法解析较为宽松。从PostgreSQL 10开始,增加了更严格的语法检查。建议至少使用PostgreSQL 12以上版本,并启用所有安全更新。特别注意不同语言配置的差异,例如使用'simple'词典配置时,某些特殊字符处理方式与'english'配置不同。
最后需要强调的是,没有任何单一防护措施是绝对安全的。必须建立纵深防御体系,结合输入验证、参数化查询、最小权限原则和持续监控,才能有效防范PostgreSQL全文搜索中的注入风险。每次升级PostgreSQL版本或修改全文搜索配置后,都应重新进行安全测试,特别是对边界情况和异常输入的测试。
