I'd like to have a Python program start listening on port 80, but after that execute without root permissions. Is there a way to drop root or to get port 80 without it?
You won't be able to open a server on port 80 without root privileges, this is a restriction on the OS level. So the only solution is to drop root privileges after you have opened the port.
Here is a possible solution to drop root privileges in Python: Dropping privileges in Python. This is a good solution in general, but you'll also have to add os.setgroups([]) to the function to ensure that the group membership of the root user is not retained.
I copied and cleaned up the code a little bit, and removed logging and the exception handlers so it is left up to you to handle OSError properly (it will be thrown when the process is not allowed to switch its effective UID or GID):
import os, pwd, grp
def drop_privileges(uid_name='nobody', gid_name='nogroup'):
if os.getuid() != 0:
# We're not root so, like, whatever dude
return
# Get the uid/gid from the name
running_uid = pwd.getpwnam(uid_name).pw_uid
running_gid = grp.getgrnam(gid_name).gr_gid
# Remove group privileges
os.setgroups([])
# Try setting the new uid/gid
os.setgid(running_gid)
os.setuid(running_uid)
# Ensure a very conservative umask
old_umask = os.umask(077)
I recommend using authbind to start your Python program, so none of it has to run as root.
https://en.wikipedia.org/wiki/Authbind
It is not a good idea to ask the user to enter his/her user-name and group whenever I need to drop privileges. Here is a slightly modified version of Tamás's code which will drop privileges and switch to the user who initiated the sudo command. I am assuming you are using sudo (if not, use Tamás's code).
#!/usr/bin/env python3
import os, pwd, grp
#Throws OSError exception (it will be thrown when the process is not allowed
#to switch its effective UID or GID):
def drop_privileges():
if os.getuid() != 0:
# We're not root so, like, whatever dude
return
# Get the uid/gid from the name
user_name = os.getenv("SUDO_USER")
pwnam = pwd.getpwnam(user_name)
# Remove group privileges
os.setgroups([])
# Try setting the new uid/gid
os.setgid(pwnam.pw_gid)
os.setuid(pwnam.pw_uid)
#Ensure a reasonable umask
old_umask = os.umask(0o22)
#Test by running...
#./drop_privileges
#sudo ./drop_privileges
if __name__ == '__main__':
print(os.getresuid())
drop_privileges()
print(os.getresuid())
systemd can do it for you, if you start your program through systemd, systemd can hand off the already-open listening socket to it, and it can also activate your program on first connection. and you don't even need to daemonize it.
If you are going to go with the standalone approach, you need the capability CAP_NET_BIND_SERVICE (check capabilities man page). This can be done on a program-by-program basis with the correct command line tool, or by making your application (1) be suid root (2) start up (3) listen to the port (4) drop privileges / capabilities immediately.
Remember that suid root programs come with lots of security considerations (clean and secure environment, umask, privileges, rlimits, all those things are things that your program is going to have to set up correctly). If you can use something like systemd, all the better then.
The following is a further adaptation of Tamás's answer, with the following changes:
Use the python-prctl module to drop Linux capabilities to a specified list of capabilities to preserve.
The user can optionally be passed as a parameter (it defaults to looking up the user who ran sudo).
It sets all the user's groups and HOME.
It optionally changes directory.
(I'm relatively new to using this functionality, however, so I may have missed something. It might not work on older kernels (<3.8) or kernels with filesystem capabilities disabled.)
def drop_privileges(user=None, rundir=None, caps=None):
import os
import pwd
if caps:
import prctl
if os.getuid() != 0:
# We're not root
raise PermissionError('Run with sudo or as root user')
if user is None:
user = os.getenv('SUDO_USER')
if user is None:
raise ValueError('Username not specified')
if rundir is None:
rundir = os.getcwd()
# Get the uid/gid from the name
pwnam = pwd.getpwnam(user)
if caps:
prctl.securebits.keep_caps=True
prctl.securebits.no_setuid_fixup=True
# Set user's group privileges
os.setgroups(os.getgrouplist(pwnam.pw_name, pwnam.pw_gid))
# Try setting the new uid/gid
os.setgid(pwnam.pw_gid)
os.setuid(pwnam.pw_uid)
os.environ['HOME'] = pwnam.pw_dir
os.chdir(os.path.expanduser(rundir))
if caps:
prctl.capbset.limit(*caps)
try:
prctl.cap_permitted.limit(*caps)
except PermissionError:
pass
prctl.cap_effective.limit(*caps)
#Ensure a reasonable umask
old_umask = os.umask(0o22)
It can be used as follows:
drop_privileges(user='www', rundir='~', caps=[prctl.CAP_NET_BIND_SERVICE])
Most of this works unless you need to request the socket after you do some other stuff that you don't want to be superuser.
I made a project called tradesocket a while ago. It allows you to pass back and forth sockets on a posix system between processes. What I do is spin off a process at the beginning that stays superuser, and the rest of the process drops down in permissions and then requests the socket from the other.
Related
How can I launch a child process that has root privileges?
I have a python program in MacOS that can do most of its operations as a normal user. But occasionally, triggered by some user interaction, it will need root permissions to preform a task.
For security reasons, I don't want the entire GUI app to be started and left running as root. I want only a child process with a very minimal subset of functions to run as root.
For UX reasons, I don't want to have to tell the user "Sorry, please restart this app as Administrator". I want to be able to have them stay in the GUI, get presented with a pop-up that says "Uh, you need root to do that. Please enter your password."
Of course, if my unprivileged python process attempts to become root with
setuid(0)
...then I just get a permissions error
PermissionError: [Errno 1] Operation not permitted
What can I use as an alternate to setuid() so that I can launch a new child process on a MacOS system, after escalating privilege by getting authentication from the user in the GUI?
I want to be able to have them stay in the GUI, get presented with a pop-up that says "Uh, you need root to do that. Please enter your password."
This is exactly what the MacOS Security API's AuthorizationExecuteWithPrivileges() function was created for.
You can call AuthorizationExecuteWithPrivileges() directly with python's ctypes.
For example, consider your parent script running as your normal, non-root user. If you try to just run setuid(0), then it will fail with
PermissionError: [Errno 1] Operation not permitted
Instead, let's create another script named root_child.py, which we'll execute as root with AuthorizationExecuteWithPrivileges()
Child (root_child.py)
#!/usr/bin/env python3
import os
if __name__ == "__main__":
try:
os.setuid(9)
print( "I am root!" )
except Exception as e:
print( "I am not root :'(" )
Parent (spawn_root.py)
We can execute the above root_child.py script as root from our non-root script spawn_root.py:
import sys, ctypes, struct
import ctypes.util
from ctypes import byref
# import some C libraries for interacting via ctypes with the MacOS API
libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c"))
# https://developer.apple.com/documentation/security
sec = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))
kAuthorizationFlagDefaults = 0
auth = ctypes.c_void_p()
r_auth = byref(auth)
sec.AuthorizationCreate(None,None,kAuthorizationFlagDefaults,r_auth)
exe = [sys.executable,"root_child.py"]
args = (ctypes.c_char_p * len(exe))()
for i,arg in enumerate(exe[1:]):
args[i] = arg.encode('utf8')
io = ctypes.c_void_p()
print( "running root_child.py")
err = sec.AuthorizationExecuteWithPrivileges(auth,exe[0].encode('utf8'),0,args,byref(io))
print( "err:|" +str(err)+ "|" )
print( "root_child.py executed!")
Example Execution
Note that, because the credential challenge for AuthorizationExecuteWithPrivileges() comes via the GUI, you must execute this from within the GUI. If you attempt to execute the above scripts, for example, over a SSH in a tty, you'll get an error -60007, which is errAuthorizationInteractionNotAllowed and means:
The Security Server denied authorization because no user interaction is allowed.
user#host ~ % ./spawn_root.py
running root_child.py
err:|-60007|
root_child.py executed!
user#host ~ %
However, if executed from the Terminal app in the GUI, then it prompts the user for their password.
If the user successfully enters their credentials correctly, then the root_child.py script is executed with root privileges.
user#host ~ % ./spawn_root.py
running root_child.py
err:|0|
root_child.py executed!
Additional Information
Security
Note that AuthorizationExecuteWithPrivileges() has been deprecated by apple in-favor of an alternatve that requires you to pay them money. Unfortunately, there's some misinformation out there that AuthorizationExecuteWithPrivileges() is a huge security hole. While it's true that using AuthorizationExecuteWithPrivileges() incorrectly can cause security issues, it is not inherently insecure to use it.
Obviously, any time you run something as root, you need to be very careful!
AuthorizationExecuteWithPrivileges() is deprecated, but it can be used safely. But it can also be used unsafely!
It basically boils down to: do you actually know what you're running as root? If the script you're running as root is located in a Temp dir that has world-writeable permissions (as a lot of MacOS App installers have done historically), then any malicious process could gain root access.
To execute a process as root safely:
Make sure that the permissions on the process-to-be-launched are root:root 0400 (or writeable only by root)
Specify the absolute path to the process-to-be-launched, and don't allow any malicious modification of that path
Further Reading
AuthorizationExecuteWithPrivileges() Reference Documentation
https://github.com/cloudmatrix/esky/blob/master/esky/sudo/sudo_osx.py
https://github.com/BusKill/buskill-app/issues/14
https://www.jamf.com/blog/detecting-insecure-application-updates-on-macos/
How can i change to root user providing my password in the script?
I have this code
import os
# change to root user
# changing to root call this function
# example
os.sytem('reboot')
PS. not only this function but iptables too, so i need to change to root with out typing the password because i want to be automated.
If you don't want to do anything after the call to reboot, you can use os.exec*() to replace the current Python process with sudo, which will switch to the root user. Then, have sudo execute reboot as below.
import os
import shutil
SUDO_PATH = shutil.which('sudo')
if SUDO_PATH is None:
raise OSError('cannot find sudo executable')
os.execl(SUDO_PATH, SUDO_PATH, 'reboot')
For the cases where you wish do something after executing an external process, use subprocess.run().
RELATED: Python multiprocessing: Permission denied
I want to use Python's multiprocessing.Pool
import multiprocessing as mp
pool = mp.Pool(3)
for i in range(num_to_run):
pool.apply_async(popen_wrapper, args=(i,), callback=log_result)
I get OSError
File "/usr/local/lib/python2.6/multiprocessing/__init__.py", line 178, in RLock
return RLock()
File "/usr/local/lib/python2.6/multiprocessing/synchronize.py", line 142, in __init__
SemLock.__init__(self, RECURSIVE_MUTEX, 1, 1)
File "/usr/local/lib/python2.6/multiprocessing/synchronize.py", line 49, in __init__
sl = self._semlock = _multiprocessing.SemLock(kind, value, maxvalue)
OSError: [Errno 13] Permission denied
I read in the related question that it's due to not having r/w to /dev/shm
Besides changing the permission in /dev/shm, is there a way to run as root in the code?
I initially thought you could do something like os.umask() but it didnt work
EDIT (rephrasing the question):
let's say a username A has r/w access to directory A
You are user B and your program needs access to directory A. how do you run a program as user A?
In order from the least dangerous to the most dangerous.
You can try dropping permissions as John Zwinck suggested.
Basically you would start the program with root level permissions,
immediately do what you need to do, and then switch to a non-root
user.
From this StackOverflow.
import os, pwd, grp
def drop_privileges(uid_name='nobody', gid_name='nogroup'):
if os.getuid() != 0:
# We're not root so, like, whatever dude
return
# Get the uid/gid from the name
running_uid = pwd.getpwnam(uid_name).pw_uid
running_gid = grp.getgrnam(gid_name).gr_gid
# Remove group privileges
os.setgroups([])
# Try setting the new uid/gid
os.setgid(running_gid)
os.setuid(running_uid)
# Ensure a very conservative umask
old_umask = os.umask(077)
You could also require the credentials for the root user to be
inputed into the script, and then only use them when they are
required.
subprocess.call("sudo python RunStuffWithElevatedPrivelages.py")
#From here, the main script will continue to run without root permissions
Or if you don't want the script to prompt the user for the password you can do
subprocess.call("echo getRootCredentials() | sudo -S python RunStuffWithElevatedPrivelages.py")
Or you could just run the entire program as a root user -- sudo python myScript.py.
As far as temporarily giving users root permission to /dev/shm only when they run your script, the only thing I could think of was having some script that runs in the background under the root user that can temporarily grant anyone who uses your script root privileges to /dev/shm. This could be done through using setuid to grant such permissions and then after a certain amount of time or if the script ends the privilege is taken away. My only concern would be if there is a way a user who has temporarily been given such permissions might be able to secure more permanent privileges.
When I execute the following code as root
import os
try:
if os.getuid() == 0:
import pwd, grp
os.setgroups([])
os.setgid(grp.getgrnam('my_user').gr_gid)
os.setuid(pwd.getpwnam('my_group').pw_uid)
os.umask(077)
print 'dropped privileges successfully'
else:
print 'no need to drop privileges'
except:
print 'unable to drop privileges'
print os.system('ls -lsa ~/')
then the last statement prints ls: cannot open directory /root/: Permission denied.
The cause is clear, but the question is: What do I need to do so that ~ will expand to /home/my_user?
In this case, where I needed root privileges in order to bind to privileged ports, it turned out to be the wrong approach to start the server as root.
Using authbind turned out to be the proper solution, so that sudo python server.py ended up being authbind python server.py.
I've read that authbind seems to have some problems with ipv6, more helpful information may be found at Is there a way for non-root processes to bind to “privileged” ports (<1024) on Linux?
I have a script that needs to be run as a super user:
$ sudo ./python-script.py
Within this script, I do some other things that do not require super user privileges.
$ os.mkdir('somefolder')
What is the best/most efficient way of creating the directory as non-root user? Should I let the script make the directory as root user, and then change permissions on it?
os.mkdir does allow you to specify the permissions explicitly:
os.mkdir(path [, mode=0777])
And you also have the option of running os.chown to set the user and group
os.chown(path, uid, gid)
You can probably get the original user like this (but it might be platform specific?)
import os
original_user = os.environ.get('SUDO_USER')
original_uid = os.environ.get('SUDO_UID')
original_gid = os.environ.get('SUDO_GID')