Python依赖项沙盒化:保护代码安全的实用指南

本文详细介绍了如何使用secimport工具为Python依赖项创建沙盒环境,防止第三方包执行恶意操作。通过实际代码示例展示如何限制网络访问和shell执行,确保生产环境安全。

在代码中沙盒化Python依赖项

运行来自不受信任来源的代码仍然是一个未解决的问题,特别是在Python和JavaScript这样的动态语言中。

我将从两个未解决的问题开始:

  • 如果你导入requests用于HTTP请求,为什么requests应该能够打开终端并切换到sudo?
  • 如果你导入logging,为什么它应该能够进行网络访问(或像Log4Shell中的LDAP),而你只需要将文件写入特定目录?

这就是我如何为Python导入编写沙盒的故事:创建生产就绪的解决方案并针对不同用例进行测试。

简要说明

解决方案如下所示。GitHub链接在底部。

第三方包中pickle如何被利用?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> 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

使用secimport,你可以控制此类操作:

1
2
3
4
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

为什么需要沙盒?

向代码添加新库可能具有挑战性,原因如下:

  • 开发人员无法确切知道包需要什么
  • 你导入它,但包内部可以在你不知情的情况下对你的环境执行任何操作
  • 很难确定运行该库所需的最低要求

我们应该允许哪组系统调用,使其正常运行,仅此而已?

我们信任开源社区。但我们使用的包的维护者是个人。当版本没有用==锁定时存在很大风险;我们的包在CI中静默更新,新代码在这些包内运行而我们不知道。

每天都有有人匿名向PyPI上传恶意wheel。有时这是你已经使用的包。

当前解决方案

今天的解决方案主要是带外和每个进程的。

如果给定模块包含漏洞、恶意逻辑或为非常小的任务提供大型代码库,我们必须以某种方式限制它。

主机(计算机)应该不受你的应用程序或其第三方应用程序的影响。

当前可用的方法

  1. 为整个应用程序创建安全计算(seccomp)配置文件

    • 如果你的程序以意外方式行为,无论是由于第三方库还是你自己的代码,它将被记录或进程将立即被杀死
    • 例如:RedHat、SE-Linux、SECCOMP
  2. 在CI/CD或IDE中进行静态代码分析/安全扫描

    • 你查找过时的包或分析代码
    • 例如:Snyk、CheckMarx、Clair
  3. 完全不使用开源或第三方软件

    • 这在规模上不可行
  4. WASI沙盒化

    • WebAssembly很棒!但与Rust/Go/.Net不同,Python不编译为WebAssembly
  5. 在VM或Hypervisor中运行软件

    • Google开发了名为gVisor的容器沙盒
    • gVisor是一种VM,可以翻译应用程序中的每个系统调用

约束Python模块的方法

我没有为应用程序使用统一的配置文件,而是希望使开发人员能够在导入/编译时使用给定范围限制代码中的任何包。

就像SELinux在执行时在Linux内核中为进程授予白名单范围一样,我希望使开发人员能够在生产环境中在特定约束下控制代码中的任何包。

限制Python模块 - MVP

我想要一个可以记录每个Python调用和每个系统调用的工具。

在此级别进行跟踪/沙盒化通常会影响运行时性能。

可以使用以下技术实现此类工具:

  • eBPF
  • DTrace
  • 任何其他.*trace工具

我选择使用DTrace,因为它:

  • 在Mac和Windows上工作
  • 具有破坏性,可以从监视Python进程的dscript探针杀死进程

DTrace实现

  1. 运行Python进程
  2. 在后台运行dtrace进程
  3. 运行任何你想要覆盖的内容
  4. dtrace输出

在这个例子中,我使用"dtrace -n"将钩子传递给dtrace。

我将此dtrace命令扩展为dscript,这是一种存储这些钩子并编程这些探针以执行我们想要的操作的方法。

我编写了一个dscript程序(脚本文件),当特定Python模块调用spawn系统调用时杀死进程。

我使用dscript语言中的关联数组高效地实现了它。

我为脚本中想要的变量实现了一个Python包装器,并为dtrace文件内容创建了一个模板。

然后,我编写了"secimport"!

secimport示例

secimport是一个Python包,可用于:

  • 在生产环境中限制特定的Python模块
  • 审核Python应用程序在用户空间/操作系统/内核级别的流程
  • 在统一配置下运行整个Python应用程序

网络示例

1
2
3
4
5
6
7
8
9
>>> import requests
>>> requests.get('https://google.com')
<Response [200]>  
>>> from secimport import secure_import
>>> requests = secure_import('requests', allow_networking=False)
# 下一个调用应该杀死进程,
# 因为我们不允许requests模块进行网络访问
>>> requests.get('https://google.com')
[1]    86664 killed

Shell示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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.
# 让我们导入subprocess模块
>>> import secimport
>>> subprocess = secimport.secure_import("subprocess", allow_shells=False)
# 让我们导入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
# 按预期工作,返回退出代码0
# 现在,让我们尝试使用不同的模块"subprocess"调用相同的逻辑,
# 该模块是使用"secure_import"导入的:
>>> subprocess.check_call('ps')
[1]    75860 killed     python

该模块的dtrace配置文件保存在:/tmp/.secimport/sandbox_subprocess.d

日志文件:/tmp/.secimport/sandbox_subprocess.log

结论

安全社区似乎需要一个能够限制代码中特定模块同时将其保持在同一个进程中的沙盒。

我提出了一种处理代码库中第三方代码的方法。

源代码: https://github.com/avilum/secimport

示例: https://github.com/avilum/secimport/blob/master/docs/EXAMPLES.md

如果我能够为像Python这样的动态语言实现这一点,我相信社区将能够在几行代码中实现其他语言的工具。

第二部分:使用eBPF保护PyTorch模型

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计