Using DBMS_ASSERT

本文介绍了如何使用DBMS_ASSERT函数集保护PL/SQL代码免受SQL注入和对象名攻击,通过实例展示了如何增强安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

If the condition which determines the property asserted in a function is not met then a value error is raised. Otherwise the input value is returned via return value. Most functions return the value unchanged, however, several functions modify the value.

ENQUOTE_LITERAL Function
This function enquotes a string literal.
Syntax
DBMS_ASSERT.ENQUOTE_LITERAL (
str VARCHAR2)
RETURN VARCHAR2;
Usage Notes
Add leading and trailing single quotes to a string literal.
Verify that all single quotes except leading and trailing characters are paired with adjacent single quotes.

ENQUOTE_NAME Function
This function encloses a name in double quotes.
Syntax
DBMS_ASSERT.ENQUOTE_NAME (
str VARCHAR2,
capitalize BOOLEAN DEFAULT TRUE)
RETURN VARCHAR2;
If TRUE or defaulted, alphabetic characters of str which was not in quotes are translated to upper case

NOOP Functions
This function returns the value without any checking.
Syntax
DBMS_ASSERT.NOOP (
str VARCHAR2 CHARACTER SET ANY_CS)
RETURN VARCHAR2 CHARACTER SET str%CHARSET;
DBMS_ASSERT.NOOP (
str CLOB CHARACTER SET ANY_CS)
RETURN CLOB CHARACTER SET str%CHARSET;

QUALIFIED_SQL_NAME Function
This function verifies that the input string is a qualified SQL name.
Syntax
DBMS_ASSERT.QUALIFIED_SQL_NAME (
str VARCHAR2 CHARACTER SET ANY_CS)
RETURN VARCHAR2 CHARACTER SET str%CHARSET;Exceptions
ORA44004: string is not a qualified SQL name

SCHEMA_NAME Function
This function verifies that the input string is an existing schema name.
Syntax
DBMS_ASSERT.SCHEMA_NAME (
str VARCHAR2 CHARACTER SET ANY_CS)
RETURN VARCHAR2 CHARACTER SET str%CHARSET;
Exceptions
ORA44001: Invalid schema name
Usage Notes
By definition, a schema name need not be just a simple SQL name. For example, "FIRST LAST" is a valid schema name. As a consequence, care must be taken to quote the output of schema name before concatenating it with SQL text.

SIMPLE_SQL_NAME Function
This function verifies that the input string is a simple SQL name.
Syntax
DBMS_ASSERT.SIMPLE_SQL_NAME (
str VARCHAR2 CHARACTER SET ANY_CS)
RETURN VARCHAR2 CHARACTER SET str%CHARSET;
Exceptions
ORA44003: string is not a simple SQL name
Usage Notes
The input value must be meet the following conditions:
The name must begin with an alphabetic character. It may contain alphanumeric characters as well as the characters _, $, and # in the second and subsequent character positions.
Quoted SQL names are also allowed.
Quoted names must be enclosed in double quotes.
Quoted names allow any characters between the quotes.
Quotes inside the name are represented by two quote characters in a row, for example, "a name with "" inside" is a valid quoted name.
The input parameter may have any number of leading and/or trailing white space characters.
The length of the name is not checked.

SQL_OBJECT_NAME Function
This function verifies that the input parameter string is a qualified SQL identifier of an existing SQL object.
Syntax
DBMS_ASSERT.SQL_OBJECT_NAME (
str VARCHAR2 CHARACTER SET ANY_CS)
RETURN VARCHAR2 CHARACTER SET str%CHARSET;
Exceptions

ORA44002: Invalid object name



NOOP
The NOOP (No Operation) function performs no error checking and returns the input string as it was entered.

