【AI与AI安全】如何快速“hack”开源模型?

阿斯特 2026-06-23 09:59 1

提示词注入原理


简单以打比方的形式讲一下原理,如果了解大模型注入原理的读者可以不看这一节。


大家有没有参加过数学建模比赛?就是那个采集数据,然后把它们拉到一条曲线上的比赛。


实际上,AI的工作原理跟这个建模比赛的原理有些类似之处(具体的原理和运算过程我们在第一节中提过)——我们知道当前主流LLM的原理是“根据文字生文字”,在LLM的工作过程中,用户输入的每个词被当做一个向量来表示,LLM会根据我们输入的所有词来对后续词汇进行预测,直到段落输出结束。


这里就存在一个很大的问题:在LLM训练完成之后,如果我们要给LLM添加功能、设定角色,这些功能和角色也需要和用户的输入放在一起,被一并输入至LLM。而LLM无法判断哪一部分输入是系统的,哪一部分输入是用户的。


听起来是不是很耳熟?在传统安全中,以“用户输入”和“系统指令”的不清为攻击面而形成的攻击类型并非第一次出现。传统安全中一系列以注入为核心的漏洞如SQL注入和SSRF、XSS都算是其中一员。


值得注意的是,上下文机制虽然和提示词注入漏洞直接相关,但提示词注入这个漏洞本身是一个大类,手法比较多,一些本文中没有提到的手法会另行讲解。


上下文机制


在实际讲解提示词注入手法之前,我们需要先了解一下LLM的上下文机制。


首先,各位读者应该都能隐隐约约把上下文跟“历史对话”关联到一起,并且事实上,这确实是上下文机制的核心原理。


假设我们在和部署在云端的LLM对话,在每轮次对话中:


1.我们输入的内容会以文字的形式被上传到云端


2.本轮次输入会被以特定格式与历史问答内容(请把与LLM交互的过程想象为写一本永不完结的书)拼接到一起,然后输入到LLM里


那么这里就有一个很关键的问题:用户、LLM(和系统)几方的输入之间是怎么拼接的,拼接格式是什么样的?


Chat Template


说到拼接格式,就要了解chat template。大部分LLM在训练阶段都会被规定一个固定的模式规则,或者说,对于同一个LLM的训练数据必定要倾向于有一个固定的格式——如果训练者预期一个足够好的训练结果。


我们拿deepseek-r1举例。它的拼接格式可以在huggingface社区或者ollama里找到。


以下是deepseek-r1的huggingface页面:




我们可以通过查看模型的tokenizer_config.json文件以寻找template。这是它的tokenizer_config.json文件位置:




它的语法template是用Jinja2写的,如下:


{%- if not add_generation_prompt is defined -%}
{%- set add_generation_prompt = false -%}
{%- endif -%}
{%- set ns = namespace(is_first=false, is_tool=false, is_output_first=true, system_prompt="", is_first_sp=true) -%}
{%- for message in messages -%}
{%- if message["role"] == "system" -%}
{%- if ns.is_first_sp -%}
{%- set ns.system_prompt = ns.system_prompt + message["content"] -%}
{%- set ns.is_first_sp = false -%}
{%- else -%}
{%- set ns.system_prompt = ns.system_prompt + "\n\n" + message["content"] -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{{- bos_token -}}
{{- ns.system_prompt -}}
{%- for message in messages -%}
{%- if message["role"] == "user" -%}
{%- set ns.is_tool = false -%}
{{- "<|User|>" + message["content"] -}}
{%- endif -%}
{%- if message["role"] == "assistant" and "tool_calls" in message -%}
{%- set ns.is_tool = false -%}
{%- for tool in message["tool_calls"] -%}
{%- if not ns.is_first -%}
{%- if message["content"] is none -%}
{{- "<|Assistant|><|tool▁calls▁begin|><|tool▁call▁begin|>" + tool["type"] + "<|tool▁sep|>" + tool["function"]["name"] + "\n" + "```json" + "\n" + tool["function"]["arguments"] + "\n" + "```" + "<|tool▁call▁end|>" -}}
{%- else -%}
{{- "<|Assistant|>" + message["content"] + "<|tool▁calls▁begin|><|tool▁call▁begin|>" + tool["type"] + "<|tool▁sep|>" + tool["function"]["name"] + "\n" + "```json" + "\n" + tool["function"]["arguments"] + "\n" + "```" + "<|tool▁call▁end|>" -}}
{%- endif -%}
{%- set ns.is_first = true -%}
{%- else -%}
{{- "\n" + "<|tool▁call▁begin|>" + tool["type"] + "<|tool▁sep|>" + tool["function"]["name"] + "\n" + "```json" + "\n" + tool["function"]["arguments"] + "\n" + "```" + "<|tool▁call▁end|>" -}}
{%- endif -%}
{%- endfor -%}
{{- "<|tool▁calls▁end|><|end▁of▁sentence|>" -}}
{%- endif -%}
{%- if message["role"] == "assistant" and "tool_calls" not in message -%}
{%- if ns.is_tool -%}
{{- "<|tool▁outputs▁end|>" + message["content"] + "<|end▁of▁sentence|>" -}}
{%- set ns.is_tool = false -%}
{%- else -%}
{%- set content = message["content"] -%}
{%- if "</think>" in content -%}
{%- set content = content.split("</think>")[-1] -%}
{%- endif -%}
{{- "<|Assistant|>" + content + "<|end▁of▁sentence|>" -}}
{%- endif -%}
{%- endif -%}
{%- if message["role"] == "tool" -%}
{%- set ns.is_tool = true -%}
{%- if ns.is_output_first -%}
{{- "<|tool▁outputs▁begin|><|tool▁output▁begin|>" + message["content"] + "<|tool▁output▁end|>" -}}
{%- set ns.is_output_first = false -%}
{%- else -%}
{{- "<|tool▁output▁begin|>" + message["content"] + "<|tool▁output▁end|>" -}}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- if ns.is_tool -%}
{{- "<|tool▁outputs▁end|>" -}}
{%- endif -%}
{%- if add_generation_prompt and not ns.is_tool -%}
{{- "<|Assistant|><think>\n" -}}
{%- endif -%}

