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;
/
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_NAME | Schema |
| 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;
/
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. 最佳实践
- 绑定变量:必须
- DBMS_ASSERT:验证
- 输入验证:完整
- 最小权限:安全
- VPD:行级
- Redaction:脱敏
- 加密:敏感
- 审计:监控
- 错误处理:友好
- 测试:安全
15. 参考资料
[1] Oracle Database PL/SQL Language Reference 19c, “Security” https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/plsql-security.html