Oracle 数据库安全改密实践:SYS、SYSTEM 与普通用户的区别及 Shell 脚本
作者|皇甫晓飞(xiaofei DBA)Oracle ACE · Oracle OCM · 资深数据库工程师
Oracle 数据库安全改密实践:SYS、SYSTEM 与普通用户的区别及 Shell 脚本

在数据库日常运维中,安全管理对于密码轮换常有这样的要求:DBA 负责准备和执行脚本,DBA不能知道密码,安全员手工输入新密码,密码不能以明文出现在终端,也不能写入脚本或命令行参数。如果不知道 SYS 的旧密码,该如何处理?普通用户是否可以采用相同方法?
本文结合一次 Oracle 19c 的实际操作,说明不同用户的改密方式,并提供适用于 Linux Bash 的隐藏输入脚本。Oracle 11g和19c NON-CDB经过测试均可以,19c 多租户环境还需要区分 CDB 根容器和 PDB。
一、先分清“修改自己的密码”和“管理员重置密码”
是否需要旧密码,不能只看用户名,还要看执行身份和使用的命令。
| 场景 | 建议方法 | 旧密码要求 | 主要权限或条件 |
|---|---|---|---|
| 普通用户修改自己的密码 | SQL*Plus PASSWORD | 通常提示旧密码 | 能以该用户连接数据库 |
| 管理员重置普通用户密码 | PASSWORD 用户名,或隐藏输入脚本执行 ALTER USER | 管理员重置其他用户时无需知道旧密码 | 对目标用户具有适用的 ALTER USER 权限 |
| 管理员重置 SYSTEM 密码 | 与普通用户相同的改密语句 | 无需知道 SYSTEM 旧密码 | 管理员连接及相应权限 |
| 重置 SYS 密码 | 本地 OS 认证 SYSDBA 连接,隐藏输入后执行 ALTER USER | 无需知道 SYS 旧密码 | OSDBA 权限,以及可更新的密码文件等条件 |
SYSTEM 虽然是高权限管理账号,但修改它的密码不等同于修改 SYS 的密码。SYS 涉及 SYSDBA 管理认证和密码文件;不能把普通的 ALTER USER 权限理解为可以任意重置 SYS。19c 文档明确指出,修改 SYS 密码还需要 SYSDBA 身份和密码文件。参见 Oracle 19c ALTER USER。
二、方法一:使用 SQL*Plus PASSWORD 命令
1. 普通用户修改自己的密码
登录时不要在命令行写密码:
sqlplus HFXF@业务服务名
按隐藏提示输入当前登录密码。连接成功后执行:
PASSWORD
SQL*Plus 会要求输入旧密码、新密码及确认密码。实际输入不会回显。这里是“已知旧密码,修改自己的密码”的常规路径。
2. 管理员重置其他用户密码
例如,在具备权限的管理员会话中执行:
PASSWORD HFXF
管理员重置其他用户时,一般会直接提示新密码和确认密码,不需要该用户的旧密码。
Oracle 11g 的 SQL*Plus 文档分别列出了修改自身密码和管理员修改其他用户密码的交互示例,并明确说明密码不会回显。参见 Oracle 11g PASSWORD 命令。
3. 为什么 PASSWORD SYS 仍然提示 Old password?
本次操作中,在 SYS 会话内执行了:
SYS@cdb19c(CDB$ROOT)> password sys
Changing password for sys
Old password:
这个实际输出说明:不能承诺“只要以 SYSDBA 登录,PASSWORD SYS 就绝不会提示旧密码”。当前用户正是 SYS,SQL*Plus 可能进入修改自身密码的交互路径。
遇到这种情况,可按 Ctrl+C 取消。如果目标是在不知道旧密码的情况下重置 SYS,使用本文的本地 SYSDBA 隐藏输入脚本。不要把“提示旧密码”等同于“管理员没有重置权限”。
三、方法二:管理员执行 ALTER USER 重置密码
基本语法如下。尖括号内只是语法占位符,不是真实密码,也不是可直接执行的完整 SQL:
ALTER USER SYS IDENTIFIED BY "<新密码>";
ALTER USER SYSTEM IDENTIFIED BY "<新密码>";
ALTER USER HFXF IDENTIFIED BY "<新密码>";
管理员重置时不需要拼接 REPLACE 旧密码。但是,直接在终端输入包含真实密码的 SQL,会使密码出现在屏幕和可能的终端记录中,因此不符合本文的使用要求。
解决办法是:Shell 隐藏读取密码,再通过标准输入管道把 SQL 交给 SQL*Plus。脚本中只有变量,真实密码不作为命令行参数传入。
本文限定数据库服务器本地 / as sysdba 连接,不把脚本直接改造成远程明文 SQL 传输工具。Oracle 文档指出,远程使用 ALTER USER 改密时,应确保连接加密;PASSWORD 命令自身具有不同的密码传输处理方式。参见 Oracle 用户密码管理说明。
四、11g 与 19c:先确认用户属于哪个容器
Oracle 11g 没有 CDB/PDB,确认 ORACLE_SID 对应目标实例即可。
在 19c CDB 中,需要进一步区分:
| 用户类型 | 示例 | 改密位置 |
|---|---|---|
| Oracle 提供的 CDB 公共用户 | SYS、SYSTEM | CDB$ROOT |
| 自建 CDB 公共用户 | C##HFXFADM | CDB$ROOT |
| PDB 本地用户 | APPUSER、HFXF | 用户实际所属的 PDB |
SYS 和 SYSTEM 都是 Oracle 提供的 CDB 公共用户,不能因为 SYSTEM 的改密 SQL 与普通用户相似,就把它当作 PDB 本地用户。参见 Oracle 19c 公共用户说明。
在 19c 管理员会话中,可先确认位置:
SHOW CON_NAME
SELECT instance_name, host_name FROM v$instance;
SELECT username, common, account_status
FROM dba_users
WHERE username IN ('SYS', 'SYSTEM', 'HFXF');
DBA_USERS 查询反映当前容器中的用户。不能仅因为根容器查不到 HFXF,就认定所有 PDB 都不存在该用户。切换到实际业务 PDB 后再检查:
ALTER SESSION SET CONTAINER = APPPDB;
SHOW CON_NAME
SELECT username, account_status
FROM dba_users
WHERE username = 'HFXF';
以上容器命令与 COMMON 列查询仅用于多租户版本,不用于 11g。
五、完整 Shell 脚本
文件名:change_sys_password.sh。虽然保留了最初的文件名,现在已经支持 SYS、SYSTEM 和其他用户。
运行条件:Linux、Bash 4 或更高版本、已配置的 SQL*Plus,以及具备本地 OSDBA 权限的操作系统账号。不能仅凭操作系统账号名叫 oracle 就认定它一定具有 OSDBA 权限。
脚本通过 Bash read -s 隐藏输入,不依赖 Python。支持普通非引号用户名,自动转成大写;不支持必须保留大小写的双引号用户名。密码仅支持可打印 ASCII 字符且不含双引号;长度和复杂度由目标数据库继续检查。
#!/usr/bin/env bash
# Run with bash on the database server as an OSDBA user.
# Set ORACLE_HOME, PATH and ORACLE_SID for the intended database first.
# Usage: bash change_sys_password.sh [USERNAME] [PDB_NAME]
# Ordinary unquoted Oracle usernames only; names are converted to uppercase.
set +x
set +v
set -o pipefail
export LC_ALL=C
if [[ $# -gt 2 ]]; then
printf '用法: bash change_sys_password.sh [用户名] [PDB名称]\n' >&2
exit 1
fi
target_user=${1:-SYS}
if [[ ! $target_user =~ ^[A-Za-z][A-Za-z0-9_\$#]{0,127}$ ]]; then
printf '用户名格式无效,仅支持普通非引号用户名。\n' >&2
exit 1
fi
target_user=${target_user^^}
pdb_name=${2:-}
if [[ -n $pdb_name ]]; then
if [[ ! $pdb_name =~ ^[A-Za-z][A-Za-z0-9_\$#]{0,127}$ ]]; then
printf 'PDB 名称格式无效。\n' >&2
exit 1
fi
pdb_name=${pdb_name^^}
if [[ $target_user == SYS || $target_user == SYSTEM ]]; then
printf 'SYS 和 SYSTEM 应在 CDB 根容器修改,请勿指定 PDB 参数。\n' >&2
exit 1
fi
fi
# Make the default local connection target the root in a CDB.
unset ORACLE_PDB_SID
new_password=''
confirm_password=''
sql_output=''
trap 'unset new_password confirm_password sql_output' EXIT
trap 'printf "\nCancelled.\n" >&2; exit 1' INT TERM HUP
if ! command -v sqlplus >/dev/null 2>&1; then
printf 'sqlplus not found. Set ORACLE_HOME and PATH.\n' >&2
exit 1
fi
if [[ -z ${ORACLE_SID:-} ]]; then
printf 'Set ORACLE_SID to the intended instance first.\n' >&2
exit 1
fi
if [[ ! -t 0 ]]; then
printf 'Run directly in an interactive terminal; do not pipe input.\n' >&2
exit 1
fi
printf '目标本机实例 ORACLE_SID=%s\n' "$ORACLE_SID"
printf '目标数据库用户: %s\n' "$target_user"
printf '目标容器: %s\n' "${pdb_name:-默认容器(CDB 根容器或非 CDB 数据库)}"
IFS= read -r -s -p "安全员请输入 ${target_user} 新密码: " new_password
read_status=$?
printf '\n'
[[ $read_status -eq 0 ]] || exit 1
IFS= read -r -s -p '再次确认新密码: ' confirm_password
read_status=$?
printf '\n'
[[ $read_status -eq 0 ]] || exit 1
if [[ -z $new_password || $new_password != "$confirm_password" ]]; then
printf '密码为空或两次输入不一致,已退出。\n' >&2
exit 1
fi
# Quoted Oracle passwords support punctuation, but not embedded double quotes.
# Reject control/non-ASCII bytes to keep each generated SQL command on one line.
if [[ $new_password == *\"* || $new_password == *[^[:print:]]* ]]; then
printf '密码仅支持可打印 ASCII 字符,不能包含双引号,已退出。\n' >&2
exit 1
fi
unset confirm_password
# printf is a Bash builtin: the password is not passed in process arguments.
# Capture all SQL*Plus output, including errors which might echo the SQL text.
sql_output=$(
{
builtin printf '%s\n' \
'WHENEVER OSERROR EXIT FAILURE' \
'WHENEVER SQLERROR EXIT FAILURE' \
'SET ECHO OFF' \
'SET VERIFY OFF' \
'SET DEFINE OFF' \
'SPOOL OFF'
if [[ -n $pdb_name ]]; then
builtin printf 'ALTER SESSION SET CONTAINER = "%s";\n' "$pdb_name"
fi
builtin printf 'ALTER USER "%s" IDENTIFIED BY "%s";\n' "$target_user" "$new_password"
builtin printf '%s\n' 'PROMPT USER_RESET_COMPLETED' 'EXIT SUCCESS'
} | sqlplus -L -S / as sysdba 2>&1
)
sql_status=$?
unset new_password
if [[ $sql_status -eq 0 && $sql_output == *USER_RESET_COMPLETED* ]]; then
printf '%s 密码修改成功。\n' "$target_user"
else
printf '%s 密码修改失败,为保护密码已隐藏 SQL*Plus 原始输出。\n' "$target_user" >&2
exit 1
fi
为什么需要这些处理?
IFS= read -r -s:隐藏输入,并保留空格、反斜杠等字符的原始含义。- 两次输入校验:避免输入错误,并拒绝空密码。
- 用户名和 PDB 名称校验:只接受限定格式的标识符,避免把任意文本拼入 SQL。
SET DEFINE OFF:避免密码中的&被 SQL*Plus 解释成替换变量。- Bash 内置
printf:将密码写入管道,不交给外部命令作为进程参数。 - 捕获标准输出和标准错误:不直接打印可能包含完整改密 SQL 的错误信息。
WHENEVER SQLERROR/OSERROR、pipefail和完成标记:综合判断执行结果,避免仅凭管道启动成功就报告改密成功。set +x、set +v:关闭脚本内部的调试输出;不要使用bash -x、bash -v或外层调试包装执行它。unset和退出清理:缩短变量保留时间;这不是对进程内存进行可证明的安全擦除。
SET ECHO OFF 只负责 SQL*Plus 命令回显,不能单独保证报错不泄露密码;sqlplus -s 也不等于“所有输出都安全”。因此脚本不显示改密阶段的原始输出。为兼容 11g,不依赖 SET HISTORY 0。
脚本只改变密码,不附带 ACCOUNT UNLOCK、授权或修改 Profile 等操作。密码重置和账号解锁是不同的管理动作。
六、执行示例
1. 准备数据库环境
使用数据库服务器上具有 OSDBA 权限的账号,加载正确的 Oracle 环境。以下 SID 来自本次操作,其他环境应替换:
export ORACLE_SID=cdb19c
command -v sqlplus
ORACLE_HOME 和 PATH 应指向该实例对应的 Oracle 安装。脚本使用 UTF-8、Linux LF 换行。运行时使用 bash,不要使用 sh。
2. 修改 SYS
bash change_sys_password.sh SYS
由安全员输入两次新密码。终端不显示字符或星号,属于正常现象。
本次现场反馈的输出为:
目标本机实例 ORACLE_SID=cdb19c
目标数据库用户: SYS
安全员请输入 SYS 新密码:
再次确认新密码:
SYS 密码修改成功。
这份反馈来自此前的 SYS 版本脚本,说明当时的改密流程返回成功。本文追加 PDB 参数后的版本增加了容器提示,不能把此前的执行结果当作所有新分支都已验证。
3. 修改 SYSTEM
bash change_sys_password.sh SYSTEM
在 CDB 中,默认在根容器修改。脚本不允许给 SYS、SYSTEM 同时指定 PDB 参数。
4. 修改 11g 或非 CDB 数据库中的普通用户
bash change_sys_password.sh HFXF
5. 修改 19c PDB 中的本地用户
假设 HFXF 属于 APPPDB:
bash change_sys_password.sh HFXF APPPDB
第二个参数是 PDB 名称,不是网络服务名。脚本先连接本机实例,再执行 ALTER SESSION SET CONTAINER,最后重置该 PDB 内的用户密码。PDB 必须存在,并处于允许该操作的状态。
6. 修改 CDB 公共用户
用户名包含 $ 等 Shell 特殊字符时,应使用单引号包围整个参数;统一使用单引号也便于复制:
bash change_sys_password.sh 'C##HFXFADM'
本脚本通过清除子进程继承的 ORACLE_PDB_SID,使默认本地连接使用根容器;指定第二个参数时才显式切换 PDB。公共用户的密码应在对应根容器中修改,普通的 CDB 公共用户不要传 PDB 参数。应用公共用户、应用根容器等进阶场景不在本脚本示例范围内。
七、如何验证密码已经生效
脚本输出“修改成功”表示 SQL*Plus 改密流程返回成功,仍应通过一次真正的密码认证验证目标登录路径。
SYS:通过网络服务验证密码文件认证
sqlplus /nolog
SQL> CONNECT SYS@目标数据库服务名 AS SYSDBA
Enter password:
在隐藏提示处输入新密码。这里不要把密码直接写进连接串。
不能通过再次执行 sqlplus / as sysdba 来证明新密码正确,因为该方式使用操作系统认证,绕过了密码认证。
SYSTEM 或普通用户
sqlplus /nolog
SQL> CONNECT SYSTEM@目标数据库服务名
Enter password:
对于 PDB 本地用户,必须使用该 PDB 对应的网络服务:
SQL> CONNECT HFXF@目标PDB服务名
Enter password:
验证成功后,检查依赖该账号的应用连接池、监控、备份任务等是否需要同步更新。修改密码本身不会替这些系统更新凭据。
八、常见问题与处理边界
| 现象 | 检查方向 |
|---|---|
PASSWORD SYS 提示旧密码 | 当前用户自身改密路径;未知旧密码时取消,使用管理员重置流程 |
本地 / as sysdba 登录失败 | OSDBA 组、Oracle 环境、SID、实例状态;不要在脚本中补写明文 SYS 密码 |
| 普通用户不存在或容器范围报错 | 核对用户所属 PDB,检查是否误在根容器操作 |
| SYS 改密失败 | 核对密码文件及 REMOTE_LOGIN_PASSWORDFILE 配置、权限和密码要求 |
| 新密码被拒绝 | 检查数据库返回的策略类别、长度、复杂度、历史限制;不要把真实密码贴入排障记录 |
| 改密后普通用户仍不能登录 | 另行检查账号锁定、过期状态及连接服务;脚本没有自动解锁 |
| 脚本提示成功但远程 SYS 登录失败 | 区分 OS 认证与密码认证,核对实际服务、节点、密码文件及新密码输入 |
当脚本隐藏错误输出时,不要通过直接 echo "$sql_output" 恢复排障信息,因为其中可能含有密码。可以先执行不涉及新密码的连接、容器、账号状态及密码文件配置检查。
SYS 的管理认证依赖密码文件。19c 文档说明,密码文件保存管理用户的认证信息,并具有特定的格式和密码管理约束。RAC、Data Guard 等部署应结合本环境的密码文件管理与同步机制检查各登录路径,不应因单实例脚本返回成功,就认定所有节点和备库均已验证。参见 Oracle 19c 管理用户密码说明。
本文脚本解决的是终端不回显、脚本不固化明文密码、命令行参数不携带密码,以及抑制可能泄露密码的原始输出。密码仍会短暂存在于进程内存、管道和数据库执行链路中;该方案不承诺绕过主机高权限监控、数据库审计或第三方采集。生产环境应沿用已有审计和凭据管理规范,而不是为隐藏密码关闭审计。
关于作者
皇甫晓飞(xiaofei DBA)
Oracle ACE|Oracle 19c/12c/11g OCM|资深数据库工程师|退役军人
拥有18年以上IT技术从业经验,长期从事Oracle数据库运维、数据库架构设计、性能优化及数据库技术管理工作,持续关注国产数据库、信创数据库迁移和数据库智能运维等技术方向。
微信公众号:xfhuangfu
MyBologs:
https://www.xiaofeihuangfu.com
CSDN:https://blog.csdn.net/xfhuangfu
ITPUB:https://blog.itpub.net/28373936/
专注Oracle数据库、数据库架构、性能优化及运维技术分享。
欢迎关注,一起交流数据库技术。