SIMPLE_SQL_NAME
The SIMPLE_SQL_NAME function checks the input string conforms to the basic characteristics of a simple SQL name.
The first character of the name is alphabetic.
The name only contains alphanumeric characters or the "_", "$", "#"
Quoted names must be enclosed by double quotes and may contain any characters, including quotes provided they are represented by two quotes in a row ("").
The function ignores leading and trailing white spaces are ignored
The length of the input string is not validated.
The "ORA-44003: invalid SQL name" exception is raised when the input string does not conform.

QUALIFIED_SQL_NAME
The QUALIFIED_SQL_NAME function checks the input string conforms to the basic characteristics of a qualified SQL name. A qualified name can be made up of several simple SQL names representing the names of the schema, object and database links. The supported syntax for qualified SQL names is displayed below.

SCHEMA_NAME
The SCHEMA_NAME function checks that the input string represents an existing schema name. The function is case sensitive and accepts quoted schema names. The "ORA-44001: invalid schema" exception is raised when the input string does not match an existing schema name.

SQL_OBJECT_NAME
The SQL_OBJECT_NAME function checks that the input string represents an existing object. The function is not case sensitive, unless the input is quoted. If a database link is specified, only the syntax off the name is checked, not the existence of the object at the remote location. The "ORA-44002: invalid object name" exception is raised when the input string does not match an existing object name.

ENQUOTE_NAME
The ENQUOTE_NAME function encloses the input string within double quotes, unless they are already present. It also checks that all other double quotes are present in adjacent pairs. If individual double quotes are found, the "ORA-06502: PL/SQL: numeric or value error" exception is raised. By default, the output of non-quoted input strings is capitalized, but this functionality can be modified in PL/SQL by setting the capitalize parameter to FALSE.

ENQUOTE_LITERAL
The ENQUOTE_LITERAL function encloses the input string within single quotes, and checks that all other single quotes are present in adjacent pairs. If individual single quotes are found, the "ORA-06502: PL/SQL: numeric or value error" exception is raised.

Example 1
This example shows how SQL injection can be used to retrieve additional data when used against badly written dynamic SQL. Create the following schema objects.

CREATE TABLE open_tab (
code VARCHAR2(5),
description VARCHAR2(50)
);

INSERT INTO open_tab VALUES ('ONE', 'Description for ONE');
INSERT INTO open_tab VALUES ('TWO', 'Description for TWO');
COMMIT;

CREATE TABLE secret_tab (
code VARCHAR2(5),
description VARCHAR2(50)
);

INSERT INTO secret_tab VALUES ('CODE1', 'SECRET 1');
INSERT INTO secret_tab VALUES ('CODE2', 'SECRET 2');
COMMIT;
The following procedure uses unnecessary dynamic SQL to display a description for the supplied value.