其中变量的值在文件上面被定义:



按照上面的模版,则一条处理完毕后的提示词示例长这样:


<|begin▁of▁sentence|>系统提示

<|User|>用户输入

<|Assistant|>
<|tool▁calls▁begin|>
<|tool▁call▁begin|>function
<|tool▁sep|>工具名称
```json
{"参数": "工具参数概括"}
```
<|tool▁call▁end|>
<|tool▁calls▁end|><|end▁of▁sentence|>

<|tool▁outputs▁begin|>
<|tool▁output▁begin|>
工具输出
<|tool▁output▁end|>
<|tool▁outputs▁end|>

<|User|>用户追问

<|Assistant|>助手回复<|end▁of▁sentence|>

利用chat template注入开源模型


由于开源模型的chat_template基本都已知,所以针对chat_template制作的POC对开源模型有额外的效果。


还是用deepseek做演示。经过测试,大多数chat template中的内容对商业部署的deepseek有效。


1 系统提示词注入


虽然deepseek中没有专门的系统提示词标签(这也导致大部分系统提示词标签类型的提示词注入对deepseek没什么用),但看chat template中可以看到规定的系统提示词在bos_token也就是<|begin▁of▁sentence|>后:



所以我们可以把payload放在这个标志后进行注入:



2 其它标签利用


这里的利用范围绝对比我写的要广,比如说——tool调用也是可以注入的。


可以用think标签伪装LLM思考过程。



这个payload我称之为天地同豆,欧耶。


同时,由于用户的对话会在上下文被压缩之前持续存在,即使LLM在第一段对话中识破了用户的意图——在后文中,用户曾使用的payload仍然会对LLM产生影响,比如这里:



最后


本文内容我在b上做过演示。文章中注入的模型是在线的deepseek(腾讯元宝/深度求索),演示中注入的是qwen(自行部署/阿里)




本系列其它文章:



前言
我一直很好奇——技术如此简单,危害如此严重,难道要出一次类似魏则西事件才有人来管吗?
当然在你行业这种事司空见惯了。
1.1 SEO
要讲GEO,我们恐怕得先从SEO说起。经营过个人博客的读者应该对SEO这个词并不陌生。简单来说,SEO是一种“搜索引擎优化技术”,它旨在通过一系列手法1.提升特定网站与特定搜索词的关联性;2.提高特定网站的搜索排名。SEO本身并不是灰产,但使用不当手法…



md格式粘贴过来可能有转义符号没消干净。格式改来改去还挺累的。
本系列不定时更新。
序言
首先,由于笔者本人也不怎么懂数学。所以在这个系列里我们不太可能进行数学上的探讨。
本系列中所有关于原理的解释,都仅致力于让读者简单理解:
①LLM中的部分原理
②AI行业的一些术语
③AI衍生的应用方向的实现方式(MCP、Skill、Agent、龙虾…)
④AI安全
所以在准确度和专业度上可能…
最新回复 (1)
  • MoreandMore 06-23 10:51
    2

    掺水的中转站是不是就这么来的,给你想看的答案,而不是正确的答案

* 帖子来源Linux.do
返回