求助,有什么办法能够控制 cmd/powershell 进行 ssh 自动化登录? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
aizya
V2EX    程序员

求助,有什么办法能够控制 cmd/powershell 进行 ssh 自动化登录?

  •  
  •   aizya 2024-07-15 12:55:42 +08:00 2920 次点击
    这是一个创建于 529 天前的主题,其中的信息可能已经有所发展或是发生改变。

    遇到一个自动化登录的问题,不是简单的 ssh 免密登录哈,要求如下:

    环境:Windows10

    需求: 输入 ssh 登录系统后,会自动弹出一个 SHA256 的码,需要能够获取到这个码,再调用一个外部的 REST 接口获取动态密码后,点击 YES,输入密码,进入 Linux 系统。

    C:\Users\xxx)>ssh [email protected] The authenticity of host '192.168.xxx.xxx (192.168.xxx.xxx:)’can't be established ED25519 key fingerprint is SHA256:edVhpWttoR1W/30HxIn2BiefgyDj6YZuxxxxr2YNo. This key is not known by any other names Are you sure you want to continue connecting(yes/no/[fingerprint])?yes 

    困难:1. 如何获取 cmd/powershell 中展示内容? 2. 如何自动控制 cmd 输入密码?

    希望有大哥能提供一点思路,最好是用 Python 方案。

    第 1 条附言    2024-07-15 14:34:08 +08:00

    可能之前问题没有描述的太清楚,以至于大家误以为是为了获取SHA256值,抱歉,下面我再补充一下实际的场景:

    1. 使用本地的Windows登录远程Linux电脑时,输入ssh [email protected].1,会弹出一个动态的Dynamic code:

    2. 需要能够读取CMD的输出,获取到这个code,然后调用一个第三方的REST接口获取OTP Code,将这个OTP Code作为密码输入到cmd中,登录到Linux系统中。整个过程自动化。

      C:\Users\demo>ssh [email protected]
      ([email protected].1)Dynamic code: XXSAXXXXXXXXPOMBBWNCAA04/rJ80bSm9Vc9hsH99UiB6CACGthZ4Swa+qIMbDSxKXKCIWZ LKSErYRII/PXXXXXtJ04Gv7cI3SCzgtmkXXXXXXXXXAAAAdutUOVPN601/AdFP/cNcGptu+3J/pG9h3UAL3LAUJJrqLLoAi02YdZX6XXXXXXX8TK+nIds8nA57I5/oAb+T
      Input OTP Code: ???

    由于没法贴图,大致的需求就是如上所示。

    25 条回复    2024-07-24 14:24:29 +08:00
    tool2dx
        1
    tool2dx  
       2024-07-15 13:01:58 +08:00
    一般不用管 sha256 ,我用"echo y | ssd ", 可以自动输入 yes
    zhlxsh
        2
    zhlxsh  
       2024-07-15 13:02:37 +08:00 via iPhone   1
    paramiko
    Wh0amis
        3
    Wh0amis  
       2024-07-15 13:06:22 +08:00
    import pexpect

    # 启动 cmd
    child = pexpect.spawn("cmd.exe")

    # 等待 cmd 提示符出现
    child.expect("C:\\")

    # 输入 ssh 命令
    child.sendline("ssh [email protected]")

    # 等待输入密码的提示
    child.expect("password:")

    # 输入密码
    child.sendline("mypassword")

    # 等待登录成功
    child.expect("#")

    # 获取登录后的输出
    print(child.before.decode())
    caomingjun
        4
    caomingjun  
       2024-07-15 13:08:49 +08:00 via Android
    获取 fingerprint 有现成的 ssh-keyscan ,不需要强行读 ssh 输出。sshpass 也支持直接通过选项指定密码登陆。

    你甚至不需要经过 powershell ,我估计 python 有现成的包可以把上面这些事情都干了。
    263
        5
    263  
       2024-07-15 13:14:23 +08:00
    import paramiko
    import hashlib
    import base64
    import getpass

    def get_host_key_fingerprint(hostname, port=22):
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    try:
    client.connect(hostname, port=port, username='dummy', password='dummy')
    except:
    pass

    key = client.get_transport().get_remote_server_key()
    fingerprint = hashlib.sha256(key.get_fingerprint()).hexdigest()

    client.close()
    return ':'.join(a+b for a,b in zip(fingerprint[::2], fingerprint[1::2]))

    def ssh_login_with_otp(hostname, username, password, otp_func):
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    # 获取 fingerprint
    fingerprint = get_host_key_fingerprint(hostname)
    print(f"Host key fingerprint: {fingerprint}")

    # 验证 fingerprint
    if input("Verify fingerprint (y/n): ").lower() != 'y':
    print("Fingerprint verification failed")
    return

    # 获取动态密码
    otp = otp_func()

    try:
    client.connect(hostname, username=username, password=password+otp)
    print("Login successful!")

    # 执行命令
    stdin, stdout, stderr = client.exec_command('ls -l')
    print(stdout.read().decode())

    except Exception as e:
    print(f"Login failed: {str(e)}")

    finally:
    client.close()

    # 模拟获取动态密码的函数
    def get_otp():
    return input("Enter OTP: ")

    # 使用示例
    hostname = 'example.com'
    username = 'your_username'
    password = getpass.getpass("Enter password: ")

    ssh_login_with_otp(hostname, username, password, get_otp)


    gpt yyds
    aizya
        6
    aizya  
    OP
       2024-07-15 13:15:05 +08:00
    @Wh0amis 好像不行,我的脚本要在 windows 上运行,pexpect 对 windows 支持好像不是特别好。

    https://github.com/pexpect/pexpect/issues/567
    virusdefender
        7
    virusdefender  
       2024-07-15 13:19:14 +08:00
    fingerprint 我记得不是固定的么,先用一个非交互式的程序获取一次,然后生成密码,再用正常的 ssh 登录的库感觉会比较简单。
    CEBBCAT
        8
    CEBBCAT  
       2024-07-15 13:24:42 +08:00
    这是两个问题,第一个是要你确认对方公钥的,另外一个问题是登录时要求提供动态密码的。
    millson
        9
    millson  
       2024-07-15 13:27:46 +08:00
    yiyiwa
        10
    yiyiwa  
       2024-07-15 14:02:58 +08:00
    import-module posh-ssh

    这个模块可以解决。
    kxg3030
        11
    kxg3030  
       2024-07-15 14:18:38 +08:00
    感觉这个不用包都能解决呢,直接拿到 io 对象,输入密码然后输入\r\n 这样不行么
    1rv013c6aiWPGt24
        12
    1rv013c6aiWPGt24  
       2024-07-15 14:33:26 +08:00
    不太懂,但是我记得可以配置 ssh 免密登录的,就是用 ssh 生成的 key ,之前用 Windowsterminal 登录服务器的时候就这样弄得
    aizya
        13
    aizya  
    OP
       2024-07-15 14:38:51 +08:00
    @caomingjun emm.. 你看我最新补充的需求,并不只是 SSH 免密。
    @263 谢谢回复,这个代码运行起来不太满足我的需求。
    zhangeric
        14
    zhangeric  
       2024-07-15 14:58:45 +08:00
    python 不熟,不过可以开 cmd 或 powershell,截图加 ocr 获取 sha,然后获取 key,然后模拟输入.
    .net 下可以用 Process 类将 cmd 或者 powershell 的 StandardInput 和 StandardOutput 重定向进行操作.
    cheng6563
        15
    cheng6563  
       2024-07-15 15:07:11 +08:00
    直接读写 stdout ,stdin 就行了吧
    你找下相关库,应该有 API 是启动进程时手动处理输入输出流的
    cheng6563
        16
    cheng6563  
       2024-07-15 15:11:53 +08:00
    Process process = Runtime.getRuntime().exec("cmd /c ...");
    InputStream inputStream = process.getInputStream();
    OutputStream outputStream = process.getOutputStream();
    InputStream errorStream = process.getErrorStream();

    java 就是这样,可以获得 3 个流,从 outputStream 可以读到 ssh 打出来的文本,之后把密码模拟按键写到 inputStream 里去。要注意用 sleep 之类的方法控制流程。
    263
        17
    263  
       2024-07-15 15:17:59 +08:00
    import subprocess
    import requests


    def get_otp(dynamic_code):
    api_url = 'https://api.example.com/get_otp'
    respOnse= requests.post(api_url, json={'dynamic_code': dynamic_code})
    return response.json()['otp']


    ssh_process = subprocess.Popen(
    ['ssh', '[email protected]'],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    universal_newlines=True,
    )

    dynamic_code = ''
    while True:
    output = ssh_process.stdout.readline()
    if 'Dynamic code:' in output:
    dynamic_code = output.split('Dynamic code:')[1].strip()
    break

    otp = get_otp(dynamic_code)

    ssh_process.stdin.write(f'{otp}\n')
    ssh_process.stdin.flush()

    while True:
    output = ssh_process.stdout.readline()
    print(output.strip())
    if 'Last login:' in output:
    print("登录成功!")
    break
    elif 'Permission denied' in output:
    print("登录失败!")
    break

    ssh_process.stdin.close()
    ssh_process.stdout.close()
    ssh_process.stderr.close()


    CLAUDE 3.5 SONNET
    error451
        18
    error451  
       2024-07-15 15:18:51 +08:00
    Python 用 subprocess 下的 Popen 可执行 shell 命令, 可以将 stdout, stdin 重定向,利用 subprocess.PIPE ,就可以实现交互式命令执行。
    然后用 re 模块, 写正则表达式来读取 stdout 中输出的 SHA256 token
    Radiation
        19
    Radiation  
       2024-07-15 15:22:34 +08:00
    用 python 的 ssh 库不更好吗,手动实现一个简单的客户端,可操作的范围也大了。
    LonnyWong
        20
    LonnyWong  
       2024-07-15 15:26:06 +08:00
    可以自己改一下 tssh 的代码 https://github.com/trzsz/trzsz-ssh/blob/d154d5bba805fa21d36fd0b02a4df6cd4dae374d/tssh/login.go#L593

    将 question 传给配置的 OtpCommand1 命令,这个命令对应的程序再自己实现,从参数 question 解释出 SHA256 码,调用 REST 接口获取到动态密码后,输出到 stdout 即可。
    aizya
        21
    aizya  
    OP
       2024-07-15 16:56:44 +08:00
    @LonnyWong 好方法,我试试,感谢!
    LonnyWong
        22
    LonnyWong  
       2024-07-15 17:05:21 +08:00
    @aizya #21 如有需要,我可以修改一下 tssh 。欢迎加入 QQ 群详聊,群号在 tssh 的 github 首页最下面。
    chf007
        23
    chf007  
       2024-07-15 17:27:40 +08:00
    这种应该是自定义的 PAM 集成了二次校验,看看官方手册有没有非交互式的参数可用,比如 --code=xxx 啥的,或者自已有权限的话,干脆禁用掉二次验证呗
    aizya
        24
    aizya  
    OP
       2024-07-24 13:07:46 +08:00
    @LonnyWong #20 结帖了,感谢 LonnyWong , 最终是使用 tszsz-ssh https://github.com/trzsz/trzsz-ssh ,配置了一个#!! OtpCommand1 实现了动态验证码的获取。
    LonnyWong
        25
    LonnyWong  
       2024-07-24 14:24:29 +08:00   1
    #24 具体可看 https://github.com/trzsz/trzsz-ssh/blob/main/README.cn.md#%E8%AE%B0%E4%BD%8F%E7%AD%94%E6%A1%88 这个文档,tssh 将在 v0.1.22 正式支持,在此之前可以 go install github.com/trzsz/trzsz-ssh/cmd/tssh@main 这样安装。

    自己实现获取动态密码的程序,指定 %q 参数可以得到问题内容,将动态密码输出到 stdout 并正常退出即可,调试信息可以输出到 stderr ( tssh --debug 运行时可以看到 )。配置举例(序号代表第几个问题,一般只有一个问题,只需配置 OtpCommand1 即可):

    Host custom_otp_command
    #!! OtpCommand1 /path/to/your_own_program %q
    #!! OtpCommand2 C:\python3\bin\python C:\your_python_code.py %q
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solaa     861 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 21:46 PVG 05:46 LAX 13:46 JFK 16:46
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86