A sandbox/supervisor for python modules.
Project description
secimport
Secure import for python modules using dtrace under the hood.
Medium Article
secimport
can be used to:
- Confine/Restrict specific python modules inside your production environment.
- Open Source, 3rd party from unstrusted sources.
- Audit the flow of your python application at user-space/os/kernel level.
- Run an entire python application under unified configuration
- Like
seccomp
but not limited to Linux kernels. Cross platform.
- Like
Quick Start
secimport
can be used out of the box in the following ways:
- Inside your code using
module = secimport.secure_import('module_name', ...)
.- Replacing the regular
import
statement withsecure_import
- Only modules that were imported with
secure_import
will be traced.
- Replacing the regular
- As a sandbox, by specifying the modules and their policies.
- Use this repository to:
- Generate a YAML policy from your code
- Compile that YAML to dscript.
- Use
dtrace
command to run your main python application, with your tailor-made sandbox.- No need for
secure_import
, you can keep using regularimport
s
- No need for
- Use this repository to:
For the full list of examples, see EXAMPLES.md.
Pickle Example
How pickle can be exploited in your 3rd party packages:
>>> import pickle
>>> class Demo:
... def __reduce__(self):
... return (eval, ("__import__('os').system('echo Exploited!')",))
...
>>> pickle.dumps(Demo())
b"\x80\x04\x95F\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x04eval\x94\x93\x94\x8c*__import__('os').system('echo Exploited!')\x94\x85\x94R\x94."
>>> pickle.loads(b"\x80\x04\x95F\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x04eval\x94\x93\x94\x8c*__import__('os').system('echo Exploited!')\x94\x85\x94R\x94.")
Exploited!
0
With secimport
, you can control such action to do whatever you want:
In [1]: import secimport
In [2]: pickle = secimport.secure_import("pickle")
In [3]: pickle.loads(b"\x80\x04\x95F\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x04eval\x94\x93\x94\x8c*__import__('os').system('echo Exploited!')\x94\x85\x94R\x94.")
[1] 28027 killed ipython
A log file is automatically created, containing everything you need to know:
$ less /tmp/.secimport/sandbox_pickle.log
@posix_spawn from /Users/avilumelsky/Downloads/Python-3.10.0/Lib/threading.py
DETECTED SHELL:
depth=8
sandboxed_depth=0
sandboxed_module=/Users/avilumelsky/Downloads/Python-3.10.0/Lib/pickle.py
TERMINATING SHELL:
libsystem_kernel.dylib`__posix_spawn+0xa
...
libsystem_kernel.dylib`__posix_spawn+0xa
libsystem_c.dylib`system+0x18b
python.exe`os_system+0xb3
KILLED
:
YAML Template Example
For a full tutorial, see YAML Profiles Usage
# An example yaml template for a sandbox.
modules:
requests:
destructive: true
syscall_allowlist:
- write
- ioctl
...
- stat64
fastapi:
destructive: true
syscall_allowlist:
- bind
- fchmod
...
- stat64
uvicorn:
destructive: true
syscall_allowlist:
- getpeername
- getpgrp
...
- stat64
Python Processing Example
Python 3.10.0 (default, May 2 2022, 21:43:20) [Clang 13.0.0 (clang-1300.0.27.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
# Let's import subprocess module, limiting it's syscall access.
>>> import secimport
>>> subprocess = secimport.secure_import("subprocess")
# Let's import os
>>> import os
>>> os.system("ps")
PID TTY TIME CMD
2022 ttys000 0:00.61 /bin/zsh -l
50092 ttys001 0:04.66 /bin/zsh -l
75860 ttys001 0:00.13 python
0
# It worked as expected, returning exit code 0.
# Now, let's try to invoke the same logic using a different module, "subprocess", that was imported using secure_import:
>>> subprocess.check_call('ps')
[1] 75860 killed python
# Damn! That's cool.
- The dtrace profile for the module is saved under:
/tmp/.secimport/sandbox_subprocess.d
:
- The log file for this module is under
/tmp/.secimport/sandbox_subprocess.log
:... (OPENING SHELL using posix_spawn): (pid 75860) (thread 344676) (user 501) (python module: <stdin>) (probe mod=, name=entry, prov=syscall func=posix_spawn) /bin/sh #posix_spawn, (TOUCHING FILESYSTEM): write(140339021606912) from thread 344676 libsystem_kernel.dylib`__fork+0xb _posixsubprocess.cpython-310-darwin.so`do_fork_exec+0x29 _posixsubprocess.cpython-310-darwin.so`subprocess_fork_exec+0x71f python.exe`cfunction_call+0x86 killing... killed.
Shell Blocking Example
# example.py - Executes code upon import;
import os;
os.system('Hello World!');
# production.py - Your production code
from secimport import secure_import
example = secure_import('example', allow_shells=False)
Let's run the and see what happens:
(root) sh-3.2# export PYTHONPATH=$(pwd)/src:$(pwd)/examples:$(pwd):$PYTHONPATH
(root) sh-3.2# python examples/production.py
Successfully compiled dtrace profile: /tmp/.secimport/sandbox_example.d
Killed: 9
- We imported
example
with limited capabilities. - If a syscall like
spawn/exec/fork/forkexec
will be executed- The process will be
kill
ed with-9
signal.
- The process will be
Network Blocking Example
>>> import requests
>>> requests.get('https://google.com')
<Response [200]>
>>> from secimport import secure_import
>>> requests = secure_import('requests', allow_networking=False)
# The next call should kill the process,
# because we disallowed networking for the requests module.
>>> requests.get('https://google.com')
[1] 86664 killed
Requirements
The only requirement is a Python interpreter that was built with --with-dtrace.
- See INSTALL.md for a detailed setup from scratch.
- pip
python3 -m pip install secimport
- Poetry
python3 -m pip install poetry && python3 -m poetry build
Tests
python -m pytest
Log4Shell as an example
Not related for python, but for the sake of explanation (Equivilant Demo soon).
- Log4Shell - CVE-2021-44228
- Let's say we want to block
log4j
from doing crazy things. - In the following import we deny
log4j
from opening an LDAP connection / shell:log4j = secure_import('log4j', allow_shells=False, allow_networking=False)
- This would disable
log4j
from opening sockets and execute commands, IN THE KERNEL. - You can choose any policy you like for any module.
- Let's say we want to block
Useful References
- Examples
- Tracing Guides
- F.A.Q
- Installation
- Mac OS Users - Disabling SIP for dtrace
- https://www.brendangregg.com/DTrace/DTrace-cheatsheet.pdf
TODO:
- ✔️ Allow/Block list configuration
- ✔️ Create a .yaml configuration per module in the code
- ✔️ Use secimport to compile that yml
- ✔️ Create a single dcript policy
- ✔️ Run an application with that policy using dtrace, without using
secure_import
- Node support (dtrace hooks)
- Go support (dtrace hooks)
- Use current_module_str together with thread ID
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for secimport-0.4.2-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | b3185b8ed300f5344bec1d30a63f855f752bb00cee81b20138a28e38cb542bc5 |
|
MD5 | 69ad7df7e253372529722e2e1862ff4b |
|
BLAKE2b-256 | 66579cd62da45b76254ed48acd9dc59595254ac4c3a7385dec51477bb427bc42 |