Execute Python Script as Root (seteuid vs c-wrapper) - python

I have a quick one off task in a python script that I'd like to call from Django (www user), that's going to need to root privileges.
At first I thought I would could use Python's os.seteuid() and set the setuid bit on the script, but then I realized that I would have to set the setuid bit on Python itself, which I assume is big no no. From what I can tell, this would also be the case if using sudo, which I really would like to avoid.
At this point, I'm considering just writing a C wrapper the uses seteuid and calls my python script as root, passing the necessary arguments to it.
Is this the correct thing to do or should I be looking at something else?

sudo does not require setuid bit on Python. You can enable sudo for one command only, no arguments:
www ALL=(ALL) NOPASSWD: /root/bin/reload-stuff.py ""
This would be secure if your script does not take any arguments, cannot be overridden by www user, and sudo does "env_reset" (the default in most distros).
You can accept arguments, but be very careful with them -- do not take output filenames, make sure you verify all inputs. In this case, remove "" from the end of sudo line.

The correct thing is called privilege separation: clearly identify minimal set of tasks which have to be done on elevated privileges. Write a separate daemon and an as much limited as possible way of communicating the task to do. Run this daemon as another user with elevated privileges. A bit more work, but also more secure.
EDIT: using a setuid-able wrapper will also satisfy the concept of privilege separation, although I recommend having the web server chrooted and mounting the chrooted file system nosuid (which would defeat that).

sudo allows you to limit arguments passed to the program. From man sudoers:
john ALPHA = /usr/bin/su [!-]*, !/usr/bin/su *root*
On the ALPHA machines, user john may su to anyone except root but
he is not allowed to specify any options to the su(1) command.
So use sudo. Of course you need to be extra careful with root access – make sure only root can modify the script itself and any parent directories, and that the script is safe and only does the absolute minimum that needs to be run as root.

Related

Run python as root using cap_sys_admin capability linux

