A sandbox/supervisor for python modules.
Project description
secimport
An easy way to constrain python modules in your code using backends like bpftrace (eBPF) and dtrace.
Medium Article
secimport
can be used to:
- Confine/Restrict specific python modules inside your production environment.
- Restrict 3rd party or open source modules in your code.
- Audit the flow of your python application at user-space/os/kernel level.
- Kill the process upon violoation of a profile.
Quick Start
secimport
can be used out of the box in the following ways:
- Modify your imports
- 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
- Inside your code using
- As a sandbox that runs your main code.
- Generate a YAML policy from your code, by specifying the modules and the policy you want for each module you use.
- Convert that YAML policy to dscript/bpftrace sandbox code.
- Use
dtrace
orebpf
to run your main python application, with your tailor-made sandbox.- No need for
secure_import
, you can keep using regularimport
s and not change your code at all.
- No need for
For the full list of examples, see EXAMPLES.md.
Docker
A working environment is not easy to create.
The easiest way to evaluate secimport, is by using our Docker for MacOS and Linux that includes secimport, bpftrace backend and eBPF libraries.
dtrace backend is not available in docker, and can be tried directly on the compatible hosts (MacOS, Solaris, Unix, some Linux distributions)
Use Cases
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 Policy 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
Blocking New Processes 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.
When using secure_import, the following files are created:
- The dtrace/bpftrace sandbox code for the module is saved under:
/tmp/.secimport/sandbox_subprocess.d
- when using dtrace
/tmp/.secimport/sandbox_subprocess.bt
:- when using bpftrace
- 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 (System Intergity Protection)
- https://www.brendangregg.com/DTrace/DTrace-cheatsheet.pdf
- https://www.brendangregg.com/blog/2018-10-08/dtrace-for-linux-2018.html
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
- ✔️ Add eBPF basic support using bpftrace
- ✔️ bpftrace backend tests
- Extandible Language Template
- Increase extandability for new languages tracing with bpftace/dtrace.
- Adding a new integration will be easy, in a single directory, using templates for filters, actions, etc.
- Increase extandability for new languages tracing with bpftace/dtrace.
- Node support (bpftrace/dtrace hooks)
- Implement a template for Node's call stack and event loop
- Go support (bpftrace/dtrace hooks)
- Implement a template for golang's call stack
- Multi Process support: Use current_module_str together with thread ID to distinguish between events in different processes
- Update all linux syscalls in the templates (filesystem, networking, processing) to improve the sandbox blocking of unknowns.
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.5.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 08d374402f1d5c13facdc49deb8e711a1b2357a2f52c037a33dcc47fd3798704 |
|
MD5 | 8add3c63ee728cbbdb8c69b26d97b7c2 |
|
BLAKE2b-256 | dc88708842746360d22c486bf2d649f9be40001aa41a5c2fe7b2988e2ef5fdd4 |