CREATE OR REPLACE PROCEDURE get_open_data(p_code IN VARCHAR2) AS
l_sql VARCHAR2(32767);
c_cursor SYS_REFCURSOR;
l_buffer VARCHAR2(32767);
BEGIN
l_sql := 'SELECT description FROM open_tab WHERE code = ''' || p_code || '''';

OPEN c_cursor FOR l_sql;
LOOP
FETCH c_cursor
INTO l_buffer;
EXIT WHEN c_cursor%NOTFOUND;

DBMS_OUTPUT.put_line(l_buffer);
END LOOP;
END;
/
When run as intended, it produces the expected output.

SQL> SET SERVEROUTPUT ON
SQL> EXEC get_open_data('ONE');
Description for ONE

PL/SQL procedure successfully completed.

SQL>
The following output shows how to SQL inject it to display all information from the table.

SQL> EXEC get_open_data('ONE'' OR ''1''=''1');
Description for ONE
Description for TWO

PL/SQL procedure successfully completed.

SQL>
In addition, we can use UNION to display data from other tables as well.

SQL> EXEC get_open_data('ONE'' UNION SELECT description FROM secret_tab WHERE ''1''=''1');
Description for ONE
SECRET 1
SECRET 2

PL/SQL procedure successfully completed.

SQL>
Using DBMS_ASSERT.ENQUOTE_LITERAL function, we can protect this procedure as follows.

CREATE OR REPLACE PROCEDURE get_open_data(p_code IN VARCHAR2) AS
l_sql VARCHAR2(32767);
c_cursor SYS_REFCURSOR;
l_buffer VARCHAR2(32767);
BEGIN
l_sql := 'SELECT description FROM open_tab WHERE code = ' ||
SYS.DBMS_ASSERT.ENQUOTE_LITERAL(p_code);

OPEN c_cursor FOR l_sql;
LOOP
FETCH c_cursor
INTO l_buffer;
EXIT WHEN c_cursor%NOTFOUND;

DBMS_OUTPUT.put_line(l_buffer);
END LOOP;
END;
/
The following output shows the procedure now works as expected, but prevents these basic SQL injection attacks.

SQL> SET SERVEROUTPUT ON
SQL> EXEC get_open_data('ONE');
Description for ONE

PL/SQL procedure successfully completed.

SQL> EXEC get_open_data('ONE'' OR ''1''=''1');
BEGIN get_open_data('ONE'' OR ''1''=''1'); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.DBMS_ASSERT", line 342
ORA-06512: at "SYS.DBMS_ASSERT", line 411
ORA-06512: at "TEST.GET_OPEN_DATA", line 6
ORA-06512: at line 1


SQL> EXEC get_open_data('ONE'' UNION SELECT description FROM secret_tab WHERE ''1''=''1');
BEGIN get_open_data('ONE'' UNION SELECT description FROM secret_tab WHERE ''1''=''1'); END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SYS.DBMS_ASSERT", line 342
ORA-06512: at "SYS.DBMS_ASSERT", line 411
ORA-06512: at "TEST.GET_OPEN_DATA", line 6
ORA-06512: at line 1

SQL>
Example 2

This example shows how badly written dynamic SQL can be used to perform a type of DOS attack. Create the following procedure to count the number of rows in a specified table.

CREATE OR REPLACE PROCEDURE get_tab_row_count(p_table_name IN VARCHAR2) AS
l_sql VARCHAR2(32767);
l_count NUMBER;
BEGIN
l_sql := 'SELECT COUNT(*) INTO :l_count FROM ' || p_table_name;

EXECUTE IMMEDIATE l_sql INTO l_count;

DBMS_OUTPUT.put_line('l_count = ' || l_count);
END;
/
When used as designed, it counts the number of rows in the table.

SQL> SET SERVEROUTPUT ON
SQL> EXEC get_tab_row_count('open_tab');
l_count = 2

PL/SQL procedure successfully completed.

SQL>
We can SQL inject it to create a cartesian product against another table.

SQL> EXEC get_tab_row_count('open_tab, all_objects');
l_count = 112632

PL/SQL procedure successfully completed.

SQL>
Imagine the impact if we had passed in a parameter like 'massive_table, massive_table, massive_table'. We could add significant load to the system, possibly causing a DOS attack.

The following function using the DBMS_ASSERT.SQL_OBJECT_NAME procedure to check the parameter value is a valid object name.

CREATE OR REPLACE PROCEDURE get_tab_row_count(p_table_name IN VARCHAR2) AS
l_sql VARCHAR2(32767);
l_count NUMBER;
BEGIN
l_sql := 'SELECT COUNT(*) INTO :l_count FROM ' ||
SYS.DBMS_ASSERT.SQL_OBJECT_NAME(p_table_name);

EXECUTE IMMEDIATE l_sql INTO l_count;

DBMS_OUTPUT.put_line('l_count = ' || l_count);
END;
/
The following output shows the procedure is now protected from this type of attack.

SQL> EXEC get_tab_row_count('open_tab');
l_count = 2

PL/SQL procedure successfully completed.

SQL> EXEC get_tab_row_count('test.open_tab');
l_count = 2

PL/SQL procedure successfully completed.

SQL> EXEC get_tab_row_count('open_tab, all_objects');
BEGIN get_tab_row_count('open_tab, all_objects'); END;

*
ERROR at line 1:
ORA-44002: invalid object name
ORA-06512: at "SYS.DBMS_ASSERT", line 316
ORA-06512: at "TEST.GET_TAB_ROW_COUNT", line 5
ORA-06512: at line 1


SQL> EXEC get_tab_row_count('missing_tab');
BEGIN get_tab_row_count('missing_tab'); END;

*
ERROR at line 1:
ORA-44002: invalid object name
ORA-06512: at "SYS.DBMS_ASSERT", line 316
ORA-06512: at "TEST.GET_TAB_ROW_COUNT", line 5
ORA-06512: at line 1


SQL>

declare v_1 date := TO_DATE('20250301', 'YYYYMMDD'); v_start date; v_end date := trunc(sysdate); v_current date; v_sql varchar2(5000); begin v_start :=trunc(v_1); for i in 0..(v_end-v_start) loop v_current := v_start+i; v_sql := 'insert into PURE_REPAYMENT_AMOUNT with t1 as (SELECT a.*,CASE WHEN a.COUNTRY_CODE = ''tz'' THEN a.IN_FORCE_DATE + 3 / 24 WHEN a.COUNTRY_CODE = ''ke'' THEN a.IN_FORCE_DATE + 3 / 24 WHEN a.COUNTRY_CODE = ''ci'' THEN a.IN_FORCE_DATE WHEN a.COUNTRY_CODE = ''ph'' THEN a.IN_FORCE_DATE + 8 / 24 END AS NEW_DATE FROM LOAN_BORROW_INFO_TEST a WHERE a.COUNTRY_CODE <> ''gh'' AND a.RISK_SERIAL_NO <> ''googleplay'' AND a.ARCHIVED=1 and a.IN_FORCE_DATE is not null), t2 as (select distinct phone,country_code,NEW_DATE from t1 where TRUNC(NEW_DATE)=TO_DATE(''' || TO_CHAR(v_current, 'YYYYMMDD') || ''', ''YYYYMMDD'')and user_type_product=0), t3 as ( select t1.NEW_DATE,t1.REPAYMENT_AMOUNT from t2 left join t1 on t2.phone=t1.phone and t2.country_code=t1.country_code and t1.NEW_DATE>=t2.NEW_DATE and t1.new_date is not null), t4 as( select week_num,TO_DATE(''' || TO_CHAR(v_current, 'YYYYMMDD') || ''', ''YYYYMMDD'') as pure_in_force_date,SUM(REPAYMENT_AMOUNT/100) AS total_AMOUNT from (SELECT REPAYMENT_AMOUNT, TRUNC((NEW_DATE - MIN(NEW_DATE) OVER ()) / 7)+1 AS week_num, MIN(NEW_DATE) OVER () + TRUNC((NEW_DATE - MIN(NEW_DATE) OVER ()) / 7) * 7 AS week_start, MIN(NEW_DATE) OVER () + TRUNC((NEW_DATE - MIN(NEW_DATE) OVER ()) / 7) * 7 + 6 AS week_end FROM t3 WHERE NEW_DATE BETWEEN TO_DATE(''2025-03-01'', ''YYYY-MM-DD'') AND TRUNC(ADD_MONTHS(SYSDATE, 12), "YYYY") - 1) GROUP BY week_num ORDER BY week_num), t5 as(select * from t4 pivot(sum(total_AMOUNT) for week_num IN (1 AS "第一周", 2 AS "第二周", 3 AS "第三周", 4 AS "第四周", 5 AS "第五周", 6 AS "第六周", 7 AS "第七周", 8 AS "第八周", 9 AS "第九周", 10 AS "第十周", 11 AS "第十一周", 12 AS "第十二周", 13 AS "第十三周", 14 AS "第十四周", 15 AS "第十五周", 16 AS "第十六周", 17 AS "第十七周", 18 AS "第十八周", 19 AS "第十九周", 20 AS "第二十周", 21 AS "第二十一周", 22 AS "第二十二周", 23 AS "第二十三周", 24 AS "第二十四周", 25 AS "第二十五周", 26 AS "第二十六周", 27 AS "第二十七周", 28 AS "第二十八周", 29 AS "第二十九周", 30 AS "第三十周", 31 AS "第三十一周", 32 AS "第三十二周", 33 AS "第三十三周", 34 AS "第三十四周", 35 AS "第三十五周", 36 AS "第三十六周", 37 AS "第三十七周", 38 AS "第三十八周", 39 AS "第三十九周", 40 AS "第四十周", 41 AS "第四十一周", 42 AS "第四十二周", 43 AS "第四十三周"))) select t5.* from t5'; EXECUTE IMMEDIATE v_sql; END LOOP; end;改成mergeinto的语句
06-07
DECLARE v_max_add_time TIMESTAMP; v_check_time TIMESTAMP := SYSTIMESTAMP; -- 统一检测时间 v_sql VARCHAR2(4000); v_actual_value NVARCHAR2(4000); v_null_token CONSTANT NVARCHAR2(20) := '<<<NULL>>>'; -- 特殊标记用于空值比较 TYPE result_rec IS RECORD ( order_no NVARCHAR2(64), tb_name NVARCHAR2(128), col_name NVARCHAR2(128), cf_value NVARCHAR2(512), add_time TIMESTAMP, scene_code NVARCHAR2(32), scene_name NVARCHAR2(128), strategy_id NUMBER(19), strategy_name NVARCHAR2(128), rule_id NUMBER(19), rule_name NVARCHAR2(128) ); TYPE result_tab IS TABLE OF result_rec; v_results result_tab; -- 检查表名是否存在的函数 FUNCTION table_exists(p_table_name VARCHAR2) RETURN BOOLEAN IS v_count NUMBER; BEGIN SELECT COUNT(*) INTO v_count FROM all_tables WHERE table_name = UPPER(p_table_name) AND owner = 'GHANA_RISK'; -- 替换为实际schema名 RETURN (v_count > 0); EXCEPTION WHEN OTHERS THEN RETURN FALSE; END; -- 检查列名是否存在的函数 FUNCTION column_exists(p_table_name VARCHAR2, p_column_name VARCHAR2) RETURN BOOLEAN IS v_count NUMBER; BEGIN SELECT COUNT(*) INTO v_count FROM all_tab_columns WHERE table_name = UPPER(p_table_name) AND column_name = UPPER(p_column_name) AND owner = 'GHANA_RISK'; -- 替换为实际schema名 RETURN (v_count > 0); EXCEPTION WHEN OTHERS THEN RETURN FALSE; END; -- 获取列数据类型函数 FUNCTION get_column_data_type( p_table_name VARCHAR2, p_column_name VARCHAR2 ) RETURN VARCHAR2 IS v_data_type VARCHAR2(128); BEGIN SELECT data_type INTO v_data_type FROM all_tab_columns WHERE owner = 'GHANA_RISK' AND table_name = UPPER(p_table_name) AND column_name = UPPER(p_column_name); RETURN v_data_type; EXCEPTION WHEN OTHERS THEN RETURN 'UNKNOWN'; END; BEGIN -- 1. 获取最新检测时间范围 SELECT MAX(ADD_TIME) INTO v_max_add_time FROM GHANA_RISK.TQ_RULE_ITEM_RESULT_7; -- 2. 获取半小时内需检测的数据 SELECT TQ_ORDER_NO, TB_NAME, COL_NAME, CF_VALUE, ADD_TIME, SCENE_CODE, SCENE_NAME, STRATEGY_ID, STRATEGY_NAME, RULE_ID, RULE_NAME BULK COLLECT INTO v_results FROM GHANA_RISK.TQ_RULE_ITEM_RESULT_7 WHERE ADD_TIME BETWEEN v_max_add_time - INTERVAL '10' MINUTE AND v_max_add_time; -- 3. 遍历每条记录并动态检测 FOR i IN 1..v_results.COUNT LOOP DECLARE v_data_type VARCHAR2(128); v_conversion_expr VARCHAR2(100); BEGIN -- 验证表名和列名是否存在 IF NOT table_exists(v_results(i).tb_name) THEN RAISE_APPLICATION_ERROR(-20001, 'Table not found: ' || v_results(i).tb_name); END IF; IF NOT column_exists(v_results(i).tb_name, v_results(i).col_name) THEN RAISE_APPLICATION_ERROR(-20002, 'Column not found: ' || v_results(i).col_name); END IF; -- 获取列数据类型并构建转换表达式 v_data_type := get_column_data_type(v_results(i).tb_name, v_results(i).col_name); CASE v_data_type WHEN 'NUMBER' THEN -- 数字类型:使用TO_CHAR保留格式(包括小于1的前导0) v_conversion_expr := 'TO_CHAR(' || v_results(i).col_name || ', ''FM9999999999999999999990D99999999999999999999'')'; WHEN 'VARCHAR2', 'CHAR', 'NVARCHAR2', 'NCHAR' THEN -- 字符类型:直接使用原列 v_conversion_expr := v_results(i).col_name; ELSE -- 其他类型:默认转换为字符串 v_conversion_expr := 'TO_CHAR(' || v_results(i).col_name || ')'; END CASE; -- 构建动态SQL(转换为NVARCHAR2以匹配cf_value类型) v_sql := 'SELECT TO_NCHAR(' || v_conversion_expr || ') ' || 'FROM GHANA_RISK.' || v_results(i).tb_name || ' ' || 'WHERE TQ_ORDER_NO = :1'; -- 执行动态查询 EXECUTE IMMEDIATE v_sql INTO v_actual_value USING v_results(i).order_no; -- 4. 比较值并插入差异 IF NVL(v_actual_value, v_null_token) <> NVL(v_results(i).cf_value, v_null_token) THEN INSERT INTO FK_ADS.ADS_MONITORING_RESULT_DI ( TQ_ORDER_NO, TB_NAME, COL_NAME, CF_VALUE, ACTUAL_VALUE, CHECK_TIME, ADD_TIME, SCENE_CODE, SCENE_NAME, STRATEGY_ID, STRATEGY_NAME, RULE_ID, RULE_NAME ) VALUES ( v_results(i).order_no, v_results(i).tb_name, v_results(i).col_name, v_results(i).cf_value, v_actual_value, v_check_time, v_results(i).add_time, v_results(i).scene_code, v_results(i).scene_name, v_results(i).strategy_id, v_results(i).strategy_name, v_results(i).rule_id, v_results(i).rule_name ); END IF; EXCEPTION WHEN NO_DATA_FOUND THEN INSERT INTO FK_ADS.ADS_MONITORING_RESULT_DI ( TQ_ORDER_NO, TB_NAME, COL_NAME, CF_VALUE, ACTUAL_VALUE, CHECK_TIME, ADD_TIME, SCENE_CODE, SCENE_NAME, STRATEGY_ID, STRATEGY_NAME, RULE_ID, RULE_NAME ) VALUES ( v_results(i).order_no, v_results(i).tb_name, v_results(i).col_name, v_results(i).cf_value, 'RECORD_NOT_FOUND', v_check_time, v_results(i).add_time, v_results(i).scene_code, v_results(i).scene_name, v_results(i).strategy_id, v_results(i).strategy_name, v_results(i).rule_id, v_results(i).rule_name ); COMMIT; END; /报错[65000][6550] ORA-06550: 第 116 行, 第 24 列: PLS-00103: 出现符号 ","在需要下列之一时: * & = - + < / > at in is mod remainder not rem then <an exponent (* ...
最新发布
07-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值