在 Python 脚本中使用 sudo

Posted

技术标签:

【中文标题】在 Python 脚本中使用 sudo【英文标题】:Using sudo with Python script 【发布时间】:2012-10-14 06:42:12 【问题描述】:

每次执行脚本时,我都在尝试编写一个小脚本来挂载 VirtualBox 共享文件夹。我想用 Python 来做,因为我正在尝试学习它来编写脚本。

问题是我需要权限才能启动 mount 命令。我可以将脚本作为 sudo 运行,但我更喜欢它自己制作 sudo。

我已经知道将密码写入 .py 文件是不安全的,但我们谈论的是根本不重要的虚拟机:我只想单击 .py 脚本并使其工作。

这是我的尝试:

#!/usr/bin/env python
import subprocess

sudoPassword = 'mypass'
command = 'mount -t vboxsf myfolder /home/myuser/myfolder'

subprocess.Popen('sudo -S' , shell=True,stdout=subprocess.PIPE)
subprocess.Popen(sudoPassword , shell=True,stdout=subprocess.PIPE)
subprocess.Popen(command , shell=True,stdout=subprocess.PIPE)

我的python版本是2.6

【问题讨论】:

不使用/etc/fstab有什么原因吗? @mensi 是的,我正在练习使用 python 来实现这些目的 你需要通过标准输入传递密码,见***.com/a/165662/894872 如果你不知道自己在做什么,避免shell=True。如果没有它你就无法让事情顺利进行,请了解它的作用以及它是如何工作的(然后通常你可以)。 这能回答你的问题吗? running a command as a super user from a python script 【参考方案1】:

许多答案都集中在如何使您的解决方案发挥作用,而很少有人认为您的解决方案是一种非常糟糕的方法。如果你真的想“练习来学习”,为什么不练习使用好的解决方案呢?硬编码您的密码正在学习错误方法!

如果您真正想要的是该卷的无密码mount,那么可能根本不需要sudo!那么我可以建议其他方法吗?

按照mensi 的建议使用/etc/fstab。使用选项 usernoauto 让普通用户挂载该卷。

使用Polkit 进行无密码操作:使用<allow_any>yes</allow_any> 为您的脚本配置一个.policy 文件并放在/usr/share/polkit-1/actions

编辑/etc/sudoers 以允许您的用户在不输入密码的情况下使用sudo。正如@Anders 建议的那样,您可以将此类使用限制为特定命令,从而避免您的帐户中无限的无密码根权限。有关/etc/sudoers 的更多详细信息,请参阅this answer。

以上所有都允许无密码的 root 权限,没有一个要求你硬编码你的密码。选择任何方法,我可以更详细地解释它。

至于为什么硬编码密码是一个非常糟糕的主意,这里有一些很好的链接供进一步阅读:

Why You Shouldn’t Hard Code Your Passwords When Programming How to keep secrets secret (Alternatives to Hardcoding Passwords) What's more secure? Hard coding credentials or storing them in a database? Use of hard-coded credentials, a dangerous programming error: CWE Hard-coded passwords remain a key security flaw

【讨论】:

最后一点,edit sudoers在askubuntu.com/a/155827/42796解释得很好 解释一下为什么硬编码用户密码是一种非常糟糕的方法,这可能对新手有所帮助。 @pdoherty926:出于安全原因,我认为这很明显,但您是对的,了解为什么可能是一个好主意。这超出了此答案的范围,因此我将对其进行编辑以添加一些链接以供进一步阅读。 关于使用无密码 sudo 的建议:硬编码密码是不好的,但将用户添加到 /etc/sudoers 几乎同样糟糕!在这两种情况下,任何有权访问用户帐户的攻击者也将拥有 root 访问权限。 @balu 意识到当您将用户添加到/etc/sudoers 时,您可以将其限制为某些命令并使用各种其他控件。将用户添加到/etc/sudoers 确实不一定 一定会启用root 访问权限。尽管对于卷的简单挂载,像/etc/fstab 这样的专用工具当然更好。【参考方案2】:
sudoPassword = 'mypass'
command = 'mount -t vboxsf myfolder /home/myuser/myfolder'
p = os.system('echo %s|sudo -S %s' % (sudoPassword, command))

