粒粒珍珠白玉光
釜中蒸煮饭飘香
三餐若少此君伴
腹内空空意未央
软硬适中口感好
搭配菜肴最是强
碳水精华凝一碗
人间至味是家常
本文介绍使用SSH登录Windows的方法,分为两种,一种是SSH到WSL2;另一种是到Windows。
本次使用的环境包括:
至少一台Windows计算机,运行2022 datacenter, Win10和Win11应该也行,不过我没试过——没有。
一台运行SSH客户端的计算机。
⚠️ 不合适的安全配置可能会使您的信息泄露。继续阅读代表您认可您对自己的行为负有全部责任。⚠️
C WSL2
到WSL2就不多说了,约等于一台Linux虚拟机,只不过藏在Windows后面。
安装
先打开一个管理员 权限的PowerShell会话:
安装基本的支持组件
看看有哪些发行版(更多发行版可以自己去别的地方找)
安装一个发行版,默认是乌班图。可以自己加-d
指定:
1
wsl - -install # -d Debian
等一会就装好了,进去给它安装SSH服务:
1
2
3
sudo apt update
sudo apt install openssh-server
sudo nano /etc/ssh/sshd_config.d/99-my-sshd.conf
配置
以上配置文件有一点点安全加固,主要是不许密码验证,不许root登录:
1
2
3
4
5
6
Port 23456 # 自己挑个端口
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no
PermitRootLogin no
#PermitRootLogin prohibit-password
改好保存然后启动服务,这时不能使用systemctl
记下它的IP地址:
网络
回到那个管理员 权限的PowerShell会话。
加入一个端口映射,令Windows将来自外部的TCP23456映射为WSL2的TCP23456:
1
netsh interface portproxy add v4tov4 listenaddress = 0.0 . 0 . 0 listenport = 23456 connectaddress =< WSL2 IP地址 > connectport = 23456
建好之后可以看看:
1
netsh interface portproxy show v4tov4
以后不想要了可以删掉:
1
netsh int portproxy reset all
防火墙
还得创建Windows防火墙规则,这步在管理员 权限的PowerShell会话完成:
1
netsh advfirewall firewall add rule name = ” Allow TCP 23456 for WSL2 ” dir = in action = allow protocol = TCP localport = 23456
建好之后可以看看:
1
netsh advfirewall firewall show rule name = all dir = in type = dynamic | select-string WSL2
不想要了可以删掉:
1
netsh advfirewall firewall delete rule name = "Open Port 23456 for WSL2"
systemd
这步可选,主要是方便以后搞服务。
先写一个配置文件,就两行。
1
2
echo '[boot]
systemd=true' | sudo tee /etc/wsl.conf
然后回到管理员 的PowerShell重启这个发行版:
重启之后就可以用systemctl
了
1
systemctl list-unit-files --type= service
SSH公钥
为了照顾小白,还是说下密钥是怎么来的。 在自己常用的计算机上面生成
1
ssh-keygen -t ed25519 -C "备注"
生成的pub文件放在SSH服务器上。 另一个当成密码,别给任何人。
至于复制到SSH服务器上你们有别的方法就用哈,没有的话就像我这样土法制作。这步在WSL2的窗口完成:
1
2
mkdir ~/.ssh
nano ~/.ssh/authorized_keys
以上把公钥粘贴进去,每行一个。
1
chmod 600 ~/.ssh/authorized_keys
好了,可以用SSH客户端登录了。
一个~/.ssh/config
的例子:
1
2
3
4
5
6
Host foo-deb # 昵称
ForwardAgent yes
HostName foo.bar.com # 有FQDN就填,没有就写IP地址
User <username> # WSL2里的用户名
Port 23456 # 之前选的端口
RequestTTY yes
在常用的计算机上用 ssh foo-deb
即可登录。
玩腻了可以:
D 正片开始
以上是水字数的,其实是SSH到Linux,接下来才是SSH到Windows。
先开个管理员 权限的PowerShell会话。
安装个SSH服务:
1
Add-WindowsCapability -Online -Name OpenSSH . Server ~~~~ 0.0 . 1 . 0
这个安装好之后名字就叫“sshd”,如图:
设为开机启动(管理员 权限的PowerShell会话):
1
Set-Service -Name sshd -StartupType 'Automatic'
给它新建防火墙规则(管理员 权限的PowerShell会话):
1
2
3
4
5
6
if (!( Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name , Enabled )) {
Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
} else {
Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."
}
编辑$env:ProgramData\ssh\sshd_config
这个文件,找到下面这两行,把它们注释掉,去掉对于管理员组成员的设置,它这里检测到是管理员组成员就去第2行的地方找key:
1
2
Match Group administrators
AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
用脚本的话是:
1
2
3
4
5
6
7
8
9
10
11
$filePath = "C:\ProgramData\ssh\sshd_config"
$content = Get-Content $filePath
$newContent = @ ()
foreach ( $line in $content ) {
if ( $line -notmatch "Match Group administrators.*" -and $line -notmatch "AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys" ) {
$newContent += $line
} else {
$newContent += "#" + $line
}
}
$newContent | Set-Content $filePath
启动!(管理员 权限的PowerShell会话)
SSH key当然是首选的验证方式,请准备好公钥。文件的位置是$env:USERPROFILE\.ssh\authorized_keys
。需要手动将权限设置为只有自己能访问(阻止继承)。资源管理器的图形界面不多说了。要想使用脚本的话把下列内容存为importKey.ps1
文件,在一个非管理员 PowerShell会话运行之(importKey.ps1
),粘贴公钥到剪贴板,每行一个。
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
Write-Warning "请先将公钥(pub)复制到剪贴板,然后按任意键继续。" -WarningAction Inquire
# 拿到当前用户名,格式为domain\username
$user = [ System.Security.Principal.WindowsIdentity ]:: GetCurrent (). Name
# 读取剪贴板内容
$clipboardContent = Get-Clipboard
# 准备写入的文件路径
$filePath = " $env:USERPROFILE \.ssh\authorized_keys"
$sshFolderPath = " $env:USERPROFILE \.ssh"
if ( -not ( Test-Path $sshFolderPath )) {
New-Item -Path $sshFolderPath -ItemType Directory
}
Write-Host "剪贴板内容如下:"
Write-Host $clipboardContent
Write-Host "确认要将此内容写入 $filePath 吗?(Y/N)"
$confirmation = Read-Host
if ( $confirmation -eq 'Y' ) {
# 写入公钥,阻止文件权限继承,为自己授权
Set-Content -Path $filePath -Value $clipboardContent
# 确保 $user 正确处理,没有额外的引号
$escapedUser = $user . Replace ( "\" , "\\" ) # 替换斜杠
# 使用 ${} 来处理变量引用
icacls . exe $filePath / inheritance : r / grant " ${escapedUser} :(F)" / grant "SYSTEM:(F)"
Write-Host "内容已写入文件。"
} else {
Write-Host "操作取消。"
}
这样就可以用了。 ssh <FQDN | IP>
即可。不过默认的shell是cmd
,要想改成SSH进来默认是PowerShell的话那就得改下注册表(管理员 权限的PowerShell会话):
1
2
3
4
5
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" `
-Name DefaultShell `
-Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" `
-PropertyType String `
-Force
改好之后再SSH进去默认就是PowerShell了。
㊟ 有的终端仿真App会“偷偷”加些料在SSH会话里面,实现git状态、语法高亮、自动补全、纠正等等功能。 但它既不认识cmd也不认识PowerShell,于是无法登录。各位要是使用这种App就得注意一下。图为我用的warp终端 。使用这种终端时我加入了如图的设置,并在欲登录到Windows时使用command ssh
命令代替ssh
命令。
E PowerShell over SSH
以上通过SSH到Windows然后运行PowerShell很好玩,但是它有个问题,就是用法必须是交互式的。还有一种玩法,能把PowerShell会话包装在SSH里面,这样我们就可以编写能循环运行的脚本。
想象下,在一台Windows计算机通过PowerShell远程处理另一台Windows计算机的时候,它使用的是WinRM, 身份验证则是Kerberos或者NTLM。
比如有人想获取一堆Windows计算机的某服务的状态,我们的循环脚本可以这样:
1
Get-Service $servicename -ComputerName $server
但要是有人想从macOS/Linux做同样的事情呢?多么蛋疼 ;又或者就是从Windows到Windows但是网络团队把TCP5985/5986端口封了您恰好发现他们忘记封TCP22,答案也是可以的。 不过Windows自带的Powershell不行。没这个能力知道吗
1
2
3
4
5
6
7
8
( Get-Command New-PSSession ). ParameterSets . Name
---
ComputerName
Uri
VMId
VMName
Session
ContainerId
Windows端得装新的,官方链接点开就装 。 同样,客户端也得装,比如macOS:
1
brew install powershell/tap/powershell-lts
或者乌班图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sudo apt update
sudo apt install -y wget apt-transport-https software-properties-common
# 乌班图版本
source /etc/os-release
# 下载微软仓库的key
wget -q https://packages.microsoft.com/config/ubuntu/$VERSION_ID /packages-microsoft-prod.deb
# 注册微软仓库的key
sudo dpkg -i packages-microsoft-prod.deb
# 文件清理
rm packages-microsoft-prod.deb
# 更新仓库列表
sudo apt update
# 正活
sudo apt install -y powershell
# 启动!
pwsh
目前版本是7,装完了是这样的(下面倒数两行带来的能力):
1
2
3
4
5
6
7
8
9
10
PS C: \ Program Files \ PowerShell \ 7 > ( Get-Command New-PSSession ). ParameterSets . Name
ComputerName
Uri
VMId
VMName
Session
ContainerId
UseWindowsPowerShellParameterSet
SSHHost
SSHHostHashParam
默认Shell也改成新的(可选):
1
2
3
4
5
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" `
-Name DefaultShell `
-Value "C:\Program Files\PowerShell\7\pwsh.exe" `
-PropertyType String `
-Force
还是编辑$env:ProgramData\ssh\sshd_config
这个文件,把它本来的Subsystem
的行注释掉。换成这样的:
1
Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs -nologo
以上不支持文件路径有空格。所以得用老DOS的风格,您要是不确定您的老DOS风格路径是什么,可以这样找:
1
2
3
4
5
6
7
# 找到位置
$path = where . exe pwsh . exe
"Long path: $path "
# 转为8.3风格
$shortPath = ( New-Object -ComObject Scripting . FileSystemObject ). GetFile ( $path ). ShortPath
"Short path: $shortPath "
改好了之后重启服务:
然后就可以试试了,比如交互式的:
1
2
Enter-PSSession -HostName < FQDN | IP >
[< FQDN | IP >] : PS C: \ Users \ username \ Documents > Exit-PSSession
也可以尝试批量的:
1
2
$session = New-PSSession -HostName $server
Invoke-Command $session -ScriptBlock { Get-service sshd | Stop-Service }
完整版,想好了再运行哦😿:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$serversFile = "list.txt"
$servers = Get-Content $serversFile
foreach ( $server in $servers ) {
try {
$session = New-PSSession -HostName $server
Invoke-Command $session -ScriptBlock { Get-service sshd | Stop-Service }
Write-Host "Stopped sshd service on $server successfully."
} catch {
Write-Host "Error occurred while processing $server : $_ "
} finally {
if ( $session ) {
Exit-PSSession $session
}
}
}
㊟ 通过SSH远程执行PowerShell是不带$PROFILE
(配置文件,不是SSH的)的,我觉得这是好事——省时间;特别想要的话可以自己运行.$profile
命令。
我们学习了用SSH登录Windows PowerShell的方法,这个方法也能用于SSH到macOS/Linux的PowerShell上面,蛋疼的喜欢的可以试试。
好了,看过了就等于会了。感谢观看!😺