Skip to main content

A sandbox/supervisor for python modules.

Project description

secimport

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.

Quick Start

secimport can be used out of the box in the following ways:

  1. Inside your code using module = secimport.secure_import('module_name', ...).
    • Replacing the regular import statement with secure_import
    • Only modules that were imported with secure_import will be traced.
  2. 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 regular imports

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 killed with -9 signal.

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.

Useful References

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

secimport-0.4.3.tar.gz (17.4 kB view hashes)

Uploaded Source

Built Distribution

secimport-0.4.3-py3-none-any.whl (20.9 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page