试试这个,让我知道它是否有效。 :-)

还有这个:

os.popen("sudo -S %s"%(command), 'w').write('mypass')

【讨论】:

这是我谷歌搜索时的第一次尝试,但不起作用:它要求我在控制台输入密码,而不是直接输入 sudoPassword 值 @RomanRdgz echo %s 将其转换为标准输入并将 sudoPassword 的输出通过管道传输到 sudo 命令的标准输入。因此它应该工作(并且确实在这里工作) 我导入了操作系统,然后复制粘贴,但它不起作用:一直要求输入密码。事实上,如果我等待并且在被问到时不写任何东西,输出看起来就像代码尝试输入密码 3 次错误,说 3 次'对不起,再试一次' 你真的不应该使用这样的行os.system('echo %s|sudo -S %s' % (sudoPassword, command)),因为它会带来一个安全漏洞。通过将密码写为 shell 命令,它可以通过.bash_history 文件和运行history shell 命令访问。始终通过标准输入传递密码,因为它更安全 这个答案有 3 个反对票,我正在增加另一个。 这段代码增加了两个漏洞:1)在进程表中记录密码,任何其他进程都可以看到,如上所述,但也2)shell注入,如果有什么否则可以设置该密码,并将其设置为 foo$(rm -rf /*)bar ?你看到这个问题了吗?【参考方案3】:

将密码传递给sudo的标准输入:

#!/usr/bin/env python
from subprocess import Popen, PIPE

sudo_password = 'mypass'
command = 'mount -t vboxsf myfolder /home/myuser/myfolder'.split()

p = Popen(['sudo', '-S'] + command, stdin=PIPE, stderr=PIPE,
          universal_newlines=True)
sudo_prompt = p.communicate(sudo_password + '\n')[1]

注意:您可以配置无密码 sudo 或 SUDO_ASKPASS 命令,而不是在源代码中硬编码您的密码。

【讨论】:

您描述的 Popen 引发错误 can only concatenate list (not “str”) to list 我将其更改为 Popen(['sudo -S ' + command] - 这对我有用。似乎在回答时这已隐式添加到列表中..不再允许?或支持.. @ppumkin 错误。查看答案中的代码。它有.split()。与您的代码进行比较。 天啊,是的。我错过了最后的 split() .. 哇,深夜编码。我睡在上面,并决定以任何方式这样做都是一个坏主意,所以改用无密码的路线:D 我只是想要一些工作并且绝望 如何通过 sudo 有效地解决多个命令?我想按以下顺序执行这些命令:``` sudo mkdir Filestore sudo mount [filestore-info] Filestore sudo chmod 777 Filestore ``` 基本上是 3 个 sudo 命令【参考方案4】:

在 sudo 命令中使用 -S 选项,告诉从 'stdin' 而不是终端设备读取密码。

告诉 Popen 从 PIPE 读取标准输入。

通过将密码用作通信方法的参数,将密码发送到进程的标准输入管道。不要忘记在密码末尾添加一个换行符“\n”。

sp = Popen(cmd , shell=True, stdin=PIPE)
out, err = sp.communicate(_user_pass+'\n')   

【讨论】:

【参考方案5】:

subprocess.Popen 创建一个进程并打开管道和东西。你正在做的是:

启动一个进程sudo -S 启动一个进程mypass 启动一个进程mount -t vboxsf myfolder /home/myuser/myfolder

这显然是行不通的。您需要将参数传递给 Popen。如果您查看its documentation,您会注意到第一个参数实际上是参数列表。

【讨论】:

好的,我知道我做错了什么,但我认为这里不可能将 sudo 的密码作为参数传递给 subprocess.Popen(['sudo', '-S' , 密码, 命令], shell=True, stdin=subprocess.PIPE)。那么我该怎么做呢? 查看链接的 SO 问题【参考方案6】:

我将它用于 python 3.5。我是使用 subprocess 模块完成的。使用这样的密码非常不安全

subprocess 模块将命令作为字符串列表,因此可以使用 split() 预先创建一个列表,或者稍后传递整个列表。阅读文档以获取更多信息。

#!/usr/bin/env python
import subprocess

sudoPassword = 'mypass'
command = 'mount -t vboxsf myfolder /home/myuser/myfolder'.split()

cmd1 = subprocess.Popen(['echo',sudoPassword], stdout=subprocess.PIPE)
cmd2 = subprocess.Popen(['sudo','-S'] + command, stdin=cmd1.stdout, stdout=subprocess.PIPE)

output = cmd2.stdout.read.decode()

【讨论】:

【参考方案7】:

有时需要回车:

os.popen("sudo -S %s"%(command), 'w').write('mypass\n')

【讨论】:

【参考方案8】:

请尝试模块 pexpect。这是我的代码:

import pexpect
remove = pexpect.spawn('sudo dpkg --purge mytool.deb')
remove.logfile = open('log/expect-uninstall-deb.log', 'w')
remove.logfile.write('try to dpkg --purge mytool\n')
if remove.expect(['(?i)password.*']) == 0:
    # print "successfull"
    remove.sendline('mypassword')
    time.sleep(2)
    remove.expect(pexpect.EOF,5)
else:
    raise AssertionError("Fail to Uninstall deb package !")

【讨论】:

【参考方案9】:

要限制你作为 sudo 运行的内容,你可以运行

python non_sudo_stuff.py
sudo -E python -c "import os; os.system('sudo echo 1')"

无需存储密码。 -E 参数将您当前用户的环境传递给进程。请注意,您的 shell 在第二个命令之后将具有 sudo 权限,因此请谨慎使用!

【讨论】:

【参考方案10】:

我知道最好不要在脚本中硬编码 sudo 密码。但是,由于某种原因,如果您无权修改/etc/sudoers 或更改文件所有者,Pexpect 是一个可行的替代方案。

这里有一个 Python 函数 sudo_exec 供您参考:

import platform, os, logging
import subprocess, pexpect

log = logging.getLogger(__name__)

def sudo_exec(cmdline, passwd):
    osname = platform.system()
    if osname == 'Linux':
        prompt = r'\[sudo\] password for %s: ' % os.environ['USER']
    elif osname == 'Darwin':
        prompt = 'Password:'
    else:
        assert False, osname

    child = pexpect.spawn(cmdline)
    idx = child.expect([prompt, pexpect.EOF], 3)
    if idx == 0: # if prompted for the sudo password
        log.debug('sudo password was asked.')
        child.sendline(passwd)
        child.expect(pexpect.EOF)
return child.before

【讨论】:

【参考方案11】:

它适用于 python 2.7 和 3.8:

from subprocess import Popen, PIPE
from shlex import split

proc = Popen(split('sudo -S %s' % command), bufsize=0, stdout=PIPE, stdin=PIPE, stderr=PIPE)
proc.stdin.write((password +'\n').encode()) # write as bytes
proc.stdin.flush() # need if not bufsize=0 (unbuffered stdin)

如果标准输入缓冲,没有.flush() 密码将无法到达sudo。 在 python 2.7 Popen 默认使用 bufsize=0stdin.flush() 是不需要的。

为了安全使用,请在受保护的目录中创建密码文件:

mkdir --mode=700 ~/.prot_dir
nano ~/.prot_dir/passwd.txt
chmod 600 ~/.prot_dir/passwd.txt 

从 ~/.prot_dir/passwd.txt 开始你的 py-script 读取密码

with open(os.environ['HOME'] +'/.prot_dir/passwd.txt') as f:
    password = f.readline().rstrip()

【讨论】:

以上是关于在 Python 脚本中使用 sudo的主要内容,如果未能解决你的问题,请参考以下文章

我正在尝试在 python 中运行 sudo 来打开一个应用程序

使用 sudo 权限在当前 shell 中执行 shell 脚本

无法从raspberry pi 2 ver B中的php脚本运行sudo killall python

如何在 gitlab ci 的构建脚本中使用 sudo?

用于安装 aur 包的 python 脚本

如何使用 python 子进程模块将 sqlplus 作为 sudo 连接到 oracle 用户?