摘要

本文介绍了一种用来代替第三方工具进行SSH连接的安全方式,虽然没有第三方工具功能那么齐全,但是基本能够覆盖大部分使用场景,并探讨关于服务器上下载上传文件的安全问题。
该方法支持Mac,Linux,win10(有WSL)系统。

背景

SSH是大家维护服务器常用的功能,但是功能有些简陋,特别是对于跳板机来说不是很方便,所以很多人会选择第三方工具。
第三方SSH工具功能确实强大,但是不免费,或者面向家庭/学校用户免费,这就可能导致随时可能丢失License,而去网上找破解版,盗版很有可能导致后门事件,参考putty后门事件
甚至即使是正版,由于闭源的原因,也无法保证不会突然出现后门。
所以有办法能够自己掌控整个链接过程而不用担心被植入奇怪的东西,又能方便的进行链接的工具那就是最好的了。
而expect正是这样一款能够一定程度上符合要求的程序。

expect简介

Expect原本是在Unix系统中进行自动化测试的工具,所以利用自动化的方式,能够帮我们快速实现SSH链接等功能。
我们在使用第三方工具进行SSH时,其实主要用了这么几种功能

  • 快速连接服务器
  • 帮助我们完成密码填写
  • 等待提示并输入葱锋号
  • 上传下载

基本使用方式已经有人提供了,可以看这篇WIKI,我接下来提供一些更加安全的使用方式。

win10使用者默认已经安装WSL相关功能,以下操作在WSL下完成

需要输入葱锋号

由于葱锋号的一直在变化,必然没有办法提前输入,但是这样也有一个好处,那就是不用担心别人看到,同一个动态口令过时就时效了,下次什么时候能用完全猜不到。
所以我们可以简单的把这个做成运行脚本时的一个参数即可,示例如下:

1
2
3
4
5
6
7
8
# 前面的expect脚本

# 把第一个参数作为密钥,如果使用了本页上级文章中的指定机器的话,那此处可能会需要把0换成1,根据自己实际参数位置调整
set token [lindex $argv 0]

# 后面的expect脚本,可以使用$token获取
# expect "token):"
# send "$token\r"

解决明文密码的问题

因为expect中无法使用加密后的密码,所以密码是以明文保存在脚本。
如果把密码以参数的形式传入到命令行中,又会被记录在历史记录中导致密码泄露。
那最好的办法,就是像Linux里面使用sudo命令时,提示输入密码,这个在expect中是可以实现的。方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
# 前面的expect脚本

# 提示输入密码,建议放在脚本一开始
stty -echo
send_user -- "请输入密码:"
expect_user -re "(.*)\n"
send_user "\n"
stty echo
set pass $expect_out(1,string)

# 后面的expect脚本中,可以使用$pass获取输入的密码
# 如,send "$pass\r"

注意上面的代码中,不要随意多加空格
当运行到这一段时,就会发现提示你输入密码了,效果就和sudo时一样

上传和下载

上传和下载对于安全的服务器管理来说,在应用服务器上应该是不允许的,应该有一个专门的中转服务器用于将线上服务器的指定数据拉取下来或者上传上去,当开发提出申请时,经过审核后由指定程序去完成,之后开发在中转服务器上获取数据或者上传数据。

当有了这个服务器之后,我们可以使用nc命令开启TCP通道进行文件传输,或者使用SSH的port forwarding和reverse tunnel就可以进行文件传输了,而这些命令都可以很方便的加入expect脚本中。

一个应该替换一些数据就能直接使用的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/expect
set timeout -1
# 把第一个参数作为服务器名称
set server [lindex $argv 0]
# 把第二个参数作为冲锋号密钥
set token [lindex $argv 1]


# 设置服务器名称和IP映射关系
set iplist [list server1 ip1 server2 ip2 server3 ip3]
array set ipmap $iplist
set targetip $ipmap($server)

# 设置服务器名称和常用账号的映射
set accountlist [list server1 user1 server2 user2 server3 user3]
array set accountmap $accountlist
set targetaccount $accountmap($server)

# 输入密码
stty -echo
send_user -- "password:"
expect_user -re "(.*)\n"
send_user "\n"
stty echo
set pass $expect_out(1,string)

# SSH到堡垒机
spawn ssh <username>@<jump-server> -p 2222

# 使用上面输入的密码
expect "password:"
send "$pass\r"

# 使用命令行输入的冲锋号
expect "token):"
send "$token\r"

# 指定服务器ip
expect "Opt>"
send "$targetip\r"

# 指定用户
expect "ID>"
send "$targetaccount\r"

# 输入++++会直接退出SSH登陆并结束expect脚本
interact ++++ return
send "exit\r"
expect "ID>"
send "exit\r"

expect eof

win10用户的不用进入到WSL的使用方式

win10用户其实不用进入到WSL即可直接调用WSL下的脚本,并且在WSL下运行,下面就展示这种方法:

本例子在PowerShell7下测试

  1. 在自己的WSL中写好上面的脚本,并测试完成,记下文件位置
  2. 打开powershell,输入code $PROFILE,用VSCODE打开powershell的配置
  3. 插入这段代码并保存
    1
    2
    Function SshToBLJ($server, $token) {wsl <expectShellPath> $server $token}
    Set-Alias -Name sshto -Value SshToBLJ
  1. 重新打开一个powershell终端,让上面的修改生效,输入sshto server1 <动态密钥>就可以和在Linux内一样使用了