Oracle PL/SQL 安全编程详解

Oracle PL/SQL 安全编程详解

适用版本:Oracle Database 10g / 11g / 12c / 19c / 23ai 文档版本:v1.0 / 2026-07


1. 概述

PL/SQL 安全编程防 SQL 注入与权限滥用[1]:

详细见:Oracle SQL 注入防护


2. SQL 注入

2.1 危险

-- 危险
CREATE OR REPLACE PROCEDURE bad(p_name VARCHAR2) IS
  v_count NUMBER;
BEGIN
  EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM employees WHERE name = ''' || p_name || ''''
    INTO v_count;
END;
/

-- 输入 ' OR '1'='1
-- SELECT COUNT(*) FROM employees WHERE name = '' OR '1'='1'

2.2 防护 - 绑定变量

-- 安全
CREATE OR REPLACE PROCEDURE good(p_name VARCHAR2) IS
  v_count NUMBER;
BEGIN
  EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM employees WHERE name = :name'
    INTO v_count USING p_name;
END;
/

详细见:Oracle PL/SQL 动态 SQL 详解

2.3 防护 - DBMS_ASSERT

-- 表名
EXECUTE IMMEDIATE 'SELECT * FROM ' || DBMS_ASSERT.QUALIFIED_SQL_NAME(p_table);

-- 字符串
EXECUTE IMMEDIATE 'SELECT * FROM t WHERE name = ' || DBMS_ASSERT.ENQUOTE_LITERAL(p_name);

-- Schema
DBMS_ASSERT.SCHEMA_NAME(p_schema);
DBMS_ASSERT.SQL_OBJECT_NAME(p_obj);
DBMS_ASSERT.SIMPLE_SQL_NAME(p_name);

2.4 函数

函数用途
ENQUOTE_LITERAL字符串字面量
ENQUOTE_NAME标识符
QUALIFIED_SQL_NAME限定名
SCHEMA_NAMESchema
SQL_OBJECT_NAME对象
SIMPLE_SQL_NAME简单名

3. 权限

3.1 最小权限

- 仅必要权限
- 角色
- 视图
- 存储过程

3.2 DEFINER vs CURRENT_USER

-- DEFINER(默认):创建者权限
CREATE PROCEDURE p AUTHID DEFINER IS ...

-- CURRENT_USER:调用者权限
CREATE PROCEDURE p AUTHID CURRENT_USER IS ...

详细见:Oracle 存储过程与函数详解

3.3 角色不可用

- DEFINER 权限存储过程
- 角色不可用
- 直接授权

4. VPD

4.1 策略

BEGIN
  DBMS_RLS.ADD_POLICY(
    object_schema => 'SCOTT',
    object_name => 'employees',
    policy_name => 'emp_policy',
    function_schema => 'SCOTT',
    policy_function => 'emp_security',
    statement_types => 'SELECT, UPDATE, DELETE'
  );
END;
/

4.2 函数

CREATE OR REPLACE FUNCTION emp_security(
  schema_var VARCHAR2, table_var VARCHAR2
) RETURN VARCHAR2 IS
  v_user VARCHAR2(30);
  v_dept NUMBER;
BEGIN
  v_user := SYS_CONTEXT('USERENV', 'SESSION_USER');
  
  SELECT dept_id INTO v_dept FROM users WHERE username = v_user;
  
  RETURN 'dept_id = ' || v_dept;
END;
/

4.3 效果

-- 用户只能看自己部门
SELECT * FROM employees;
-- 自动加 WHERE dept_id = ?

详细见:Oracle VPD 详解


5. 加密

5.1 TDE

-- 透明数据加密
ALTER SYSTEM SET ENCRYPTION KEY IDENTIFIED BY ******

ALTER TABLE employees MODIFY (salary ENCRYPT);

详细见:Oracle TDE 详解

5.2 DBMS_CRYPTO

-- 哈希
SELECT DBMS_CRYPTO.HASH(UTL_RAW.CAST_TO_RAW('password'), 4) FROM dual;

-- 加密
DECLARE
  v_key RAW(32) := UTL_I18N.STRING_TO_RAW('mykey', 'AL32UTF8');
  v_enc RAW(2000);
BEGIN
  v_enc := DBMS_CRYPTO.ENCRYPT(
    src => UTL_I18N.STRING_TO_RAW('secret', 'AL32UTF8'),
    typ => DBMS_CRYPTO.ENCRYPT_AES256 + DBMS_CRYPTO.CHAIN_CBC + DBMS_CRYPTO.PAD_PKCS5,
    key => v_key
  );
END;
/

详细见:Oracle PL/SQL 内置包大全详解


6. 审计

6.1 标准

AUDIT SELECT, INSERT, UPDATE, DELETE ON employees BY ACCESS;
AUDIT EXECUTE ON my_proc BY ACCESS;

