最核心的解决思路是让密码不经过shell命令行参数或环境变量,而是由expect进程通过内存读取并注入交互流程;推荐用bash的stty -echo + read -s读取密码后,以here-document方式传入expect脚本,用gets stdin password接收,杜绝命令行传参、日志记录、调试输出及密码拼接等泄露风险。

最核心的解决思路是:让密码不经过Shell命令行参数或环境变量,而是由Expect进程自身通过内存方式读取并注入交互流程。Shell调用Expect脚本时,绝不把密码作为参数传入,也不在脚本中硬编码或echo输出。
使用 stty -echo 临时关闭终端回显,安全读取密码
在Expect脚本外部(如Bash包装脚本中),用stty -echo禁用当前终端的输入回显,再通过read -s读取密码到变量,最后将该变量以安全方式传给Expect——但注意:不能用命令行参数传递!推荐用标准输入(stdin)或临时命名管道(fifo)。
- ✅ 推荐做法:Bash中读取密码后,用
expect script.exp 将密码通过here-document传入;Expect脚本用<code>gets stdin password接收 - ❌ 避免:
expect script.exp "$PASSWORD"—— 密码会出现在ps输出和Shell历史中 - ⚠️ 注意:
stty -echo后务必在脚本退出前执行stty echo恢复,否则终端会“失声”
Expect脚本内不记录、不打印、不导出密码变量
Expect中定义的密码变量(如set pass $argv)仅存在于Tcl解释器内存中,只要不主动send、puts或写入文件,就不会泄露。关键是要杜绝以下行为:
- 不使用
log_file记录包含密码的交互过程(或启用前先log_user 0关闭用户输出) - 不调用
exp_debug 1开启调试日志(会把所有send/expect内容打到stderr) - 不在
send前用puts $pass做调试输出 - 避免在
spawn命令中把密码拼进命令字符串(如spawn ssh user@host "echo $pass")
替代方案:用SSH密钥+ssh-agent,彻底绕过密码交互
对SSH类场景,这是更安全、更主流的做法。Expect不再需要处理密码,只需触发已认证的连接:
- 提前用
ssh-keygen生成密钥,ssh-copy-id部署公钥 - 启动
ssh-agent并ssh-add加载私钥(可配合keychain持久化) - Expect脚本直接
spawn ssh user@host command,无需expect/send密码逻辑 - 若必须用密码(如某些设备不支持密钥),再回归Expect + stdin传参方案
增强防护:限制Expect进程可见性与生命周期
进一步降低风险,可从系统层面收紧:
- 运行Expect脚本时加
setsid使其脱离当前终端会话,避免被ps --forest轻易关联 - 敏感脚本设权限为
700,确保只有属主可读写执行 - 避免在脚本中用
system或exec调用Shell命令处理密码相关内容 - 必要时用
memlock资源限制(ulimit -l)防止密码页被swap到磁盘(需root配置)