I have the following capability of python2.7 (using getcap):
/usr/bin/python2.7 = cap_sys_admin+ep
I know that CAP_SYS_ADMIN is "CAP_SYS_ADMIN is a highly privileged access level and should generally be avoided as it is equivalent to root access level." (https://docs.bridgecrew.io/docs/bc_k8s_36)
It's mean that I can run as root using /usr/bin/python2.7?
So how actually I can get example root bash using /usr/bin/python2.7?
Setting cap_sys_admin on /usr/bin/python means that python will be able to make any 'root' system call. This means that any one who runs python has the power to do things that normally only root can do. If someone (maliciously or accidentally) tries to do something dangwerous, they won't be stopped. Something as simple as the following could be catastrophic:
import shutil
shutil.rmtree('/')
In most cases if you need a script to run with elevated privileges it is better to use a user account that has these permissions, using su or sudo, rather than making a command always run without limits. That way you can focus on 'who' is allowed to do thingsm rather than 'what'.

Is using os.system('curl ...') in Python truly unsafe, compared to native libraries?

I'm writing a project with Angular on the frontend, and a backend written in Python.
For certain API calls, instead of using Python's built-in libraries (and since I am more of a C/C++/bash programmer) and did not know how to do a similar normal system call in python, I just did a os.system('curl ...').
When people were reviewing my code they said that it would be better practice to use Python's built in libraries instead of doing a system() call since it looks better and could be less dangerous.
Is this a legitimate criticism? How could it be more dangerous if the python library is probably doing the same thing anyway?
I am not asking for opinions on style but legitimate problems with this method.
The objection is entirely legitimate.
Let's say that your command looks like:
def post_result(result_string):
os.system('curl http://example.com/report-result/%s' % (result_string,))
Now, what happens if you're told to report a result that contains ; rm -rf ~? The shell invoked by os.system() runs curl http://example.com/report-result/, and then it runs a second command of rm -rf ~.
Several naive attempts at fixes don't work.
Consider, for example:
# Adding double quotes should work, right?
# WRONG: ''; rm -rf ~'' doesn't work here, but $(rm -rf ~) still does.
os.system('curl http://example.com/report-result/"%s"' % (result_string,))
# ...so, how about single quotes?
# STILL WRONG: $(rm -rf ~) doesn't work on its own, but result_string="'$(rm -rf ~)'" does.
os.system("curl http://example.com/report-result/'%s'" % (result_string,))
Even if you avoid direct shell injection vulnerabilities, using a shell exposes you to other kinds of bugs.
At startup time, a shell does a number of operations based on filesystem contents and environment variables. If an untrusted user can manipulate your program into setting environment variables of their choice before calling os.system(), they can cause a file named in ENV to have its commands executed; can shadow commands with exported functions, or can cause other mischief. See ShellShock for a well-publicized historical example.
And that's before considering other things that can happen to your data. If you're passing a filename to a shell, but unknown to you it contains whitespace and glob characters, that filename can be split into / replaced with other names.
The official Python documentation warns against shell use.
Quoting a warning from the Python subprocess module documentation, which is also relevant here:
Warning: Executing shell commands that incorporate unsanitized input from an untrusted source makes a program vulnerable to shell injection, a serious security flaw which can result in arbitrary command execution. For this reason, the use of shell=True is strongly discouraged in cases where the command string is constructed from external input:
>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / #
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...
shell=False disables all shell based features, but does not suffer from this vulnerability; see the Note in the Popen constructor documentation for helpful hints in getting shell=False to work.
When using shell=True, pipes.quote() can be used to properly escape whitespace and shell metacharacters in strings that are going to be used to construct shell commands.
os.system() has all the same faults as subprocess.Popen(..., shell=True) -- even more faults, since subprocess.Popen() provides a way to pass data out-of-band from code, and so can be used safely.
Native Python libraries don't call shells for work that the Python runtime can do.
Python has a socket library in its standard library interface, which directly invokes operating system and libc calls to create network connections and interact with them. There is no shell involved in these syscalls; arguments are C structs, C strings, etc; so they aren't prone to shell injection vulnerabilities in the same way that os.system() is.
Some Python libraries, like libcurl, may be slightly less native insofar as they use their own C libraries rather than only calling out to the operating system through functions included in the Python runtime itself; even then, though, these OS-level syscalls are at a much lower level than any shell.
This answer is entirely correct.
But I'd also like to point out to other cases in which you might think that security doesn't matter. E.g. the command you are running is hard-coded or you have 100% control or trust over what is supplied to it.
Even it that case using os.system() is wrong.
In fact:
You have to rely on external tools that might not be present or, even worse, you might have a command with that name, but it doesn't do what you expect it to do. Maybe because it has a different version or maybe because it's a different implementation of that command (e.g. GNUtar != BSDtar). Manging python dependencies will be much more easy and reliable.
It is more difficult to handle errors. You only have a return code which is not always enough to understand what is going on. And I hope that your solution to this problem isn't to parse the command output.
Environment variables can modify the way a program works unexpectedly. Many programs use environment variables as an alternative to command line or configuration options. If your python script relies on specific behavior from the command, an unexpected variable in the user's environment can break it.
If at some point in the future you will need to let the user customize a bit your script behavior you will need to rewrite it from scratch without os.system() or you might have security problems.

How to open a "root" file without typing your password every time?

I read this very interesting post, which is very close to what I need but not completely, they use it for shell scripting while I need the same solution mainly for a built-in Python function.
Long story short:
open("/dev/input/event3", "rb")
This doesn't work out of the box because in order to open event3 I need to type my password every single time I execute my Python script, due to elevated privileges. What can I do so I don't have to type my password every time nor write my password in plane text in my script? I like the solution offered in the post I linked above, but this doesn't work because that would mean the -sort of speak- handle I get on this open file will be in another scope/python script.
Any solutions?
Thanks
EDIT
I tried modifying the privileges of my entire Python script, so that I don't need to type a password, but that didn't work neither. What I tried:
1) modify access rights
sudo chown root:root /home/username/myscript.py
sudo chmod 700 /home/username/myscript.py
2) modify visudo
myusername ALL=(ALL) NOPASSWD: /home/username/myscript.py
3) trying to execute my python script now fails, altough it is clearly there
myusername$ ./myscript
bash: ./myscript: No such file or directory
It occurs to me that you may be approaching this problem backward. Currently, you're asking how you can elevate the permissions of a python script without entering a password every time when you should be asking, "why do I need to enter a password at all?"
As long as the file isn't a security concern both the script and the file in question should be owned by non-root users in the input group. The user who owns the python script can then execute it without root privileges to access the file which doesn't require them.
Read more about setuid. It is tricky (so I won't even try to explain it here), and is the basis of authentication related programs like sudo, su, login etc. See also setreuid(2), setuid(2), execve(2), credentials(7), chmod(1).
A good Unix programming book (such as ALP, or something newer) should explain setuid in terms of system calls (listed in syscalls(2)).
setuid executables cannot be scripts (with shebang); they should be binary ELF executables (see elf(5)). However, you could write some setuid wrapper program in C (or most other compiled languages, e.g. Rust, Ocaml, C++, Go, ...) which runs your Python script. Be careful, since a mistake could open a huge security hole. But with such a setuid executable, you won't have to type any password.
You could also have some specific user or group owning the /dev/input/event3 (so configure appropriately your system for that, thru udev or systemd...) and have a setuid or setgid program.
BTW, you could configure sudo (see sudoers(5) and this) to avoid typing any password. Of course, that weakens the security of your entire system (but the choice is yours).
You can pass the sudo password in the same line with the command, using this syntax:
echo password | sudo -S your_command
This way you won't be prompted for sudo password before the command can execute - sounds like that's what you are looking for.

Open a file as superuser in python

I have to open a system file and read from it. This file is usually only readable by root (the super user). I have a way to ask the user for the superuser password. I would like to use this credentials to open the file and read from it without having my entire program running as a superuser process. Is there a way to achieve this in a multiplatform way?
Since privileges work completely differently on Unix-like systems and Windows, you're going to need to have platform-specific code. In any case, you'll need to break up your program into two separate programs, one of which runs with elevated permissions and the other of which runs with standard/reduced permissions.
In Unix-like systems (including Linux and Mac OS X), the executable that runs with elevated permissions should do this:
Assume you're running as root and open the file for reading. Since you mentioned that the file is very large, you don't actually read the whole file in, you just keep an open file descriptor. If opening it fails, print an error message and exit.
Use setreuid(2) and setregid(2) to set your user ID and group ID back to an unprivileged user.
Use one of the exec(3) functions to execute the unprivileged executable.
If you want to make it so that you can run this program without using sudo, then make it owned by root and make it a set-user-ID executable with chown root the-program; chmod +s the-program.
The unprivileged program will now be run with normal permissions, but when it starts up, it will have an open file descriptor (file descriptor #3) that can be used to read from your special file.
For Windows, it's similar but slightly different:
Assume you're running as root and open the file for reading using CreateFile. Do not use default security attributes -- create a SECURITY_ATTRIBUTES structure with bInheritHandle set to TRUE so that the handle will be inherited by child processes. If opening the file failed, print an error message and exit.
Use CreateProcess to launch your child process. Pass in the handle above on the command line (e.g. printed as a numerical value); you could also use a shared memory region, but that's more trouble than it's worth for this problem.
Embed a manifest in this executable with requireAdministrator set to true. After you do this, when you run the program, you'll get a UAC prompt asking you if you want to allow the program to makes changes.
The child process then does grabs the inherited handle by parsing the command line, and it can then read in the data as it pleases.
One problem with this approach is that when you inherit a handle, you have to use the low-level system calls (read(2) on Unix, ReadFile on Windows) to read from it -- you can't use higher-level functions like C's fread(3) or C++'s iostreams (ok, Unix has fdopen(3), but there's no equivalent on Windows as far as I'm aware).
As I'm sure you've noticed by now, everything above has been in C. In Unix, this translates pretty straightforwardly into Python, since the os module has lots of goodies like setreuid, exec*, and fdopen. On Windows, you might be able to do some of this stuff with the ctypes module and/or Pywin32, but it's probably easier to stick with C.
What you're looking for is called privilege escalation, and it very much depends on the platform you're running on. In general, what your program would have to do is run a portion as the superuser. On unix systems, for instance, you might be able to use sudo to read the contents of the file.
But as mentioned, this really depends on what system you're running on.
I would split the program in two.
Handles opening the file and accessing the contents. It can assume it's started with the privileges it needs.
Everything else that doesn't require special privileges.
Put a config entry which describes how to exec or subprocess the command that requires extra privileges. ie.
access_special_file: sudo access_special_file
or
access_special_file: runas /user:AccountWithPrivs access_special_file
This offloads some of the system specifics for privilege escalation to the system shell where there may be more convenient ways of gaining the permissions you need.
On linux it's a cynch, as #ViktorKerkez showed. This is how I streamed my WiFi passwords files (readable only by root/sudo):
import subprocess
import sys
# substitute your Windoze/DOS/PowerlessShell command here:
cat_wifi_pws = 'sudo cat /etc/NetworkManager/system-connections/*'
process = subprocess.Popen(cat_wifi_pws, stdout=subprocess.PIPE, shell=True)
# read one line at a time, as it becomes available
for line in iter(process.stdout.readline, ''):
sys.stdout.write(line)
Of course this will prompt you for your sudo password. And you can use gksudo if you're on a system that has it and you prefer dialog boxes. As a bonus, if you have a decent timeout_default in /etc/sudoers, and you've recently run sudo in the same shell where you launched the python interpreter, you won't have to enter a password at all.

when we need use sudo python xxx.py or just python xxx.py or xxx.py

I have write a website,what confused me is when i run the website,first i need start the the app,
so there are 3 ways:
sudo python xxx.py
python xxx.py
xxx.py
I didn't clear with how to use each of them,the NO.3 method currently in my computer dosen't work well
sudo will run the application with superuser permissions. Considering that you're referring to a website, this is certainly not what you want to do. (For a webapp, if it requires superuser permissions, it's broken. That's far, far too big of a security risk to consider actually using.)
Under other circumstances you might have a python program that does some sort of system maintaince and requires being run as root. In this case, you'd use sudo, but you would never want to do this for something that's publicly accessible and could potentially be exploited. In fact, for anything other than testing, you should probably run the webapp as a separate user with very limited access (e.g. with their shell set to /dev/null, no read or write access to anything that they don't need, etc...).
The other two are effectively identical (in therms of what they do), but the last option (executing the script directly) will require:
the executable bit to be set (on
unix-y systems) (e.g. chmod +x whatever.py)
a shebang on the first line(e.g. #!
/usr/bin/python) pointing to the
python execuctable that you want to
run things with (again, this only applies to unix-y systems)
Calling python to run the code (python whatever.py) and following the steps above (resulting in a script that you can call directly with whatever.py)
do exactly the same thing (assuming that the shebang in the python file points to the same python executable as "python" does, anyway...)

Categories