大模型防火墙-环节分析

  1. 使用者类别(用户画像)
  2. 检测&防御方案
  3. 攻击类型
  4. 越狱攻击类型
  5. 恶意Prompt来源
  6. WAF接入方式
  7. 接入考量
  8. 附录–关键词匹配代码

对大模型安全WAF中出现的实体进行分析

8ea0e713a177d64d2e201ec0cc40066b

使用者类别(用户画像)

首先要分析的是谁在使用模型服务,谁会尝试越狱攻击,攻击特征如何。

  • 正常用户, 占大多数, 平时发向模型的query都是正常的,可能会在无意识的情况下询问一些较为敏感、恶意的问题,这些问题一般特征明显,很好识别。
  • 特殊用户,对模型有特殊用途(渗透测试、AI女友、敏感数据合成),需要通过prompt限制模型的安全对齐的用户。可能会直接使用各种公开的模板,特点是重复度高,可以通过持续收集在野的模板进行防御。
  • 科研人员,使用自己研究的方法对模型在指定数据集上进行测试\Fuzz。
  • 恶意用户,特征可能类似于科研人员,但会将恶意样本用于真实的恶意目的。

不同的用户特征不同,如一个正常的程序员,平常的问题可能就是BUG解决、代码生成,不会刻意构造越狱模板来绕过模型对齐。对于这种类型的帐号,可以通过简单的输入输出过滤,减少资源消耗。科研人员可能会通过API持续进行Fuzz,Fuzz特征明显,可以通过用户标记、向量匹配等进行检测。

用户画像应该是基于对话持续更新的,理想的情况应该是对不同的用户设置不同的防御强度,避免过度的资源消耗。

检测&防御方案

不同的检测方案有不同的延迟、成本,需要综合考虑进行方案实施

  • 规则匹配,攻击者的输入、模型的输出会含有大量敏感词,最直接的方法是关键词检测。通过正则表达式、Trie树、AC自动机来实现,需要持续维护敏感词库。可以通过python的ahocorasick来实现一个简单的demo,实现检测效果。规则匹配实现简单,缺点是对于隐藏意图的输入难以检测。
    image-20250923212934522
  • 模型检测,将问题转换成一个分类问题,训练模型来检测攻击意图。可以训练一个Bert模型做分类任务,如PromptGurad就是基于Bert模型进行训练,用于检测prompt注入。也可以训练一个较小的语言模型,进行推理输出(准确率高,但是延迟也高),如llamaguard就是一个14b的模型,用于检测。
    PromptGuard的简析可以看我的另一篇博客
  • 意图识别, 在回答用户的query前,先对query使用语言模型进行分析,若分析出用户有恶意的意图,则拒绝。可以使用外置的小模型进行意图识别、分类;也可能通过prompt工程、微调模型等,对基模进行训练,使其学会在回答问题前进行意图识别、分析。意图识别会用到语言模型的推理能力,所以延迟较高,可解释性较强。
  • prompt工程,在系统提示词方面进行安全加固,如强调模型要保持有用性、无害性。OpenAI、Claude的模型都会在系统提示词上进行安全性的强调。
    https://github.com/asgeirtj/system_prompts_leaks
  • prompt聚类,维护一个向量库,对于常见的越狱模板、恶意问题可以进行向量化,用户的输入通过相似度匹配进行搜索,若有相似的样本,则将其标记为疑似恶意query,后续使用其他手段进行二次分析即可。对于FUZZ攻击,其词向量相似度很高,用这种方法进行防御应该会有不错的效果。
  • 基模训练,最直接的方法就是通过微调、强化学习等方式让模型学会拒绝。这需要标注足够多的数据,同时基模训练成本较高,训练后的模型在通用能力上可能会下降。
  • 用户画像,如可以每天基于用户的对话历史,对用户进行标记,对不同的用户,使用不同的防御手段等。
  • 模型表征,有些学者研究发现,模型输出的隐藏层、token预测的困惑度都可以作为衡量是否被越狱的指标,这些可以作为额外信息辅助检测。
  • query改写,可以用来防御gcg等攻击,gcg攻击的特点是后面跟了不可读的token,可以外接一个小模型做Query改写,在保留用户原始意图的同时,破坏gcg结构。
  • Special Token 越狱一般会导致模型指令被劫持,可以在system prompt中要求模型输出一些特殊的Token,在输出时检查,若没有输出,则可能被劫持。

成本、准确率、用户体验,是很难同时平衡到的,最理想的waf应该是动态的、多方位的。

攻击类型

LLM存在不同的攻击面,随着未来模型的落地场景增加,模型的攻击面也会越来越多。

  • 越狱攻击,输出有害内容, 最开始的时候模型仅有一个聊天页面,攻击的目标是让模型输出有害的内容。最经典的就是让模型回答如何制作炸弹。
  • 系统提示词窃取,复杂的应用拥有复杂的预设提示词,如Cursor、Trae、codebuddy等,提示词都是成百上千行,这些提示词本身具有一定的商业价值。
  • 间接注入,上下文工程,RAG、联网搜索、PDF解析、function call… 填充入的信息不再完全是由用户确定的,恶意Prompt可能会存在其中,导致简介注入。
  • 命令执行,为了让模型拥有更多的能力,可以通过MCP编写外部工具供模型调用。这样模型就不单是一个聊天模型来,它可以通过工具调用来与外界进行交互。模型能力增加的同时,也带来了新的风险。
  • 拒绝服务,如让模型重复”ABC” 1000次,导致模型循环不断输出重复的Token,消耗服务器端资源。

