I'm trying to use setfsuid() with python 2.5.4 and RHEL 5.4.
Since it's not included in the os module, I wrapped it in a C module of my own and installed it as a python extension module using distutils.
However when I try to use it I don't get the expected result.
setfsuid() returns value indicating success (changing from a superuser), but I can't access files to which only the newly set user should have user access (using open()), indicating that fsuid was not truely changed.
I tried to verify setfsuid() worked, by running it consecutively twice with the same user input
The result was as if nothing had changed, and on every call the returned value was of old user id different from the new one. I also called getpid() from the module, and from the python script, both returned the same id. so this is not the problem.
Just in case it's significant, I should note that I'm doing all of this from within an Apache daemon process (WSGI).
Anyone can provide an explanation to that?
Thank you
The ability to change the FSUID is limited to either root or non-root processes with the CAP_SETFCAP capability. These days it's usually considered bad practice to run a webserver with root permissions so, most likely, you'll need to set the capability on the file server (see man capabilities for details). Please note that doing this could severly affect your overall system's security. I'd recommend considering spawning a small backend process that runs as root and converses with your WSGI app via a local UNIX socket prior to mucking with the security of a high-profile target like Apache.
Related
Here is the scenario, my website has some unsafe code, which is generated by website users, to run on my server.
I want to disable some reserved words for python to protect my running environment, such as eval, exec, print and so on.
Is there a simple way (without changing the python interpreter, my python version is 2.7.10) to implement the feature I described before?
Many thanks.
Disabling names on python level won't help as there are numerous ways around it. See this and this post for more info. This is what you need to do:
For CPython, use RestrictedPython to define a restricted subset of Python.
For PyPy, use sandboxing. It allows you to run arbitrary python code in a special environment that serializes all input/output so you can check it and decide which commands are allowed before actually running them.
Since version 3.8 Python supports audit hooks so you can completely prevent certain actions:
import sys
def audit(event, args):
if event == 'compile':
sys.exit('nice try!')
sys.addaudithook(audit)
eval('5')
Additionally, to protect your host OS, use
either virtualization (safer) such as KVM or VirtualBox
or containerization (much lighter) such as lxd or docker
In the case of containerization with docker you may need to add AppArmor or SELinux policies for extra safety. lxd already comes with AppArmor policies by default.
Make sure you run the code as a user with as little privileges as possible.
Rebuild the virtual machine/container for each user.
Whichever solution you use, don't forget to limit resource usage (RAM, CPU, storage, network). Use cgroups if your chosen virtualization/containerization solution does not support these kinds of limits.
Last but not least, use timeouts to prevent your users' code from running forever.
One way is to shadow the methods:
def not_available(*args, **kwargs):
return 'Not allowed'
eval = not_available
exec = not_available
print = not_available
However, someone smart can always do this:
import builtins
builtins.print('this works!')
So the real solution is to parse the code and not allow the input if it has such statements (rather than trying to disable them).
I have a python script that is launched as root, I can't change it.
I would like to know if it's possible to exectute certain lines of this script (or all the script) as normal user (I don't need to be root to run this).
The reason is, I use notifications, and python-notify don't work in all machines in root (looks like this bug)
So ,do you know if it's possible to change it, with a subprocess, or other?
Thanks
I would like to know if it's possible to exectute certain lines of this script (or all the script) as normal user
Yes, it's possible—and a good idea.
Python's os module has a group of functions to set the real, effective, and saved user and group id, starting with setegid. What exactly each of these does is up to your platform, as far as Python is concerned; it's just calling the C functions of the same names.
But POSIX defines what those functions do. See setuid and seteuid for details, but the short version is:
If you want to switch to a normal user and then switch back, use either seteuid or setreuid, to set just effective, or real and effective, but not saved UID. Then use the same function again to set them back to root.
If you want to run the whole script as a normal user and make sure you can't get root back, use setresuid instead, to set all three.
If you're using Python 3.1 and earlier, you don't have all of these functions. You can still use seteuid to switch effective ID back and forth, but setuid will… well, it depends on your platform, but I think most modern platforms will change saved as well as real, meaning you can't get root back. If you read the linked POSIX doc, there are a bunch of caveats and complexities in the POSIX documentation. If you only care about one platform, you probably want to read your local manpages instead, rather than reading about all of the cases and then trying to figure out which one covers your platform.
So ,do you know if it's possible to change it, with a subprocess, or other?
That isn't necessary (at least on a conforming POSIX system), but it can make things easier or safer. You can use subprocess, multiprocessing, os.fork, or any other mechanism to launch a child process, which immediately uses setuid to drop privileges—or even setresuid to give up the ability to ever restore its privilege. When that child process is done with its task, it just exits.
you need getpwnam from PWD module , for access user-id by pass username and then with os.setuid() you can change the user and Run python script as another user .
import pwd, os
uid = pwd.getpwnam('username')[2] #instead of index 2 you can use pw_uid Attribute
os.setuid(uid)
But Note that using setuid can make a enormous security hole .
If the script is running as root, you can use os.setuid to change the process's current UID to that of another user (irrevocably) or os.seteuid to change the process's current effective UID (and you can use it again afterwards to reset the EUID to root).
Note that os.setuid changes both the real and effective UID - this is the reason it is irrevocable.
os.seteuid changes the effective UID. Since the real UID will still be root, you can still switch back the EUID to root later on in the script.
I have a drive already mapped to a designated letter, 'R:\'. If I run the python script to access this space while logged on or with the computer unlocked it works fine. The problem occurs when I set task scheduler to run the script early in the morning before I come in. Basically I stay logged in and lock the machine, but at some point it looks like my network drive mappings time out (but reconnect when I unlock the machine in the morning) and this is why the script isn't able to find them.
The error comes when trying to do an os.path.exists() to check for folders on this drive and create them if they don't already exist. From a 'try/except' loop I get the exception "The system cannot find the path specified: 'R:\'.
My question: is there a way to force a refresh through python? I have seen other postings about mapping network drives...but not sure if this applies to my case since I already have the drive mapped. The letter it uses needs to stay the same as different applications have absolute references to it. Wondering if mapping the same drive will cause problems or not work, but also not wanting to temporarily map to another letter with a script and unmap when done...seems like an inefficient way to do this?
Using python 2.6 (what another program requires).
Thanks,
The best solution would be to refer to the 'drive' by its UNC pathname, i.e. a path of the form \\hostname\sharename, but, unfortunately, Python's base library has very poor support for dealing with UNC paths.
Option #1 would be to find a Python extension module you can install to get better support for UNC paths. Try googling for "python unc".
Option #2 would be to use the Python subprocess module to execute net use commands, and parse the resulting output. e.g. from a DOS prompt, running net use will output something like this...
Status Local Remote Network
-------------------------------------------------------------------------------
OK R: \\hostname\sharename Microsoft Windows Network
...which you can use to tell if the drive is already mapped, and if not, you can just execute net use R: \\hostname\sharename to map it. It's possible that calling net use with no paramaters is actually sufficient to 'refresh' the mapping if it has 'timed out', but I'm not sure.
Option #3 would be to investigate using the Python ctypes module to call the underlying Windows libraries directly to emulate the functionality of calling net use.
My solution to this problem was to just use the IP address for the referenced machine. Worked like a charm and no problems with mapped drives...thanks for the responses.
I have a script. It uses GTK. And I need to know if another copy of scrip starts. If it starts window will extend.
Please, tell me the way I can detect it.
You could use a D-Bus service. Your script would start a new service if none is found running in the current session, and otherwise send a D-Bus message to the running instace (that can send "anything", including strings, lists, dicts).
The GTK-based library libunique (missing Python bindings?) uses this approach in its implementation of "unique" applications.
You can use a PID file to determine if the application is already running (just search for "python daemon" on Google to find some working implementations).
If you detected that the program is already running, you can communicate with the running instance using named pipes.
The new copy could search for running copies, fire a SIGUSER signal and trigger a callback in your running process that then handles all the magic.
See the signal library for details and the list of things that can go wrong.
I've done that using several ways depending upon the scenario
In one case my script had to listen on a TCP port. So I'd just see if the port was available it'd mean it is a new copy. This was sufficient for me but in certain cases, if the port is already in use, it might be because some other kind of application is listening on that port. You can use OS calls to find out who is listening on the port or try sending data and checking the response.
In another case I used PID file. Just decide a location and a filename, and everytime your script starts, read that file to get a PID. If that PID is running, it means another copy is already there. Otherwise create that file and write your process ID in it. This is pretty simple. If you are using django then you can simply use django's daemonizer: "from django.utils import daemonize". Otherwise you can use this script: http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
I need to have a Python CGI script do some stuff (a little bit of security checking), and then end up calling a Perl CGI script, passing anything it received (e.g., POST info) onto the Perl script.
For background, my reason for doing this is that I'm trying to integrate Swish searching with Mailman list archives.
Swish searching uses swish.cgi, a Perl script, but because these are private list archives I can't just allow people to call swish.cgi directly as recommended on this page: http://wpkg.org/Integrating_Mailman_with_a_Swish-e_search_engine#Mailman_configuration
I believe what I need to do is have the Mailman "private" cgi-bin file (written in Python) do its regular security checking (which calls a few Mailman/python modules) and THEN call on swish.cgi to do the search (after having verified that the user is on the mailing list).
Essentially, I believe the simplest solution would just be to protect access to the swish.cgi Perl script with a variant of the standard mailman cgi-bin/private Python script.
(I considered the idea that people could search with a non-protected swish.cgi, and people wouldn't be able to view the full results because those posts are already password-protected by default Mailman setup... but the problem is that even showing the Swish post excerpts in the search results could expose confidential information, so I must restrict access to even the search itself to just subscribers.)
If someone has a better idea of how to solve the overall problem without doing the Python-CGI-calls-Perl-CGI I'll be happy to consider that the "answer".
Just know that my goal is to make little (ideally no) changes to the standard Mailman installation. Copying the "private" cgi-bin script (whose source is mailman-2.1.12/Mailman/Cgi/private.py) and making changes to call swish.cgi is cool, but modifying the existing private cgi-bin script wouldn't really be cool.
Here's what I did to test the answer (using os.execv to replace the python script with the perl script, so that the perl script will inherit the python script's environment):
I created a pythontest script with:
import os
os.environ['FOO'] = 'BAR'
mydir = os.path.dirname(os.environ.get('SCRIPT_FILENAME'))
childprog = mydir + '/perltest'
childargs = []
os.execv(childprog, childargs)
Then a perltest script with:
print "Content-type: text/html\n\n";
while (($key,$value) = each %ENV) {
print "<p>$key=$value</p>\n";
}
Then I called http://myserver.com/cgi-bin/pythontest and saw that the environment printout included the custom FOO variable so the child perltest process had successfully inherited all the environment variables.
I'm just going to state the obvious here because I don't have any detailed knowledge about your specific environment.
If your python script is a genuine CGI and not a mod_python script or similar then it is just a regular process spawned to handle the one request. You can use os.execv to replace it with another process (e.g. the perl CGI) and the new process will inherit the current process' environment, stdin, stdout and stderr. This assumes that you don't need to read stdin for your security checks. It may also depend on whether your CGI is running in a restricted environment. execv is potentially dangerous and might be blocked in such an environment.
If you're running from a mod_python environment or if you need to peek at posted data (i.e. stdin) then the execv approach isn't available to you. You have two main alternatives.
You could run the perl CGI directly (e.g. look at the subprocess module) handing it a correct environment and feeding it the correct data to its stdin. You can the spool the returned data from its stdout raw (or cooked if needed) directly back to the web server.
Otherwise, you could make a local web request to run the CGI. This is likely to require a bit less knowledge about the server setup, but a bit more work in the python CGI to make and handle the HTTP request.