Add --no-sudo-pythonpath option

This provides a way to avoid setting PYTHONPATH when invoking the
privileged part of sshuttle with sudo. This is useful if running
sshuttle as a PEX archive, as Telepresence does, as it enables
sshuttle's sudo access to be securely locked down.

PEX archives will extract themselves into the invoking user's home
directory, which means that the invoking user has full control over
the code in them. This makes restricting sudo access with
PYTHONPATH set completely pointless in this scenario -- an attacker
could put any code into ~/.pex and gain full root access anyway.

On the other hand, if sshuttle is a PEX archive, the privileged
invocation will simply extract itself into /root/.pex anyway, so
there is no need to set PYTHONPATH in this case.
This commit is contained in:
Steven McDonald 2018-09-21 18:02:16 +02:00
parent 561b648e4b
commit f528bb9846
3 changed files with 18 additions and 7 deletions

View File

@ -183,7 +183,7 @@ class MultiListener:
class FirewallClient: class FirewallClient:
def __init__(self, method_name): def __init__(self, method_name, sudo_pythonpath):
# Default to sudo unless on OpenBSD in which case use built in `doas` # Default to sudo unless on OpenBSD in which case use built in `doas`
elevbin = 'sudo' elevbin = 'sudo'
@ -198,10 +198,12 @@ class FirewallClient:
['--firewall']) ['--firewall'])
if ssyslog._p: if ssyslog._p:
argvbase += ['--syslog'] argvbase += ['--syslog']
elev_prefix = [part % {'eb': elevbin, 'pp': python_path} elev_prefix = [part % {'eb': elevbin}
for part in ['%(eb)s', '-p', for part in ['%(eb)s', '-p',
'[local %(eb)s] Password: ', '[local %(eb)s] Password: ']]
'/usr/bin/env', 'PYTHONPATH=%(pp)s']] if sudo_pythonpath:
elev_prefix += ['/usr/bin/env',
'PYTHONPATH=%s' % python_path]
argv_tries = [elev_prefix + argvbase, argvbase] argv_tries = [elev_prefix + argvbase, argvbase]
# we can't use stdin/stdout=subprocess.PIPE here, as we normally would, # we can't use stdin/stdout=subprocess.PIPE here, as we normally would,
@ -550,7 +552,7 @@ def main(listenip_v6, listenip_v4,
ssh_cmd, remotename, python, latency_control, dns, nslist, ssh_cmd, remotename, python, latency_control, dns, nslist,
method_name, seed_hosts, auto_hosts, auto_nets, method_name, seed_hosts, auto_hosts, auto_nets,
subnets_include, subnets_exclude, daemon, to_nameserver, pidfile, subnets_include, subnets_exclude, daemon, to_nameserver, pidfile,
user): user, sudo_pythonpath):
if daemon: if daemon:
try: try:
@ -560,7 +562,7 @@ def main(listenip_v6, listenip_v4,
return 5 return 5
debug1('Starting sshuttle proxy.\n') debug1('Starting sshuttle proxy.\n')
fw = FirewallClient(method_name) fw = FirewallClient(method_name, sudo_pythonpath)
# Get family specific subnet lists # Get family specific subnet lists
if dns: if dns:

View File

@ -76,7 +76,8 @@ def main():
opt.daemon, opt.daemon,
opt.to_ns, opt.to_ns,
opt.pidfile, opt.pidfile,
opt.user) opt.user,
opt.sudo_pythonpath)
if return_code == 0: if return_code == 0:
log('Normal exit code, exiting...') log('Normal exit code, exiting...')

View File

@ -310,3 +310,11 @@ parser.add_argument(
(internal use only) (internal use only)
""" """
) )
parser.add_argument(
"--no-sudo-pythonpath",
action="store_false",
dest="sudo_pythonpath",
help="""
do not set PYTHONPATH when invoking sudo
"""
)