If Scriptform is running as root, drop privileges to 'nobody' by default. Otherwise, don't drop privileges before executing scripts.

pull/7/head
Ferry Boender 9 years ago
parent 901e0d5938
commit 03f655b6e6
  1. 20
      doc/MANUAL.md
  2. 25
      examples/run_as/README.md
  3. 21
      examples/run_as/job_run_as.py
  4. 15
      examples/run_as/run_as.json
  5. 26
      src/formconfig.py
  6. 2
      src/formdefinition.py

@ -42,6 +42,7 @@ This is the manual for version %%VERSION%%.
1. [Script execution](#script_execution)
- [Validation](#script_validation)
- [Field Values](#script_fieldvalues)
- [Execution security policy](#script_runas)
1. [Users](#users)
- [Passwords](#users_passwords)
- [Form limiting](#users_formlimit)
@ -448,6 +449,11 @@ Structurally, they are made up of the following elements:
view it, if you know its name. This is useful for other forms to
redirect to this forms and such.
- **`run_as`**: Change to this user (and its groups) before running the
script. Only works if Scriptform is running as `root`. See also
[Execution security policy](#script_runas) **Optional**, **String**,
**Default:** `nobody`.
- **`fields`**: List of fields in the form. Each field is a dictionary.
**Required**, **List of dictionaries**.
@ -871,7 +877,7 @@ out themselves.
## <a name="script_executing">Script execution</a>
## <a name="script_execution">Script execution</a>
When the user submits the form, Scriptform will validate the provided values.
If they check out, the specified script for the form will be executed.
@ -944,6 +950,18 @@ ends.
Examples of file uploads can be found in the `examples/simple` and
`examples/megacorp` directories.
### <a name="script_runas">Execution security policy</a>
Running arbitrary scripts from Scriptform poses somewhat of a security risk.
Scriptform tries to mitigate this risk by running scripts as a different user
in some cases:
* If Scriptform itelf is running as root:
- By default, scripts will be run as user 'nobody'.
- If a form specifies as `run_as` field, scripts will be executed as that user.
* If Scriptform itself is running as a non-root user, scripts will be executed
as that user.

@ -0,0 +1,25 @@
ScriptForm test example
=========================
This test example shows the usage of the `run_as` functionality. If we specify a `run_as` field in a form like so:
"forms": [
{
"name": "run_as",
"title": "Run as...",
"description": "",
"submit_title": "Run",
"run_as": "man",
"script": "job_run_as.py",
"fields": []
}
]
Scriptform will try to run the script as that user (in this case: `man`). This
requires Scriptform to be running as root.
If no `run_as` is given in a script, Scriptform will execute scripts as the
current user (the one running Scriptform). If, however, Scriptform is being run
as root and you don't specify a `run_as` user, the scripts will run as user
`nobody` for security considerations!

@ -0,0 +1,21 @@
#!/usr/bin/python
import os
import pwd
import grp
pw = pwd.getpwuid(os.getuid())
gr = grp.getgrgid(pw.pw_gid)
groups = [g.gr_gid for g in grp.getgrall() if pw.pw_name in g.gr_mem]
priv_esc = True
try:
os.seteuid(0)
except OSError:
priv_esc = False
print """Running as:
uid = {0}
gid = {1}
groups = {2}""".format(pw.pw_uid, gr.gr_gid, str(groups))

@ -0,0 +1,15 @@
{
"title": "Run as",
"forms": [
{
"name": "run_as",
"title": "Run as...",
"description": "",
"submit_title": "Run",
"run_as": "man",
"script": "/tmp/test/job_run_as.py",
"fields": [
]
}
]
}

@ -19,6 +19,7 @@ def run_as(uid, gid, groups):
os.setuid(uid)
return set_acc
class FormConfigError(Exception):
"""
Default error for FormConfig errors
@ -100,17 +101,26 @@ class FormConfig(object):
for key, value in form_values.items():
env[key] = str(value)
# Get the user uid, gid and groups we should run as
# Get the user uid, gid and groups we should run as. If the current
# user is root, we run as the given user or 'nobody' if no user was
# specified. Otherwise, we run as the user we already are.
run_as_uid = None
if os.getuid() == 0:
if form.run_as is not None:
pw = pwd.getpwnam(form.run_as)
else:
# Run as nobody
pw = pwd.getpwnam('nobody')
gr = grp.getgrgid(pw.pw_gid)
groups = [g.gr_gid for g in grp.getgrall() if pw.pw_name in g.gr_mem]
uid = pw.pw_uid
gid = pw.pw_gid
msg = "Running script as user={0}, gid={1}, groups={2}"
run_as_fn = run_as(pw.pw_uid, pw.pw_gid, groups)
self.log.info(msg.format(pw.pw_name, gr.gr_name, str(groups)))
if os.getuid() != 0:
self.log.error("Not running as root! Running as different user "
"will probably fail!")
else:
run_as_fn = None
if form.run_as is not None:
self.log.critical("Not running as root, so we can't run the script"
"as user '{0}'".format(form.run_as))
# If the form output type is 'raw', we directly stream the output to
# the browser. Otherwise we store it for later displaying.
@ -121,7 +131,7 @@ class FormConfig(object):
stderr=stderr,
env=env,
close_fds=True,
preexec_fn = run_as(uid, gid, groups))
preexec_fn=run_as_fn)
stdout, stderr = proc.communicate(input)
return proc.returncode
except OSError as err:
@ -136,7 +146,7 @@ class FormConfig(object):
stderr=subprocess.PIPE,
env=env,
close_fds=True,
preexec_fn = run_as(uid, gid, groups))
preexec_fn=run_as_fn)
stdout, stderr = proc.communicate()
return {
'stdout': stdout,

@ -19,7 +19,7 @@ class FormDefinition(object):
"""
def __init__(self, name, title, description, fields, script,
output='escaped', hidden=False, submit_title="Submit",
allowed_users=None, run_as='nobody'):
allowed_users=None, run_as=None):
self.name = name
self.title = title
self.description = description

Loading…
Cancel
Save