6.2 FGA

BEGIN
  DBMS_FGA.ADD_POLICY(
    object_schema => 'SCOTT',
    object_name => 'employees',
    policy_name => 'audit_high_salary',
    audit_condition => 'salary > 10000',
    audit_column => 'salary'
  );
END;
/

6.3 统一审计(12c+)

CREATE AUDIT POLICY emp_audit
  PRIVILEGES SELECT ANY TABLE
  ACTIONS SELECT ON scott.employees
  WHEN 'SYS_CONTEXT(''USERENV'', ''SESSION_USER'') = ''HR'''
  EVALUATE PER STATEMENT;

详细见:Oracle 审计详解


7. 敏感数据

7.1 Data Redaction

BEGIN
  DBMS_REDACT.ADD_POLICY(
    object_schema => 'SCOTT',
    object_name => 'employees',
    policy_name => 'redact_salary',
    column_name => 'salary',
    function_type => DBMS_REDACT.FULL,
    expression => 'SYS_CONTEXT(''USERENV'', ''SESSION_USER'') != ''HR'''
  );
END;
/

7.2 视图

-- 隐藏列
CREATE VIEW emp_public AS
SELECT id, name, dept_id FROM employees;

-- 掩码
CREATE VIEW emp_mask AS
SELECT id, name,
  CASE WHEN SYS_CONTEXT('USERENV', 'SESSION_USER') = 'HR' 
       THEN salary ELSE NULL END AS salary
FROM employees;

8. 输入验证

8.1 长度

IF LENGTH(p_name) > 100 THEN
  RAISE_APPLICATION_ERROR(-20001, 'Name too long');
END IF;

8.2 格式

IF NOT REGEXP_LIKE(p_email, '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') THEN
  RAISE_APPLICATION_ERROR(-20002, 'Invalid email');
END IF;

8.3 范围

IF p_salary < 0 OR p_salary > 1000000 THEN
  RAISE_APPLICATION_ERROR(-20003, 'Invalid salary');
END IF;

9. 错误处理

9.1 不泄露信息

EXCEPTION
  WHEN OTHERS THEN
    log_error(SQLCODE, SQLERRM);  -- 内部
    RAISE_APPLICATION_ERROR(-20000, 'Internal error');  -- 用户
END;

9.2 日志

CREATE OR REPLACE PROCEDURE log_error(p_code NUMBER, p_msg VARCHAR2) IS
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO error_log (code, msg, user, time)
  VALUES (p_code, p_msg, USER, SYSTIMESTAMP);
  COMMIT;
END;
/

详细见:Oracle PL/SQL 异常处理详解


10. 密码

10.1 哈希

-- 推荐 SHA256+
SELECT DBMS_CRYPTO.HASH(UTL_I18N.STRING_TO_RAW('password' || 'salt', 'AL32UTF8'), 4)
FROM dual;

10.2 PBKDF2

- 多次迭代
- Salt
- 强度

10.3 不存储明文

- 哈希
- Salt
- 验证

11. 应用上下文

11.1 创建

CREATE OR REPLACE CONTEXT app_ctx USING ctx_pkg;

11.2 设置

CREATE OR REPLACE PACKAGE ctx_pkg AS
  PROCEDURE set_user(p_user VARCHAR2);
END;
/

CREATE OR REPLACE PACKAGE BODY ctx_pkg AS
  PROCEDURE set_user(p_user VARCHAR2) IS
  BEGIN
    DBMS_SESSION.SET_CONTEXT('app_ctx', 'user', p_user);
  END;
END;
/

EXEC ctx_pkg.set_user('alice');

11.3 使用

SELECT SYS_CONTEXT('app_ctx', 'user') FROM dual;

12. 应用场景

12.1 Web 应用

- 绑定变量
- 输入验证
- 最小权限
- VPD
- 审计

12.2 内部应用

- 角色控制
- 数据脱敏
- 加密
- 日志

12.3 报表

- 视图限制
- VPD
- Redaction
- 审计

13. 常见坑与排错

13.1 SQL 注入

- 拼接字符串
- 绑定变量
- DBMS_ASSERT

13.2 权限过度

- PUBLIC 授权
- 最小权限

13.3 错误泄露

- 异常细节
- 用户友好
- 日志

14. 最佳实践

  1. 绑定变量:必须
  2. DBMS_ASSERT:验证
  3. 输入验证:完整
  4. 最小权限:安全
  5. VPD:行级
  6. Redaction:脱敏
  7. 加密:敏感
  8. 审计:监控
  9. 错误处理:友好
  10. 测试:安全

15. 参考资料

[1] Oracle Database PL/SQL Language Reference 19c, “Security” https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/plsql-security.html