前言
ORACLE数据库系统可以说是当今市面上最常见的数据库系统了,相信各位在工作和学习的过程中经常能接触到ORACLE数据库,那么utlpwdmg.sql作为ORACLE自带的密码复杂度校验脚本来说,对ORACLE的安全也举足轻重。可能由于我搜索姿势不对,在网上很少找到关于utlpwdmg.sql这个文件的文章(可能因为太简单了???),在下便在此浅显的分析下该文件。本人主要目的是在此记录一下自己分析和学习的过程,可能会有不对的地方,也请各位大佬多多指点。
作为一个使用数据库的人,我们经常会对数据库是否安全产生顾虑,而密码复杂度和密码策略则是安全计算环境的重要组成部分。那么本文就以ORACLE11g数据库的密码复杂度校验脚本utlpwdmg.sql为例,分析该脚本中各项参数的内容,以及如何修改使其符合等保标准
一、utlpwdmg.sql是什么?
utlpwdmg.sql是ORACLE数据库自带的密码校验脚本。其路径一般为$ORACLE_HOME/RDBMS/ADMIN/utlpwdmg.sql
二、使用步骤
1.进入对应路径
cd $ORACLE_HOME/RDBMS/ADMIN/
如不进入该路径,则会在应用脚本时报错
SQL> @utlpwdmg.sql SP2-0310: 无法打开文件 "utlpwdmg.sql"
2.登录数据库
sqlplus /nolog
3.使用sysdba应用utlpwdmg.sql
SQL>conn / as sysdba SQL>@utlpwdmg.sql
如回显如下则说明应用成功
函数已创建。 授权成功。 配置文件已更改 函数已创建。 授权成功。
4.结果查询
SQL> select * from dba_profiles where profile='DEFAULT'; PROFILE RESOURCE_NAME RESOURCE LIMIT ------------------------------ -------------------------------- -------- ---------------------------------------- DEFAULT COMPOSITE_LIMIT KERNEL UNLIMITED DEFAULT SESSIONS_PER_USER KERNEL UNLIMITED DEFAULT CPU_PER_SESSION KERNEL UNLIMITED DEFAULT CPU_PER_CALL KERNEL UNLIMITED DEFAULT LOGICAL_READS_PER_SESSION KERNEL UNLIMITED DEFAULT LOGICAL_READS_PER_CALL KERNEL UNLIMITED DEFAULT IDLE_TIME KERNEL UNLIMITED DEFAULT CONNECT_TIME KERNEL UNLIMITED DEFAULT PRIVATE_SGA KERNEL UNLIMITED DEFAULT FAILED_LOGIN_ATTEMPTS PASSWORD 10 DEFAULT PASSWORD_LIFE_TIME PASSWORD 180 DEFAULT PASSWORD_REUSE_TIME PASSWORD UNLIMITED DEFAULT PASSWORD_REUSE_MAX PASSWORD UNLIMITED DEFAULT PASSWORD_VERIFY_FUNCTION PASSWORD VERIFY_FUNCTION_11G DEFAULT PASSWORD_LOCK_TIME PASSWORD 1 DEFAULT PASSWORD_GRACE_TIME PASSWORD 7
可以看到 PASSWORD_VERIFY_FUNCTION 字段已经被指定为了 VERIFY_FUNCTION_11G 密码复杂度校验函数。
三、utlpwdmg.sql分析
打开utlpwdmg.sql,我们可以看到在文件头部做了一些注释,说明了该文件的作用,表明
“这是一个通过设置默认密码资源限制来启用密码管理功能的脚本”,当然这里不是重点。
Rem Rem $Header: rdbms/admin/utlpwdmg.sql /st_rdbms_11.2.0/1 2013/01/31 01:34:11 skayoor Exp $ Rem Rem utlpwdmg.sql Rem Rem Copyright (c) 2006, 2013, Oracle and/or its affiliates. Rem All rights reserved. Rem Rem NAME Rem utlpwdmg.sql - script for Default Password Resource Limits Rem Rem DESCRIPTION Rem This is a script for enabling the password management features Rem by setting the default password resource limits. Rem Rem NOTES Rem This file contains a function for minimum checking of password Rem complexity. This is more of a sample function that the customer Rem can use to develop the function for actual complexity checks that the Rem customer wants to make on the new password. Rem Rem MODIFIED (MM/DD/YY) Rem skayoor 01/17/13 - Backport skayoor_bug-14671375 from main Rem asurpur 05/30/06 - fix - 5246666 beef up password complexity check Rem nireland 08/31/00 - Improve check for username=password. #1390553 Rem nireland 06/28/00 - Fix null old password test. #1341892 Rem asurpur 04/17/97 - Fix for bug479763 Rem asurpur 12/12/96 - Changing the name of password_verify_function Rem asurpur 05/30/96 - New script for default password management Rem asurpur 05/30/96 - Created Rem -- This script sets the default password resource parameters -- This script needs to be run to enable the password features. -- However the default resource parameters can be changed based -- on the need. -- A default password complexity function is also provided. -- This function makes the minimum complexity checks like -- the minimum length of the password, password not same as the -- username, etc. The user may enhance this function according to -- the need. -- This function must be created in SYS schema. -- connect sys/<password> as sysdba before running the script
接着,文件中定义了一个密码复杂度校验函数 VERIFY_FUNCTION_11G ,并预先声明了一些将要使用的变量及其变量类型。
CREATE OR REPLACE FUNCTION verify_function_11G (username varchar2, password varchar2, old_password varchar2) RETURN boolean IS n boolean; m integer; differ integer; isdigit boolean; ischar boolean; db_name varchar2(40); digitarray varchar2(20); chararray varchar2(52); i_char varchar2(10); simple_password varchar2(10); reverse_user varchar2(32);
接着是函数体部分,这块儿才是重点 ,BEGIN开始函数体,为变量digitarray、chararray赋值,用于后续检测密码中是否含有数字和大小写字母。
BEGIN digitarray:= '0123456789'; chararray:= 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
接着正式进入了密码复杂度检测的部分,首先对密码长度进行了检测,要求密码长度大于等于8位,若密码长度小于8位,则放回一个错误-20001
-- Check for the minimum length of the password IF length(password) < 8 THEN raise_application_error(-20001, 'Password length less than 8'); END IF;
接着比对了密码和用户名的相似度,若密码与用户名相同或与用户名后拼接上1-100的数字相同,则返回错误 -20002,-20005
-- Check if the password is same as the username or username(1-100) IF NLS_LOWER(password) = NLS_LOWER(username) THEN raise_application_error(-20002, 'Password same as or similar to user'); END IF; FOR i IN 1..100 LOOP i_char := to_char(i); if NLS_LOWER(username)|| i_char = NLS_LOWER(password) THEN raise_application_error(-20005, 'Password same as or similar to user name '); END IF; END LOOP;
判断密码与用户名的逆序是否相同,若相同则返回错误 -20003
-- Check if the password is same as the username reversed FOR i in REVERSE 1..length(username) LOOP reverse_user := reverse_user || substr(username, i, 1); END LOOP; IF NLS_LOWER(password) = NLS_LOWER(reverse_user) THEN raise_application_error(-20003, 'Password same as username reversed'); END IF;
比对了密码和服务器名的相似度,若密码与服务器名相同或与服务器名后拼接上1-100的数字相同,则返回错误 -20004,-20005
-- Check if the password is the same as server name and or servername(1-100) select name into db_name from sys.v$database; if NLS_LOWER(db_name) = NLS_LOWER(password) THEN raise_application_error(-20004, 'Password same as or similar to server name'); END IF; FOR i IN 1..100 LOOP i_char := to_char(i); if NLS_LOWER(db_name)|| i_char = NLS_LOWER(password) THEN raise_application_error(-20005, 'Password same as or similar to server name '); END IF; END LOOP;
比对密码是否为‘welcome1’、‘database1’、‘account1’、‘user1234’这类弱口令,若是则返回错误-20006
-- Check if the password is too simple. A dictionary of words may be -- maintained and a check may be made so as not to allow the words -- that are too simple for the password. IF NLS_LOWER(password) IN ('welcome1', 'database1', 'account1', 'user1234', 'password1', 'oracle123', 'computer1', 'abcdefg1', 'change_on_install') THEN raise_application_error(-20006, 'Password too simple'); END IF;
比对密码是否为oracle或oracle拼接上1-100的数字,若相同则返回错误 -20007
-- Check if the password is the same as oracle (1-100) simple_password := 'oracle'; FOR i IN 1..100 LOOP i_char := to_char(i); if simple_password || i_char = NLS_LOWER(password) THEN raise_application_error(-20007, 'Password too simple '); END IF; END LOOP;
接下来是verify_function_11G函数判断密码是否含有数字和字符的部分
判断密码是否包含至少一个数字(0-9),若不包含则返回错误-20008
-- Check if the password contains at least one letter, one digit -- 1. Check for the digit isdigit:=FALSE; m := length(password); FOR i IN 1..10 LOOP FOR j IN 1..m LOOP IF substr(password,j,1) = substr(digitarray,i,1) THEN isdigit:=TRUE; GOTO findchar; END IF; END LOOP; END LOOP; IF isdigit = FALSE THEN raise_application_error(-20008,'Password should contain at least one \ digit, one character'); END IF;
判断密码是否包含至少一个字母(a-z,A-Z),若不包含则返回错误-20009
-- 2. Check for the character <<findchar>> ischar:=FALSE; FOR i IN 1..length(chararray) LOOP FOR j IN 1..m LOOP IF substr(password,j,1) = substr(chararray,i,1) THEN ischar:=TRUE; GOTO endsearch; END IF; END LOOP; END LOOP; IF ischar = FALSE THEN raise_application_error(-20009, 'Password should contain at least one \ digit, one character'); END IF; <<endsearch>>
到这一步,VERIFY_FUNCTION_11G这个密码复杂度校验函数就结束了。
判断新密码是否与原密码至少存在3个不同的字符,如果不存在则返回错误-20011
-- Check if the password differs from the previous password by at least -- 3 letters IF old_password IS NOT NULL THEN differ := length(old_password) - length(password); differ := abs(differ); IF differ < 3 THEN IF length(password) < length(old_password) THEN m := length(password); ELSE m := length(old_password); END IF; FOR i IN 1..m LOOP IF substr(password,i,1) != substr(old_password,i,1) THEN differ := differ + 1; END IF; END LOOP; IF differ < 3 THEN raise_application_error(-20011, 'Password should differ from the \ old password by at least 3 characters'); END IF; END IF; END IF; -- Everything is fine; return TRUE ; RETURN(TRUE); END; /
赋予PUBLIC用户verify_function的函数执行权限,并更改ORACLE的profile文件,配置密码的最常使用期限为180天、密码过期锁定时间为7天(即密码超过180天使用期限后若7天内不更改密码则会将该账户锁定)、密码重用时间为不限制、密码重用最大时间为不限制(两个参数都为UNLIMITED时,密码可以随意重用,两参数均为指定值时,必须都满足才可以重用密码。两参数有其中一个不为UNLIMITED,则密码不能重用)、登陆失败锁定次数为10次、密码锁定时间为一天、密码复杂度校验函数为verify_function_11G。
GRANT EXECUTE ON verify_function_11G TO PUBLIC; -- This script alters the default parameters for Password Management -- This means that all the users on the system have Password Management -- enabled and set to the following values unless another profile is -- created with parameter values set to different value or UNLIMITED -- is created and assigned to the user. ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME 180 PASSWORD_GRACE_TIME 7 PASSWORD_REUSE_TIME UNLIMITED PASSWORD_REUSE_MAX UNLIMITED FAILED_LOGIN_ATTEMPTS 10 PASSWORD_LOCK_TIME 1 PASSWORD_VERIFY_FUNCTION verify_function_11G;
以上就是 utlpwdmg.sql文件里面的一个标准组成模板,后续内容与其大同小异,在此不做赘述。
可以看到verify_function_11G该函数仅对密码复杂度做了最简单的判断和过滤,完全不能符合当前情况下的信息安全基线要求。文件中已经给出了大体的校验框架,我们仅仅需要对密码复杂度校验函数verify_function_11G做一些小小的修改就可以使其符合当前情况下的信息安全基线要求。
四、修改密码复杂度校验函数verify_function_11G
为了使得密码符合要求:至少包含大写字母、小写字母、数字、特殊字符 四种其中的三种,首先我们应修改变量chararray varchar2(52),将其分割成upperchar varchar2(26)和lowerchar varchar2(26),并新声明变量punctarray varchar2(25)、flagfortypes integer
CREATE OR REPLACE FUNCTION verify_function_11G (username varchar2, password varchar2, old_password varchar2) RETURN boolean IS n boolean; m integer; differ integer; isdigit boolean; ischar boolean; db_name varchar2(40); digitarray varchar2(20); --chararray varchar2(56); punctarray varchar2(25); upperchar varchar2(26); lowerchar varchar2(26); flagfortypes integer; i_char varchar2(10); simple_password varchar2(10); reverse_user varchar2(32);
分别为四类变量赋值 digitarray punctarray upperchar lowerchar和标志 flagfortypes
BEGIN digitarray:= '0123456789'; upperchar:= 'abcdefghijklmnopqrstuvwxyz'; lowerchar:= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; punctarray:='!"#$%&()``*+,-/:;<=>?_'; flagfortypes:=0
接下来,我们需要对密码复杂度函数verify_function_11G的检查是否含有数字和字符部分进行修改
-- Check if the password contains 3 types of the listed array:integer,upperchar,lowerchar,punct -- 1. Check for the digit isdigit:=FALSE; m := length(password); FOR i IN 1..10 LOOP FOR j IN 1..m LOOP IF substr(password,j,1) = substr(digitarray,i,1) THEN isdigit:=TRUE; flag=flag+1; GOTO findupper; END IF; END LOOP; END LOOP; -- 2. Check for the uppercharacter <<findupper>> ischar:=FALSE; FOR i IN 1..length(upperchar) LOOP FOR j IN 1..m LOOP IF substr(password,j,1) = substr(upperchar,i,1) THEN ischar:=TRUE; flag=flag+1; GOTO findlower; END IF; END LOOP; END LOOP; -- 3. Check for the lowercharacter <<findlower>> ischar:=FALSE; FOR i IN 1..length(lowerchar) LOOP FOR j IN 1..m LOOP IF substr(password,j,1) = substr(lowerchar,i,1) THEN ischar:=TRUE; flag=flag+1; GOTO findpunct; END IF; END LOOP; END LOOP; -- 4. Check for the punctarray <<findpunct>> ispunct:=FALSE; FOR i IN 1..length(punctarray) LOOP FOR j IN 1..m LOOP IF substr(password,j,1) = substr(punctarray,i,1) THEN ispunct:=TRUE; flag=flag+1; GOTO checktypes; END IF; END LOOP; END LOOP; -- 5. Check for the types <<checktypes>> IF flag <3 THEN raise_application_error(-20008, 'Password should contain at least 3 types of the listed array:integer,upperchar,lowerchar,punct'); END IF;
这样,VERIFY_FUNCTION_11G这个密码复杂度校验函数就会对密码进行字符类别数量检测,如果字符数量小于3,则会报错。接下来我们还需要修改profile文件的配置将登陆失败锁定次数改为5次、密码锁定时间为10分钟。
GRANT EXECUTE ON verify_function_11G TO PUBLIC; -- This script alters the default parameters for Password Management -- This means that all the users on the system have Password Management -- enabled and set to the following values unless another profile is -- created with parameter values set to different value or UNLIMITED -- is created and assigned to the user. ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME 180 PASSWORD_GRACE_TIME 7 PASSWORD_REUSE_TIME UNLIMITED PASSWORD_REUSE_MAX UNLIMITED FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1/144 PASSWORD_VERIFY_FUNCTION verify_function_11G;
到此,已经可以初步符合当前情况下的信息安全基线要求。
需要再次重复第二节中的内容对utlpwdmg.sql进行应用,以上的修改才会生效