2

My requirement

My python server runs as a regular user on RHEL But it needs to create files/directories at places it doesn't have access to. Also needs to do chown those files with random UID/GID

My approach

Trying this in capability-only environment, no setuid. I am trying to make use of cap_chown and cap_dac_override capabilities. But am totally lost of how to get it working in systemctl kind of environment

At present I have following in the service file:

#cat /usr/lib/systemd/system/my_server.service 

[Service]
Type=simple
SecureBits=keep-caps
User=testuser
CapabilityBoundingSet=~
Capabilities=cap_dac_override,cap_chown=eip
ExecStart=/usr/bin/linux_capability_test.py

And following on the binary itself:

# getcap /usr/bin/linux_capability_test.py
/usr/bin/linux_capability_test.py = cap_chown,cap_dac_override+ei

But this here says, that it will never work on scripts: Is there a way for non-root processes to bind to "privileged" ports on Linux?

With the current setting, the capabilities I have for the running process are:

# ps -ef | grep lin
testuser    28268     1  0 22:31 ?        00:00:00 python /usr/bin/linux_capability_test.py

# getpcaps 28268
Capabilities for `28268': = cap_chown,cap_dac_override+i

But if I try to create file in /etc/ from within that script:

try:
    file_name = '/etc/junk'
    with open(file_name, 'w') as f:
        os.utime(file_name,None)

It fails with 'Permission denied'

Is that the same case for me that it won't work ? Can I use python-prctl module here to get it working ?

Community
  • 1
  • 1
mittal
  • 915
  • 10
  • 29

3 Answers3

0

setuid will not work with scripts because it is a security hole, due to the way that scripts execute. There are several documents on this. You can even start by looking at the wikipedia page.

A really good workaround is to write a small C program that will launch your Python script with hard-coded paths to python and the script. A really good discussion of all the issues may be found here

Community
  • 1
  • 1
Patrick Maupin
  • 8,024
  • 2
  • 23
  • 42
  • Thanks for your reply and the link. But I think I specifically want to avoid setuid on scripts that is why want to make use of 'Linux capabilities' alone. Which is what my question is about – mittal Aug 08 '15 at 09:28
0

Update: A method to do this, not sure if the best one. Using 'python-prctl' module:

1. Ditch 'User=testuser' from my-server.service
2. Start server as root
3. Set 'keep_caps' flag True
4. Do 'setgroups, setgid and setuid'
5. And immediately limit the permitted capability set to 'DAC_OVERRIDE' and 'CHOWN' capability only
6. Set the effective capability for both to True

Here is the code for the same

import prctl

prctl.securebits.keep_caps = True

os.setgroups([160])
os.setgid(160)
os.setuid(160)

prctl.cap_permitted.limit(prctl.CAP_CHOWN, prctl.CAP_DAC_OVERRIDE)
prctl.cap_effective.dac_override = True
prctl.cap_effective.chown = True`

DONE !!

mittal
  • 915
  • 10
  • 29
  • So are you running your entire server with CAP_CHOWN now? – Patrick Maupin Aug 08 '15 at 12:37
  • Yes, that is a caveat here. But I think my original attempt to set Capabilities in the .service file etc was also trying to do the same, set capabilities on entire server. You mentioned in your comment **write a small C program that will launch your Python script with hard-coded paths to python and the script.** I didn't understand that part. In the link which you mentioned, which section should I look for an example ? – mittal Aug 08 '15 at 13:27
  • 1
    There is a section that starts "The answer to this is to use a setuid binary to execute the script." But if you hand a random script chown capability, all it has to do is modify and edit a couple of files in /etc, and then it can execute whatever it wants as part of a cron job. You want a new user for your chown, and you want it very isolated. See, for example [this answer](http://stackoverflow.com/questions/31272030/linux-changing-file-ownership-without-a-copy/31748737#31748737). – Patrick Maupin Aug 08 '15 at 13:34
  • Ok, that section. So in my case the binary, with setuid 0, if starts the python server and the server is anyways again doing a setuid(regular_user), it will again loose the capability, right ? I totally understand the security concern and have raised the same with our folks ;) The second link which you posted is exactly what I had in mind as alternate approach. In my case the server(also REST server) may need to overwrite/modify/delete files owned by any user and also chown them to any other. How do I give capability to an individual user ? – mittal Aug 08 '15 at 13:52
  • 1
    Depending on which version of linux you have, you [may not be able to](http://stackoverflow.com/questions/1956732/is-it-possible-to-configure-linux-capabilities-per-user) give capability to users directly. But you can certainly add the capabilities you want to the permitted and inheritable capabilities of the small C program. If you can do everything you need in the small C program, you may not need SETUID. If you need to invoke a script from the program, you should certainly isolate the script by putting it and the C program under control of the separate user. – Patrick Maupin Aug 08 '15 at 14:27
  • **you can certainly add the capabilities you want to the permitted and inheritable capabilities of the small C program. If you can do everything you need in the small C program, you may not need SETUID** The C program will basically do copy/move/delete and chown operations only. To achieve this, how should I specify the capabilities ? On the program itself, within the program, before calling the program from server ? Sorry, if I am asking for too much info – mittal Aug 08 '15 at 15:26
  • 1
    Use the setcap program on the executable. – Patrick Maupin Aug 08 '15 at 15:37
0

Based upon our discussion above, I did the following:

[Service]
Type=simple
User=testuser
SecureBits=keep-caps
Capabilities=cap_chown,cap_dac_override=i
ExecStart=/usr/bin/linux_capability_test.py

This starts the server with both those capabilities as inheritable.

Wrote a small C, test code to chown file

#include <unistd.h>

int main()
  {
    int ret = 0;

    ret = chown("/etc/junk", 160, 160);

    return ret;
  }

Set following on the gcc'ed binary

chown testuser:testuser /usr/bin/chown_c
chmod 550 /usr/bin/chown_c
setcap cap_chown,cap_dac_override=ie /usr/bin/chown_c

The server does following to call the binary

import prctl
prctl.cap_inheritable.chown = True
prctl.cap_inheritable.dac_override = True
execve('/usr/bin/chown_c',[],os.environ)

And I was able to get the desired result

# ll /etc/junk 
-rw-r--r-- 1 root root 0 Aug  8 22:33 /etc/junk

# python capability_client.py 

# ll /etc/junk 
-rw-r--r-- 1 testuser testuser 0 Aug  8 22:33 /etc/junk
mittal
  • 915
  • 10
  • 29