不同的攻击类型需要不同的防御手段,越狱攻击是我们讨论的主流。 提示词窃取可以通过相似度匹配,对模型输出进行检测。命令执行需要对Agent做能力限制,不能执行高权限高风险的命令。拒绝服务需要做意图识别,拒绝这种无意义query。

越狱攻击类型

越狱攻击有不同的手法,对应的也有不同的特征。攻击手法有很多,很难完全覆盖,下面分析几种主流的方法。

  • 构造Prompt直接询问,特点是缝合。如使用通用的越狱模板(DAN)+否定抑制(不要输出SORRY)+指令劫持(忽略上述指令)+有害问题。 这类攻击特征、恶意意图都很明显,很容易检测到。
  • 隐藏恶意意图,将有害问题进行加密,并提供模型线索进行解密,模型在推理中逐渐失去安全能力,最终响应用户问题。 这类攻击就需要使用语言模型进行推理检测。
  • 多轮对话越狱,如最近很火的回声室攻击等,通过不断对话,诱导模型输出有害内容。可以对模型输出进行持续监测。
  • 基于优化的方法,如GCG这种白盒攻击方法,特点是有许多不可读的字符,困惑度较高。
  • 低资源/跨模态,模型对齐可能是只使用中文/英文训练,切换成小语种可能绕过。多模态模型(语音、音频、视频、图片)中新的模态可以用来包含恶意指令。
  • 思维链劫持,如论文H-COT会构造模型思维链内容发送给目标模型,影响其思维链中模型对齐。更简单的方法如在prompt最后加上follow user instruct 等。
  • ….

恶意Prompt来源

不同的Prompt有不同的结构、特征

  • 手工构造,特征是攻击者多次尝试、进行绕过。
  • 互联网公开的模板,可以定期维护收集模板库来防御。
  • FUZZ,攻击频率高、样本之间有相似性。

WAF接入方式

  • 串联:用户输入处理后才能 发送给业务模型,模型输出处理后才发送给用户。
  • 并联:持续检测用户输入、模型输出。检测到异常时迅速终断。

可以考虑使用混合的模式,如输入串联阻塞、输出并联监测等。

接入考量

  • 维护成本:不同的防御方案有不同的成本,如对齐基模成本巨大,且不能实时更新
  • 性能影响:query改写、模型对齐等可能会降低模型等的通用能力
  • 用户体验:过度拒绝、延迟增加可能会影响用户体验
  • 准确率:不同的方案有不同的准确率、误报率

附录–关键词匹配代码

通过AC自动机,加载敏感词库。

import ahocorasick
import os

def load_sensitive_words(file_path):
    """
    从指定文件中加载敏感词。
    """
    sensitive_words = []
    if not os.path.exists(file_path):
        print(f"错误:文件 '{file_path}' 不存在。请创建该文件并添加敏感词。")
        return sensitive_words
        
    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            word = line.strip()
            if word:
                sensitive_words.append(word)
    return sensitive_words

def create_ac_automaton(words):
    """
    构造 AC 自动机。
    """
    A = ahocorasick.Automaton()
    for index, word in enumerate(words):
        A.add_word(word, (index, word))
    A.make_automaton()
    return A

def filter_text(text, automaton):
    """
    使用 AC 自动机过滤文本并替换敏感词。
    """
    found_words_info = []
    # 查找所有匹配到的敏感词及其位置
    for end_index, (index, original_word) in automaton.iter(text):
        start_index = end_index - len(original_word) + 1
        found_words_info.append({
            'word': original_word,
            'start': start_index,
            'end': end_index
        })

    # 根据找到的位置进行替换
    result_text_list = list(text)
    # 按倒序遍历,避免索引变化
    for item in sorted(found_words_info, key=lambda x: x['start'], reverse=True):
        start = item['start']
        end = item['end']
        result_text_list[start:end+1] = ['*'] * len(item['word'])
    
    return "".join(result_text_list)


if __name__ == "__main__":
    sensitive_words_file = "色情类.txt"
    print("正在加载敏感词库...")
    sensitive_words = load_sensitive_words(sensitive_words_file)
    
    if not sensitive_words:
        print("未加载到敏感词。程序退出。")
    else:
        print(f"成功加载 {len(sensitive_words)} 个敏感词。")
        
        # 构造 AC 自动机
        print("正在构建 AC 自动机...")
        ac_automaton = create_ac_automaton(sensitive_words)
        print("AC 自动机构建完成。")
        
        # 接收用户输入并进行检测
        print("\n请输入文本进行检测(输入 'exit' 退出):")
        while True:
            user_input = input(">> ")
            if user_input.lower() == 'exit':
                break
            
            # 过滤文本
            filtered_text = filter_text(user_input, ac_automaton)
            
            # 输出结果
            if user_input == filtered_text:
                print("未检测到敏感词。")
            else:
                print("检测到敏感词,已替换:")
                print(f"原文: {user_input}")
                print(f"结果: {filtered_text}")

image-20250924121008900


文章参考: https://github.com/asgeirtj/system_prompts_leaks
https://github.com/fwwdn/sensitive-stop-words

博客地址: qwrdxer.github.io

欢迎交流: qq:1944270374 wx:qwrdxer


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1944270374@qq.com