权限滥用问题在数据库安全中往往被低估,多数人把精力集中在防范SQL注入上,却忽略了数据库内部逻辑同样可能成为攻击载体。存储过程与函数一旦权限配置不当,攻击者即便无法通过注入直接读取数据表,也能利用高权限的存储过程进行提权、横向移动或数据破坏。这类攻击不需要绕过WAF,不需要构造复杂的联合查询,只需要调用一个现成的存储过程,传入精心设计的参数。
存储过程与函数权限模型的核心缺陷数据库的权限体系通常分为连接权限、对象权限和执行权限三个层级。问题在于,很多数据库默认将存储过程的执行权限与定义者权限绑定,而不是调用者权限。这意味着一个低权限用户调用某个存储过程时,实际执行操作的是存储过程创建者的身份。如果创建者是数据库管理员,那么调用者就临时获得了管理员级别的操作能力。这种“定义者权限”机制在Oracle、PostgreSQL、SQL Server等主流数据库中普遍存在,但很多开发和运维人员并不清楚其安全影响。
具体来说,当存储过程使用SECURITY DEFINER(PostgreSQL语法)或AUTHID DEFINER(Oracle语法)创建时,过程体内的所有SQL语句都以创建者的权限执行。攻击者只要能找到这样一个存储过程并且有调用权限,就可以借助它操作原本无权访问的数据。更隐蔽的是,有些存储过程内部存在动态SQL拼接,即便没有直接的注入漏洞,参数传递不当也可能产生次级注入。
典型攻击场景:从低权限到高权限的跃升假设一个Web应用使用受限的数据库账户连接,该账户只有对特定视图的查询权限,没有直接访问底层基表的权限。但应用中有一个用于生成报表的存储过程,该存储过程由高权限账户创建,内部查询了包含敏感信息的基表。攻击者通过应用逻辑漏洞或接口参数篡改,成功调用了这个存储过程并传入恶意参数,就可能提取到基表中的全部数据。
更危险的情况发生在存储过程包含数据修改操作时。一个用于更新用户状态的存储过程,如果内部没有对输入参数做充分的权限校验,攻击者可以传入超出预期的参数范围,修改其他用户的数据甚至提升自己的权限级别。这类攻击在审计日志中往往显示为合法的存储过程调用,难以与正常业务操作区分。
函数索引与表达式注入的隐蔽通道基于函数的索引是另一个容易被忽视的攻击面。数据库允许在表达式上创建索引,例如CREATE INDEX idx_lower_name ON users(LOWER(username))。如果这个自定义函数存在漏洞或者可以被恶意重定义,查询优化器在执行相关查询时就会自动调用该函数。攻击者可以通过修改函数逻辑,在每次索引扫描时执行额外的恶意操作,比如将查询结果写入外部表或发送网络请求。
PostgreSQL中函数可以标记为IMMUTABLE、STABLE或VOLATILE,这些属性直接影响优化器何时调用函数。一个标记为IMMUTABLE但实际行为不纯的函数,可能被优化器在查询规划阶段提前执行,产生预期之外的副作用。MySQL的GENERATED COLUMN使用表达式计算列值,如果表达式引用了可被篡改的存储函数,同样构成持久化后门。
跨数据库链接的权限放大大型企业环境中,数据库之间存在信任关系和链接。一个SQL Server实例可能通过Linked Server连接到另一个实例,Oracle通过Database Link访问远程数据库。存储过程在这些跨库操作中扮演关键角色。如果本地低权限用户可以调用一个访问远程数据库的存储过程,而远程数据库的权限配置又较为宽松,攻击者就能以跳跃方式渗透到内部网络的其他数据库系统。
具体案例中,攻击者首先获得一个只读权限的数据库账户,发现存在一个用于数据同步的存储过程,该过程通过数据库链接从远程系统拉取数据。存储过程内部使用固定凭据或信任关系认证。攻击者调用该过程并注入额外的远程查询语句,成功在远程数据库上执行命令,最终控制了整个数据库集群。这种攻击链条的关键在于存储过程对远程资源的访问权限远大于调用者本地的权限。
存储过程代码中的SQL注入二次利用很多人认为存储过程天然免疫SQL注入,因为参数化是默认行为。但实际情况是,大量存储过程内部使用动态SQL来拼接查询语句,特别是在需要动态表名、列名或排序字段的场景中。如果这些动态拼接的字符串来自用户输入且未经严格过滤,存储过程内部就存在注入点。
CREATE PROCEDURE search_products(IN sort_column VARCHAR(100))
BEGIN
SET @query = CONCAT('SELECT * FROM products ORDER BY ', sort_column);
PREPARE stmt FROM @query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
上述MySQL存储过程接收一个排序字段名作为参数,直接拼接到ORDER BY子句中。攻击者传入"price; DROP TABLE orders--"这样的值,虽然MySQL的PREPARE通常只允许单条语句,但在某些配置下或结合其他技巧仍可能造成破坏。更现实的攻击是使用CASE WHEN语句进行盲注,或者利用EXTRACTVALUE、UPDATEXML等函数进行报错注入提取数据。
权限提升的具体技术手法在Oracle数据库中,如果存储过程使用了INVOKER RIGHTS以外的定义方式,并且过程内引用了调用者无权访问的对象,攻击者可以利用PL/SQL的EXECUTE IMMEDIATE执行任意语句。Oracle的DBMS_SCHEDULER、UTL_HTTP、UTL_FILE等内置包如果被授予了过度的执行权限,低权限用户可以通过调用这些包实现命令执行、文件读写和网络外联。
PostgreSQL中,SECURITY DEFINER函数如果设置了search_path不当,攻击者可以通过创建同名对象来劫持函数内的查询。例如函数内执行SELECT * FROM users,如果search_path优先搜索public模式,攻击者在public模式下创建一个同名的users表,函数就会读取攻击者控制的表而非原始目标表。这种攻击手法极为隐蔽,因为函数代码本身没有任何改动。
审计与发现现有风险的方法要发现数据库中的权限滥用风险,需要系统性地审查存储过程和函数的定义。第一步是列出所有使用高权限定义的存储过程。在PostgreSQL中执行查询:
SELECT proname, proowner, prosecdef FROM pg_proc WHERE prosecdef = true;
Oracle中检查DBA_PROCEDURES视图的AUTHID字段。SQL Server中通过sys.sql_modules和sys.procedures关联查询,关注EXECUTE AS子句的使用情况。第二步是分析这些存储过程的代码,查找动态SQL拼接、外部资源访问、以及是否存在参数注入可能。第三步是检查哪些低权限用户或角色被授予了这些存储过程的执行权限。
对于函数索引和计算列,需要审查索引定义中使用的函数,以及这些函数的权限设置。检查是否存在用户可以修改的函数被用于索引或约束中。数据库链接的审计则需要梳理所有远程连接的定义,确认其认证方式和权限映射规则。
防御策略与最小权限原则的落地修复权限滥用问题的核心原则是:存储过程应以调用者权限执行,除非有明确的业务需求需要使用定义者权限。在必须使用定义者权限的场景下,要严格控制哪些用户可以调用这些存储过程,并在过程内部添加显式的权限检查逻辑。
具体措施包括:将存储过程改为SECURITY INVOKER模式,让操作以调用者的实际权限执行。如果业务逻辑确实需要提权访问,使用代码签名或证书认证的方式授予最小必要权限,而不是直接使用管理员账户创建过程。在存储过程内部,对输入参数进行严格的类型校验和白名单验证,特别是用于动态SQL拼接的参数。设置正确的search_path,将系统模式放在搜索路径的最前面,避免对象劫持。
对于数据库链接,使用映射到最低权限的专用账户,避免使用信任关系或固定凭据。定期审计存储过程的执行日志,关注异常参数模式和调用频率。将数据库的审计功能配置为记录所有存储过程和函数的调用,特别是那些以高权限运行的过程。
开发阶段的防护实践在开发阶段就建立安全的存储过程编码规范,能从根本上减少权限滥用风险。代码审查清单应包括:是否使用了动态SQL,动态SQL的参数是否经过验证,存储过程的定义者是谁,执行权限是否合理,是否存在search_path注入风险,是否引用了外部资源或数据库链接。
对于ORM框架自动生成的存储过程,需要特别关注其权限设置。很多ORM工具为了方便,会使用高权限账户创建存储过程,且默认使用定义者权限。部署前应修改这些默认设置。持续集成流程中加入数据库代码的静态分析,自动检测不安全的权限配置和潜在的注入模式。
存储过程与函数权限滥用是一个需要持续关注的安全领域。它不像SQL注入那样有明显的攻击特征和成熟的防御方案,而是隐藏在数据库内部的权限逻辑中。攻击者一旦掌握了这种利用技巧,就能在看似安全的数据库环境中自由穿行。理解存储过程的权限模型,系统性地审计和加固,才能堵住这条被大多数人忽视的攻击通道。
