I am adding functionality to a PyQt5 application. This new functionality involves copying, linking and removing files (and links) that may be in protected directories, so commands like os.symlink or shutil.copyfile would fail.
Of course the main application is not run with root privileges (and asking users to do so is out of question), so I need a workaround.
First thing is of course to wrap the critical code in try/except blocks and investigate any exceptions. If it turns out missing root privileges are the issue I would ask for the password in a dialog (presumably storing the password for as long as the current dialog is alive).
But I'm not sure how I can repeat the step with the root password. I would strongly prefer doing it in the Python domain (or does Qt provide some support for file operations? I'd bet it does but I couldn't find it). I think it should be possible by doing the file operations in a shell command and somehow pass the password to that, but since Python and PyQt were designed to shield the programmer from the intricacies of OS differences I would prefer to avoid that route.
Some pseudocode should give a clear idea of the question:
def my_copy(source, dest):
try:
os.path.symlink(source, dest)
except: # check for permission problem:
# use dialog to ask for password
# repeat the symlink procedure with password
What you're trying to do here is basically impossible on most modern operating systems, and for good reason.
Imagine a typical macOS or Windows user who expects to be able to auth by using the fingerprint reader instead of typing their password. Or a blind user. Or someone who's justifiably paranoid about your app storing their password in plaintext in a Python string in non-kernel memory (not to mention in a Windows dialog box whose events can be hooked by any process). This is why modern platforms come with a framework like libPAM/XSSO, Authorization Services. etc.
And the way privilege escalation works is so different between Windows and POSIX, or even between macOS and Linux, not to mention so rapidly evolving, that, as far as I know, there's no cross-platform framework to do it.
In fact, most systems discourage app-driven privilege escalation in the first place. On Windows, you often ask the OS to run a helper app with elevated privileges (and the OS will then apply the appropriate policy and decide what to ask for). On macOS, you usually write a LaunchServices daemon that you get permission for at install time (using special installer APIs) rather than runtime. For traditional non-server-y POSIX apps, you'd usually do something similar, but with a setuid helper that you can create at install time just because the installation runs as root. For traditional POSIX servers, you often start as root then drop privs after forking and execing a similar helper daemon.
If all of this seems like way more work than you wanted to deal with… well, honestly, that was probably the intention. OS designers don't want app designers introducing security holes, so they make sure you have to understand what the platform wants and how to work with it rather than against it before you can even try to do things like moving around files you don't have permissions for.
Wrap the try/except code in a loop with a counter:
def my_copy(source, dest):
for attempts in [1, 2]:
try:
os.path.symlink(source, dest)
# we succeeded, so don't try any more
break
except: # check for permission problem:
if attempts == 1:
# use dialog to ask for password
# repeat the symlink procedure with password
else:
# we already tried as root, and failed again
break
Related
I wrote an automation program which logs in to a website to perform a certain task. The .py file will be given to a number of users but I don't want them to be able to read the code and see the password used for logging in. How do I make sure that they can only execute the file but not read it?
To lock a Python script so it cannot be edited, you compile the PY file into a PYC file. This ensures that the users are unable to make any changes to the existing code within the scripts. A PYC file is binary in nature and cannot be read directly. Below is code that compiles a PY file into a PYC file:
import py_compile
script = "C:\\temp\\myscript.py"
py_compile.compile(script)
That code would make myscript.pyc. PYC files run even if the PY files are not present.
Source : https://support.esri.com/en/technical-article/000010321
You can't do it. If you give a password to your users, no matter how much you try to hide it, it's always possible to find it out.
You can make it slightly more difficult to find out with encryption and obfuscation, but that only stops non-tech-savvy users. And those users probably wouldn't think to read through a bunch of code looking for a plaintext password anyways.
The correct way is to make it so that it's OK if users know their own passwords. Make the server side bit block people from doing things they're not supposed to do (if you don't have one, you need to make one). Use separate accounts for each user so you can separate deactivate them if needed.
Include this in your code:
import py_compile
if not '.pyc' in __file__:
TheNewPathGoesHear=__file__.replace('file.py', 'file.pyc')
py_compile.compile(__file__, TheNewPathGoesHear)
this if statment ensures that this is not a pyc before compiling, if you compile with out this if statment thy will be a new file for the uses each time thy open the pyc file.
NOTE: there must be a '.pyc' in the format of the new file path, you can not to define the new path and that will send it to the __pycache__ folder in your main folder.
One possibility is to have a daemon (service) running which holds the password. That would be running under a restricted user to which normal security has been applied. The users should not be able to access anything under the daemon's user.
Users have a python program which communicates a login request to the daemon via an IPC mechanism, you could use a socket, named-pipe, etc. The daemon performs the task on behalf of the user and communicates the results back.
How practical that is depends on the amount of communication between the user and the server. There would be performance issues if this was an interactive task.
The daemon would probably have to be multi-threaded, depending on volumes, so this could be a lot of work.
A similar possibility is that the daemon could be a web server (using, say, Apache), and then the users access using a browser. That could be easier to program and maintain, but it depends on your environment if that is feasible.
Best way to do that would be as #cdarke offered, but a faster way would be to store the .py file in a hidden, password-protected folder.
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.
This answer, stating that the naming of classes in Python is not done because of special privileges, here confuses me.
How can I access lower rings in Python?
Is the low-level io for accessing lower level rings?
If it is, which rings I can access with that?
Is the statement "This function is intended for low-level I/O." referring to lower level rings or to something else?
C tends to be prominent language in os -programming. When there is the OS -class in Python, does it mean that I can access C -code through that class?
Suppose I am playing with bizarre machine-language code and I want to somehow understand what it means. Are there some tools in Python which I can use to analyze such things? If there is not, is there some way that I could still use Python to control some tool which controls the bizarre machine language? [ctypes suggested in comments]
If Python has nothing to do with the low-level privileged stuff, do it still offers some wrappers to control the privileged?
Windows and Linux both use ring 0 for kernel code and ring 3 for user processes. The advantage of this is that user processes can be isolated from one another, so the system continues to run even if a process crashes. By contrast, a bug in ring 0 code can potentially crash the entire machine.
One of the reasons ring 0 code is so critical is that it can access hardware directly. By contrast, when a user-mode (ring 3) process needs to read some data from a disk:
the process executes a special instruction telling the CPU it wants to make a system call
CPU switches to ring 0 and starts executing kernel code
kernel checks that the process is allowed to perform the operation
if permitted, the operation is carried out
kernel tells the CPU it has finished
CPU switches back to ring 3 and returns control to the process
Processes belonging to "privileged" users (e.g. root/Administrator) run in ring 3 just like any other user-mode code; the only difference is that the check at step 3 always succeeds. This is a good thing because:
root-owned processes can crash without taking the entire system down
many user-mode features are unavailable in the kernel, e.g. swappable memory, private address space
As for running Python code in lower rings - kernel-mode is a very different environment, and the Python interpreter simply isn't designed to run in it, e.g. the procedure for allocating memory is completely different.
In the other question you reference, both os.open() and open() end up making the open() system call, which checks whether the process is allowed to open the corresponding file and performs the actual operation.
I think SimonJ's answer is very good, but I'm going to post my own because from your comments it appears you're not quite understanding things.
Firstly, when you boot an operating system, what you're doing is loading the kernel into memory and saying "start executing at address X". The kernel, that code, is essentially just a program, but of course nothing else is loaded, so if it wants to do anything it has to know the exact commands for the specific hardware it has attached to it.
You don't have to run a kernel. If you know how to control all the attached hardware, you don't need one, in fact. However, it was rapidly realised way back when that there are many types of hardware one might face and having an identical interface across systems to program against would make code portable and generally help get things done faster.
So the function of the kernel, then, is to control all the hardware attached to the system and present it in a common interface, called an API (application programming interface). Code for programs that run on the system don't talk directly to hardware. They talk to the kernel. So user land programs don't need to know how to ask a specific hard disk to read sector 0x213E or whatever, but the kernel does.
Now, the description of ring 3 provided in SimonJ's answer is how userland is implemented - with isolated, unprivileged processes with virtual private address spaces that cannot interfere with each other, for the benefits he describes.
There's also another level of complexity in here, namely the concept of permissions. Most operating systems have some form of access control, whereby "administrators" have total control of the system and "users" have a restricted subset of options. So a kernel request to open a file belonging to an administrator should fail under this sort of approach. The user who runs the program forms part of the program's context, if you like, and what the program can do is constrained by what that user can do.
Most of what you could ever want to achieve (unless your intention is to write a kernel) can be done in userland as the root/administrator user, where the kernel does not deny any API requests made to it. It's still a userland program. It's still a ring 3 program. But for most (nearly all) uses it is sufficient. A lot can be achieved as a non-root/administrative user.
That applies to the python interpreter and by extension all python code running on that interpreter.
Let's deal with some uncertainties:
The naming of os and sys I think is because these are "systems" tasks (as opposed to say urllib2). They give you ways to manipulate and open files, for example. However, these go through the python interpreter which in turn makes a call to the kernel.
I do not know of any kernel-mode python implementations. Therefore to my knowledge there is no way to write code in python that will run in the kernel (linux/windows).
There are two types of privileged: privileged in terms of hardware access and privileged in terms of the access control system provided by the kernel. Python can be run as root/an administrator (indeed on Linux many of the administration gui tools are written in python), so in a sense it can access privileged code.
Writing a C extension or controlling a C application to Python would ostensibly mean you are either using code added to the interpreter (userland) or controlling another userland application. However, if you wrote a kernel module in C (Linux) or a Driver in C (Windows) it would be possible to load that driver and interact with it via the kernel APIs from python. An example might be creating a /proc entry in C and then having your python application pass messages via read/write to that /proc entry (which the kernel module would have to handle via a write/read handler. Essentially, you write the code you want to run in kernel space and basically add/extend the kernel API in one of many ways so that your program can interact with that code.
"Low-level" IO means having more control over the type of IO that takes place and how you get that data from the operating system. It is low level compared to higher level functions still in Python that give you easier ways to read files (convenience at the cost of control). It is comparable to the difference between read() calls and fread() or fscanf() in C.
Health warning: Writing kernel modules, if you get it wrong, will at best result in that module not being properly loaded; at worst your system will panic/bluescreen and you'll have to reboot.
The final point about machine instructions I cannot answer here. It's a totally separate question and it depends. There are many tools capable of analysing code like that I'm sure, but I'm not a reverse engineer. However, I do know that many of these tools (gdb, valgrind) e.g. tools that hook into binary code do not need kernel modules to do their work.
You can use inpout library http://logix4u.net/parallel-port/index.php
import ctypes
#Example of strobing data out with nStrobe pin (note - inverted)
#Get 50kbaud without the read, 30kbaud with
read = []
for n in range(4):
ctypes.windll.inpout32.Out32(0x37a, 1)
ctypes.windll.inpout32.Out32(0x378, n)
read.append(ctypes.windll.inpout32.Inp32(0x378)) #Dummy read to see what is going on
ctypes.windll.inpout32.Out32(0x37a, 0)
print read
[note: I was wrong. usermode code can no longer access ring 0 on modern unix systems. -- jc 2019-01-17]
I've forgotten what little I ever knew about Windows privileges. In all Unix systems with which I'm familiar, the root user can access all ring0 privileges. But I can't think of any mapping of Python modules with privilege rings.
That is, the 'os' and 'sys' modules don't give you any special privileges. You have them, or not, due to your login credentials.
How can I access lower rings in Python?
ctypes
Is the low-level io for accessing lower level rings?
No.
Is the statement "This function is intended for low-level I/O." referring to lower level rings or to something else?
Something else.
C tends to be prominent language in os -programming. When there is the OS -class in Python, does it mean that I can access C -code through that class?
All of CPython is implemented in C.
The os module (it's not a class, it's a module) is for accessing OS API's. C has nothing to do with access to OS API's. Python accesses the API's "directly".
Suppose I am playing with bizarre machine-language code and I want to somehow understand what it means. Are there some tools in Python which I can use to analyze such things?
"playing with"?
"understand what it means"? is your problem. You read the code, you understand it. Whether or not Python can help is impossible to say. What don't you understand?
If there is not, is there some way that I could still use Python to control some tool which controls the bizarre machine language? [ctypes suggested in comments]
ctypes
If Python has nothing to do with the low-level privileged stuff, do it still offers some wrappers to control the privileged?
You don't "wrap" things to control privileges.
Most OS's work like this.
You grant privileges to a user account.
The OS API's check the privileges granted to the user making the OS API request.
If the user has the privileges, the OS API works.
If the user lacks the privileges, the OS API raises an exception.
That's all there is to it.