使用subprocess发送密码

32

我正在尝试使用Python的subprocess模块登录到一个安全的ftp站点并获取文件。但是每次在请求密码时,我都会遇到问题。目前我有以下代码:

from subprocess import Popen, PIPE

proc = Popen(['sftp','user@server', 'stop'], stdin=PIPE)
proc.communicate('password')

这仍然停留在密码提示处。如果我手动输入密码,它会进入ftp站点,然后在命令行中输入密码。我看到有人建议使用pexpect,但长话短说,我需要一个标准库解决方案。是否可以使用subprocess和/或任何其他stdlib?我在上面忘记了什么?


重复的 https://dev59.com/ukzSa4cB1Zd3GeqPlEeq - Robert Gowland
14个回答

12

试试

proc.stdin.write('yourPassword\n')
proc.stdin.flush()

应该可以。

你所描述的听起来像是stdin=None,让子进程继承父进程的stdin(也就是你的Python程序)。


2
嗯... 这似乎仍在停止并要求密码。 - The Jug
1
你在第二个线程中读取进程的标准输出,对吧?否则,可能会发生奇怪的事情。此外,请检查sftp是否允许从stdin输入密码,可以使用命令行中的“echo password | sftp ...”进行测试。有可能sftp总是打开一个shell。 - Aaron Digulla
3
据我所知,密码不会通过标准输入写入。这段代码经过测试了吗? - Sebastian Gabriel Vinci
2
@SebastianGabrielVinci 是的。大多数程序在等待密码时将控制台置于RAW/NOECHO模式,因此您无法看到它们,但它们都从stdin读取。一些代码还检查stdin是否为tty。对于这些情况,您需要使用伪终端。 - Aaron Digulla

6
from subprocess import Popen, PIPE

proc = Popen(['sftp','user@server', 'stop'], stdin=PIPE)
proc.communicate(input='password')

尝试在通信中使用input='password',这对我有效。


6
也许你应该使用类似于expect的库?例如Pexpect(示例)。还有其他类似的Python库。

+1 for Pexpect。这是与控制台进程交互的正确解决方案。 - Chinmay Kanchi
有没有标准库的expect模块?我需要坚持使用标准库。 - The Jug
Pexpect不是标准库,但它是纯Python的,所以只需将pexpect.py文件放在你的Python路径中的某个位置。例如,可以将其放在与你的脚本文件相邻的位置。 - codeape
1
我不想为我们的产品再追踪另一个非标准库,因为它并不是很重要。然而,据我所知,这个库还是不错的。从我所见,没有标准库解决这个问题,但我会给你提供好的库建议的功劳。如果以后有人遇到这个问题,并想知道我是怎么做的,我可能会让我们的代码调用一个 bash 脚本。 - The Jug
30
-1 是因为问题明确要求使用标准 Python 库解决方案,而不是 pexpect。就这样说吧。 - Danny Whitt
2
“example”链接http://www.noah.org/wiki/Pexpect#Overview现在已经失效了;我猜应该是与https://pexpect.readthedocs.io/en/stable/examples.html相同的内容。 - sdaau

5

使用Paramiko进行SFTP。对于其他任何事情,可以使用以下方法:

import subprocess

args = ['command-that-requires-password', '-user', 'me']

proc = subprocess.Popen(args, 
                        stdin=subprocess.PIPE, 
                        stdout=subprocess.PIPE, 
                        stderr=subprocess.PIPE)

proc.stdin.write('mypassword\n')
proc.stdin.flush()

stdout, stderr = proc.communicate()
print stdout
print stderr

这绝对比paramiko更快。我切换到这个实现后,速度提高了2倍。 - Novice

2
由于某些原因,我无法使用标准库中的任何答案 - 所有的答案都出现了非常奇怪的问题。这里还有一个人: 无法向子进程提供密码 [python] 遇到了同样的问题,并得出结论,最终你只能使用pexpect来发送密码。
我想在这里添加我的最终代码,以便节省任何遇到类似问题的人的时间,因为我在这上面浪费了很多时间(Python 3,2020):
ssh_password = getpass("user's password: ")
ssh_password = (ssh_password + "\n").encode()

scp_command = 'scp xx.xx.xx.xx:/path/to/file.log /local/save/path/'

child = pexpect.spawn(scp_command)
# make output visible for debugging / progress watching
child.logfile = sys.stdout.buffer

i = child.expect([pexpect.TIMEOUT, "password:"])
if i == 0:
    print("Got unexpected output: {} {}".format(child.before, child.after))
    return
else:
    child.sendline(ssh_password)
child.read()

以上代码运行一个SCP命令,将一个文件从远程服务器拉到本地电脑 - 根据需要修改服务器IP和路径。

