V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
aizya
V2EX  ›  程序员

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

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

    遇到一个自动化登录的问题,不是简单的 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   ·   实用小工具   ·   1075 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 18:13 · PVG 02:13 · LAX 11:13 · JFK 14:13
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.