一、说明
本篇文章主要说一说oracle数据库中身份鉴别控制点中测评项a的相关内容和理解。
二、测评项a
a)应对登录的用户进行身份标识和鉴别,身份标识具有唯一性,身份鉴别信息具有复杂度要求并定期更换;
三、测评项a要求1
应对登录的用户进行身份标识和鉴别
oracle使用用户名对登录用户进行身份标识,这没啥好说的。oracle在身份鉴别这一块,拥有密码文件验证和操作系统验证两种方式。
密码文件验证的话,是需要登录用户输入用户名、口令的,但是操作系统验证则不需要。
对于数据库特殊账户的操作系统验证,仅由sqlnet.ora文件中的SQLNET.AUTHENTICATION_SERVICES进行控制。
这里大家如果不想麻烦的话,其实也没有必要把SQLNET.AUTHENTICATION_SERVICES参数的值在linux和windows系统中代表的意义都搞清楚,测评的时候直接测试即可。
以centos系统为例子,也就是测评的时候先登录oracle数据库服务器的oracle账户,如果被测评方不清楚oracle账户的口令,也可以登录到root账户,然后使用“su - oracle”命令切换过去。
其实这里需要登录的是属于oinstall用户组的用户,但是一般情况下没人会去改默认配置,默认安装情况下操作系统的oracle用户会属于oinstall用户组。
登录oracle账户成功后,使用sqlplus / as sysdba命令去登录oracle数据库,这里就是使用数据库特殊账户的操作系统验证方式去验证。
如果能登录成功则说明开启了这种验证方式,不能则说明没开启。
如果开启了,那么这个要求项它就不能满足了,顶多是部分符合,因为这种方式登录数据库特殊账户不需要用户名、密码(虽然只能在本地登录,且登录的操作系统账户需要隶属于oinstall用户组),是一个比较大的安全隐患。
还有一种针对数据库普通账户的操作系统验证方式,这种验证方式对于登录的操作系统账户没有啥要求。
先去查看remote_os_authent和os_authent_prefix参数,它们的默认值分别是FALSE和ops$。
如果remote_os_authent是FALSE,那么是否开启这种验证方式还是由SQLNET.AUTHENTICATION_SERVICES决定。
如果开启了,假定os_authent_prefix的值是ops$,假定操作系统中存在一个cv的账户。
那么只要oracle数据库中建立一个名为ops$cv的账户并给与基础权限(连接权限等),用户在登录操作系统中的cv后,可以直接使用sqlplus /或sqlplus / as normal(两个命令是一个意思),以普通账户的身份登录ops$cv账户(当然这里需在cv账户的环境变量里配置一下好方便的使用sqlplus命令)。
同样的,如果开启了这种验证方式,那么也就顶多是部分符合了。
如果remote_os_authent为TRUE,那不用看了,数据库普通账户的操作系统验证方式肯定开启了,并且数据库普通账户的远程操作系统验证方式也开启了。
还是用上面的数据举例子,假定os_authent_prefix的值是ops$,假定远程终端存在一个cv的操作系统账户,那么在远程终端那也可以用空用户名、空口令的方式登录ops$cv账户,更不安全了。
至于空口令,在oracle中似乎不能设置空口令,我反正没有尝试成功……
四、测评项a要求2
身份标识具有唯一性
即用户名不会重复,oracle自动实现,默认符合。
五、测评项a要求3
身份鉴别信息具有复杂度要求
这个要从两个方面看,我个人觉得两个方面都符合才能算达到要求。
第一个方面即实际的口令是否具有一定的复杂度,也即口令至少8位,且包含大写字母、小写字母、数字、特殊字符这四类字符种的三种,且口令不包含简单排列规律,如admin!@#123此类弱口令。
第二个方面就是在oracle中是否设置了口令复杂度策略,这里要看的就是配置表中的PASSWORD_VERIFY_FUNCTION字段的值:
select * from dba_profiles;
这里插一句嘴,说一说配置表的规则,用户是可以自定义多个配置给不同的用户使用。
比如这里的PASSWORD_VERIFY_FUNCTION的Profile字段值为Default,某用户要使用Default的相关配置,就要设置DBA_USERS表中的Profile字段值为Default。
默认情况下,用户使用的都是Default:
select username,profile from DBA_USERS
好,咱们说一说PASSWORD_VERIFY_FUNCTION字段是什么意思,该字段的值应该为oracle中某函数对象的名字,当创建、更改用户口令时会调用到该函数对口令进行校验,默认情况下这里的值是null,也即不使用任何函数对口令进行校验。
在初级教材中,让我们去查看utlpwdmg.sql中的相关信息,其实是不准确的。
因为utlpwdmg.sql并不是函数本身,它只是创建函数的一段语句而已。
实际上在oracle11g中,运行utlpwdmg.sql会创建两个函数,一个是新版本的口令校验函数,一个是老版本的,在里面还会对配置表进行修改,让PASSWORD_VERIFY_FUNCTION的值为新版本的口令校验函数的名字。
这个是utlpwdmg.sql文件中新版本的口令校验函数FUNCTION 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; ispunct boolean; db_name varchar2(40); digitarray varchar2(20); punctarray varchar2(25); chararray varchar2(52); i_char varchar2(10); simple_password varchar2(10); reverse_user varchar2(32);BEGIN digitarray:= '0123456789'; chararray:= 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; -- Check for the minimum length of the password IF length(password) < 8 THEN raise_application_error(-20001, 'Password length less than 8'); END IF; -- 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; -- 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; -- 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; -- 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; -- 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; -- 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 must contain at least one digit, one character'); END IF; -- 2\. Check for the character <> 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 must contain at least one \ digit, and one character'); END IF; <> -- 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;/
然后在这里,对配置文件进行了修改,设置PASSWORD_VERIFY_FUNCTION的值为verify_function_11G:
-- 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 LIMITPASSWORD_LIFE_TIME 180PASSWORD_GRACE_TIME 7PASSWORD_REUSE_TIME UNLIMITEDPASSWORD_REUSE_MAX UNLIMITEDFAILED_LOGIN_ATTEMPTS 10PASSWORD_LOCK_TIME 1PASSWORD_VERIFY_FUNCTION verify_function_11G;
这个是老版本的校验函数FUNCTION verify_function
的定义语句:
-- Below is the older version of the script-- 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/ as sysdba before running the scriptCREATE OR REPLACE FUNCTION verify_function(username varchar2, password varchar2, old_password varchar2) RETURN boolean IS n boolean; m integer; differ integer; isdigit boolean; ischar boolean; ispunct boolean; digitarray varchar2(20); punctarray varchar2(25); chararray varchar2(52);BEGIN digitarray:= '0123456789'; chararray:= 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; punctarray:='!"#$%&()``*+,-/:;<=>?_'; -- Check if the password is same as the username IF NLS_LOWER(password) = NLS_LOWER(username) THEN raise_application_error(-20001, 'Password same as or similar to user'); END IF; -- Check for the minimum length of the password IF length(password) < 4 THEN raise_application_error(-20002, 'Password length less than 4'); END IF; -- 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 ('welcome', 'database', 'account', 'user', 'password', 'oracle', 'computer', 'abcd') THEN raise_application_error(-20002, 'Password too simple'); END IF; -- Check if the password contains at least one letter, one digit and one -- punctuation mark. -- 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(-20003, 'Password should contain at least one digit, one character and one punctuation'); END IF; -- 2\. Check for the character <> 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 findpunct; END IF; END LOOP; END LOOP; IF ischar = FALSE THEN raise_application_error(-20003, 'Password should contain at least one \ digit, one character and one punctuation'); END IF; -- 3\. Check for the punctuation <> 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; GOTO endsearch; END IF; END LOOP; END LOOP; IF ispunct = FALSE THEN raise_application_error(-20003, 'Password should contain at least one \ digit, one character and one punctuation'); END IF; <> -- 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); IF abs(differ) < 3 THEN IF length(password) < length(old_password) THEN m := length(password); ELSE m := length(old_password); END IF; differ := abs(differ); 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(-20004, 'Password should differ by at \ least 3 characters'); END IF; END IF; END IF; -- Everything is fine; return TRUE ; RETURN(TRUE);END;/
所以,实际上想要知道现在实际使用的校验函数的内容,就应该直接在数据库中查看校验函数的内容,而不是去查看utlpwdmg.sql文件。
在这里,使用的是verify_function_11G,由于其内容我没有修改过,所以和utlpwdmg.sql中所定义的是一样的:
至于其中的内容嘛,注释写得很清楚的,我就从上到下大概说明下:
检查口令的最小长度,如果小于8就返回错误,所以口令的最小长度是9
-- 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的数字,比如用户名时user,那么口令如果是user0到user100中的任何一个,也会返回错误。
-- 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;
这里是检查口令是否等于倒序的用户名,或者与其相似,比如用户名是user,则口令不能是r、re、res、resu中的任何一个
-- 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;
这里检查口令是否和数据库名相等,以及是否和数据库名0到数据库名100相等。
我这里是默认的数据库名:ORCL
-- 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;
检查口令是否是一些简单的单词,只不过这里只是一个示例而已,用户可以根据需要多添加一些弱口令。
-- 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;
检查口令是否是oracle1到oracle100中的一个,不过这里口令可以等于oracle,不知道为啥这里是否等于oracle的校验
-- 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;
检查口令是否包含至少一个数字和一个字母(同时包含才可以通过校验)
由于这里字母数组的定义是:chararray:= ‘abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ’;
所以指的是至少一个大写或小写字母。
-- 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 must contain at least one digit, one character'); END IF; -- 2\. Check for the character <> 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 must contain at least one \ digit, and one character'); END IF;
检查口令和旧口令的相似程度:
假设新口令是user,旧口令是admin
新口令长度和旧口令长度的差值大于等于3的话,直接通过校验。
这里的差值是1,所以需要进行校验。
循环的次数为长度小的那一边的口令的长度,也就是user字符串的长度4。
顺序取字符串来对比:
u不等于a,differ的值加1,为1
s不等于d,differ加1,为2
最后循环4次结束,differ为4,
如果最后differ小于3,则未通过校验,大于等3,则通过校验。
-- 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;
最后,如果通过了所有的校验(中途没有返回错误),就直接返回true:
-- Everything is fine; return TRUE ; RETURN(TRUE);
从整个默认的新版的校验函数看来,主要就是口令长度不低于9位,需要同时包含数字、字母(大小写皆可),感觉稍微差了那么一点。
理论上比较好的应该是口令长度不低于8位(这里是9位当然更好了),同时包含数字、大写字母、小写字母、特殊字符其中的3种字符。
另外,不知道为什么,SYS账户的口令的更改,不受到这个校验函数的限制。
登录SYS按道理来说使用的是密码文件或操作系统的验证,所以不知道在USER$中SYS账户的PASSWORD字段的值有什么用,虽然也会跟着口令的更改而更改就是了。
六、测评项a要求4
并定期更换
一方面查看实际的更换时间:
select name,PTIME from USER$
另一方面查看相关的策略,先看和口令过期直接相关的两个参数:
PASSWORD_LIFE_TIME和PASSWORD_GRACE_TIME
这里的时间单位是天,我这里自己修改过,默认情况下PASSWORD_LIFE_TIME的值是180,PASSWORD_GRACE_TIME的值是7。
PASSWORD_LIFE_TIME代表口令过期时间,而PASSWORD_GRACE_TIME则代表口令过期后的宽恕时间。
口令过期但还未超过宽恕期时,登录时会提醒你口令在多久后过期(这里的过期是指超过宽恕期),询问你是否修改口令,可以不修改,然后正常登录账户。
但是超过宽恕期再登录,就无法登录上去了。
oracle在这里的逻辑是这样的,需要通过用户的登录来改变用户的状态。
举个例子,假如PASSWORD_LIFE_TIME的值是1,PASSWORD_GRACE_TIME的值是也是1。
我在2020年4月7日10点整,创建了一个账户,在DBA_USERS表中,该账户的CREATED(创建时间)就是2020年4月7日10点,ACCOUNT_STATUS(账户状态)是OPEN,在USER$表中该账户的PTIME(口令修改时间)也是2020年4月7日10点整。
在DBA_USERS表中,该账户的EXPIRY_DATE是PTIME+PASSWORD_LIFE_TIME的值,也就是2020年4月8日10点整。
(注意,在USER$表中也有一个EXPTIME字段,但是好像这个字段有时候不是实时的,需要用户的登录才会更新,比如现在,该字段就是一个空字符串)
那么,在到达2020年4月8日10点整后,这个账户的ACCOUNT_STATUS是不会自动变成EXPIRED(GRACE)(口令过期但还处于宽恕期内),只要你不登录这个账户,那么该账户会一直处于OPEN状态。哪怕是过了两天,也就是超过PASSWORD_LIFE_TIME+PASSWORD_GRACE_TIME的值,仍然是OPEN状态。
在2020年4月8日10点整后,登录该账户,会更新账户的ACCOUNT_STATUS,更新成EXPIRED(GRACE),而EXPIRY_DATE也会更新,更新为登录时间点+PASSWORD_GRACE_TIME的值。
同样的,如果在这之后一直不登录账户,则超过宽恕期后,账户的状态还是EXPIRED(GRACE),只有等你尝试登录这个账户时,才会更新状态值为EXPIRED,当然这个时候你也登录不上了。
所以根据账户状态值不同,EXPIRY_DATE代表的意思有所不同,状态值为OPEN,则代表过期时间是什么时间。状态值为EXPIRED(GRACE),则代表宽恕期结束是什么时间。
另外,更新PASSWORD_LIFE_TIME值,也会实时的更新账户的EXPIRY_DATE值,但是仅对处于OPEN状态的账户有效。
如某账户为OPEN状态,其EXPIRY_DATE值为2020年4月8日10点,此时将PASSWORD_LIFE_TIME更改为2,则EXPIRY_DATE值就变成2020年4月9日10点了。
但是如果该账户是处于EXPIRED(GRACE)状态,修改PASSWORD_LIFE_TIME或PASSWORD_GRACE_TIME对它都是没用的。
不知道为什么,SYS账户不受到口令过期的限制,我测试的时候确实是这样。
可能是因为它属于特殊账户?毕竟判断是否过期也要去先读取数据库,而SYS账户可以在数据库未开启的时候连接它。
与口令过期间接相关的两个参数:PASSWORD_REUSE_TIME和PASSWORD_REUSE_MAX
PASSWORD_REUSE_TIME:指定了口令在多少天内不能重用,单位为1天。
PASSWORD_REUSE_MAX:指定了当前口令在被重用之前需要更改几次。
这里的重用是针对每个账户的自己的口令,如果不设置这两个参数,设置账户新口令的时候就可以使用当前的口令,那么口令过期就不存在意义了。
这里光看说明是看是不太容易理解的,试一试就明白了:
在PASSWORD_REUSE_TIME、PASSWORD_REUSE_MAX均为默认值UNLIMITED的时候,修改口令的时候可以随意重用,用当前的口令当新口令也都可以。
这里会突然想到verify_function_11G,如果新口令和旧口令一样的话,应该通不过校验的才对。
经过实验,不知道是为什么old_password是空字符串,压根就没有传值进来,所以实际上直接跳过了旧口令和新口令的对比校验……
当PASSWORD_REUSE_TIME、PASSWORD_REUSE_MAX均非默认值UNLIMITED的时候,则需要两个条件都满足才可以更改口令。
如PASSWORD_REUSE_TIME从UNLIMITED变成1,以及PASSWORD_REUSE_MAX从UNLIMITED变成1开始,oracle开始记录使用过的口令。
如某账户,这个时候依次设置新口令a、b、c、d、e,此时的口令是e,在1天内,该账户设置新口令的话,使用a、b、c、d、e中的任何一个都不可以。
超过1天后,a、b、c、d均可作为新口令,但是e不可以,因为PASSWORD_REUSE_MAX的值是1,相当于从e往前数,有1个口令不能被当做新口令,那就是e自己了。如果PASSWORD_REUSE_MAX的值是2,那么e、d均不可以作为新口令。
而如果PASSWORD_REUSE_TIME为unlimited、PASSWORD_REUSE_MAX为某个数值,或者PASSWORD_REUSE_TIME为某个数值,PASSWORD_REUSE_MAX为unlimited的时候,账户使用的口令都不能当做新口令进行设置。
所以为了满足定期更换的要求,PASSWORD_REUSE_TIME、PASSWORD_REUSE_MAX都应该设置为非unlimited的值。
最后,不知道为什么,SYS账户也不受到这两个参数的限制,可以随意重用口令。
*本文作者:起于凡而非于凡,转载请注明来自FreeBuf.COM
精彩推荐