需要记住的关键点:

  • 在 child.expect 调用中必须有 pexpect.TIMEOUT
  • 无论传递什么字符串,都必须编码为字节,并且必须使用默认编码
  • 将 pexpect 输出写入 sys.stdout.buffer 以便实际查看正在发生的情况
  • 最后必须有一个 child.read()

1
我建议放弃使用子进程方法,改用paramiko包进行SFTP访问。

0

这个问题困扰了我一个星期。我需要安全地通过 subprocess 提交来自用户输入的密码,因为我试图避免引入命令注入漏洞。以下是我如何在同事的帮助下解决该问题的方法。

import subprocess

command = ['command', 'option1', '--password']
subprocess.Popen(command, stdin=subprocess.PIPE).wait(timeout=60)

最重要的组件是 .wait(timeout=int),因为它允许用户向 stdin 提供输入。否则,超时默认为 0,不给用户时间输入,导致返回 None 或 null 字符串。我花了很长时间才弄清楚这一点。

如果你知道自己要多次使用此功能,可以覆盖 popen 函数并将其用作私有方法,据同一程序员告知,如果你预计其他人会对代码进行维护并且不想让他们乱改,这是最佳实践。

def _popen(cmd):
   proc_h = subprocess.Popen(cmd, stdin=subprocess.PIPE)
   proc_h.wait(timeout=60)
   return proc_h.poll() == os.EX_OK

如果用户将被提示输入,则删除stdout=subprocess.PIPE非常重要。否则,进程似乎会挂起60秒,用户不会得到提示,也不会意识到他们需要提供密码。stdout将自然地进入shell窗口,并允许用户向popen()传递输入。

此外,只是为了解释为什么返回proc_h.poll() == os.EX_OK,因为它在命令成功时返回0。这只是C风格的最佳实践,用于在函数失败时返回系统错误代码,同时考虑到return 0将被解释器视为“false”。


0

这是一个使用expect而不是pexpect的纯Python解决方案。

如果您在Ubuntu上,首先需要安装expect:

sudo apt install expect

Python 3.6 或之后版本:

def sftp_rename(from_name, to_name):
    sftp_password = 'abigsecret'
    sftp_username = 'foo'
    destination_hostname = 'some_hostname'
    from_name = 'oldfilename.txt'
    to_name = 'newfilename.txt'
    commands = f"""
spawn sftp -o "StrictHostKeyChecking no" 
{sftp_username}@{destination_hostname}
expect "password:"
send "{sftp_password}\r"
expect "sftp>"
send "rename {from_name} {to_name}\r"
expect "sftp>"
send "bye\r"
expect "#"
"""
    sp = subprocess.Popen(['expect', '-c', commands], stdin=subprocess.PIPE, stdout=subprocess.PIPE)

你应该阅读此主题的问题中第四条评论。 - user1767754
@user1767754,你具体想让我注意什么?我提供的答案最接近要求 - 纯Python。 - Duke Dougal

0

Python有一个名为ftplib的内置库,可以用于FTP进程而无需任何麻烦。(假设远程服务器运行了FTP服务)

from ftplib import FTP
ftp = FTP('ftp.us.debian.org')  # connect to host, default port
ftp.login()                     # user anonymous, passwd anonymous@
##'230 Login successful.'
ftp.cwd('debian')               # change into "debian" directory
##'250 Directory successfully changed.'
ftp.retrlines('LIST') 

否则,您可以使用scp命令,这是一个命令行工具。可以通过为远程主机创建无密码用户来避免密码问题。
import os
os.system('scp remoteuser@remotehost:/remote/location/remotefile.txt /client/location/')

Linux系统中创建一个无需密码的用户,按照以下步骤进行操作。请参考此SO答案

> ssh-keyscan remotehost 
> known_hosts ssh-keygen -t rsa # ENTER toevery field (One time) 
> ssh-copy-id remoteuser@remotehost

0

由于您只想抓取一个文件,我尝试使用“子进程”,但对我来说不起作用。所以现在我正在使用 paramiko,这是我的代码: 这是我在网上找到的一个教程: 使用 Python 的 paramiko 将文件从本地服务器传输到远程服务器,反之亦然 "https://www.youtube.com/watch?v=dtvV2xKaVjw"

下面是我用于将 Linux 中一个文件夹中的所有文件从 Linux 传输到 Windows 的代码

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='11.11.11.1111', username='root', password='********', port=22)
sftp_client = ssh.open_sftp()
source_folder = '/var/ftp/file_pass'
local_folder = 'C:/temp/file_pass'
inbound_files = sftp_client.listdir(source_folder)
print(inbound_files)

for ele in inbound_files:
    try:
        path_from = source_folder + '/' + ele
        path_to = local_folder + '/'+ ele
        sftp_client.get(path_from, path_to)
    except:
        print(ele)

sftp_client.close()
ssh.close()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接