2024年11月28日,DataCon2024大数据安全分析竞赛落下帷幕。比赛共吸引706支战队、1556人报名参与。在历经多日的激烈角逐后,最终诞生了五大赛道的冠军、亚军和季军。来自武汉大学的“0817iotg”战队荣获漏洞分析赛道冠军,本期为大家分享“0817iotg”战队的解题报告。
[篇幅较长,建议点赞收藏]
1.1 题目背景
在进行漏洞挖掘工作时,对特定目标的历史漏洞挖掘经验的学习是至关重要的一步。然而,传统的搜索引擎在面对海量数据时往往显得效率低下,难以快速有效地获取所需的关键信息。近年来,随着人工智能大模型技术的发展及其在自然语言处理方面的显著进步,利用大模型从海量漏洞分析文章中提取关键知识已经成为一种可行的方法。本挑战要求选手利用大模型技术,对漏洞分析文章进行高效梳理,从中提取出有价值的摘要信息,任务包括但不限于:文献整理、文本预处理、关键信息提取、摘要生成、结果验证等。
1.2 实验/项目环境
-
操作系统:Ubuntu 22.04 / Windows 10
-
开发语言:Python 3.10
-
第三方库:langchain、BeautifulSoup4、paddle
1.3 技术实现的核心思路
-
使用BeautifulSoup4解析html文件,去除图片等无用信息。
-
运用提示工程方法生成精细化提示,拆分输出维度,使用两次大模型调用分别判定不同输出维度。
-
运用投票模型检查结果,减少大模型输出内容的不确定性,并且去除不合理结果。
-
扩展框架功能与识别范围,使其能够识别版本、修复建议;提取POC/EXP代码;以及支持图片内容识别。
1.4 技术实现的具体步骤
1.4.1 步骤1:环境配置
python或python虚拟环境运行:使用pip安装langchain、langchain_openai、BeautifulSoup4、paddle(仅被用于读取HTML中图片信息),配置test.py中的data_dir、answer_dir、key_env和base_env,然后使用python调用test.py运行
使用docker运行:使用下述代码导入镜像,挂载数据集目录与输出目录,提供API_key并运行镜像
1.4.2 步骤2:html文件信息提取
在提取html文件信息内容时,我们主要关注以下几点:(1)如何完整提取文本的具体内容与相关代码;(2)在提取的过程中尽可能筛除无关信息,来防止提示词超出token上限;(3)如果超出了8000词的token上限,该如何处理;(4)如何在不丢失完整文本语义的情况下,处理长文本内容。接下来是我们解决方案的详细介绍:
简单的文件信息提取:通过BeautifulSoup库来进行html文件内容的解析
在解析之后,我们主要关注html中更具价值的<p>、<code>等标签的提取,而html文件中的无关内容,如<script>、<style>、<noscript>、<header>、<footer>等标签,则进行排除。接下来通过soup.get_text()来获取html文件中的主要文本内容,经过这一步后,获取到的文本已经能够完整的包含题目所要求的具体信息。
超过token上限的处理: 在题目所给的大语言模型api中,单次提问仅支持8k token的上限,而提示词的设计往往便需要占据将近2k个token,因此,如果简单把上一步提取的文本和提示词进行拼接,再喂给大模型,很容易超出token上限,从而导致运行报错,因此需要进一步对文本进行处理,这里我们主要实现了三种思路:
-
利用 tiktoken库根据token数截取:在超过8k token的上限后,截掉超过8000的内容,由于一个token并没有对应的字符数,因此很难通过字符串长度来判定,这里我们具体通过tiktoken库来实现,通过分词器来对传入的提示内容进行统计,并按照`token`划分标准进行切分,最后统计前8k个token来作为完整的提示内容调用大模型api,但是这样处理存在的问题在于,程序每次运行前,均需要联网下载编码库cl100k_base,从而在后续能够根据编码标准进行具体的token划分。然而,本题最终需要在不联网的环境下运行,因此我们后来发现上述思路并不适用。
-
自适应截取:在前一种思路不可行的前提下,我们选择设定字符串长度上限,来防止token数超过提示上限,同时初步设置字符串上限长度为18000,但是这样在处理存在大量反汇编代码等占用token数过多的案例时,仍然不能完全覆盖,因此我们最终设计了函数check_output_regex,根据提问后的报错输出,来调整字符串长度,在每次案例提问中,设置一个初始长度上限18000,后续每一次报错,长度上限减2000,直至不再报错。
-
文本与代码分离进行分标签提取:为了保证文本内容的完整性,以及分析的有效性,我们同样设计了将文本标签<p>和代码标签<code>分开提取,同时将原先文本中的内容用一个占位标识符来替代,这样可以大模型的记忆功能,先只提供具体文本内容给大模型分析,并提示大模型,如果分析需要,请告诉我需要的代码片段编号,然后我们再根据编号返回特定的代码片段。
1.4.3 步骤3:运用提示工程的大模型情报提取
输出维度拆分:如数据说明所述,此部分共需利用大模型提取7个维度(除去文件名)的信息,分别是:
-
文件名、漏洞编号、厂商或产品名、编程语言、是否有漏洞成因分析、危险函数名、是否有POC/EXP、是否有POC/EXP解释
由于单次分析过于复杂的任务时,大模型的分析能力可能会降低,我们对上述输出维度进行了拆分。经过分析发现,漏洞成因部分需要通过分析相应代码及漏洞危险函数获得,同时漏洞危险函数确定则相应的编程语言也随之确定。而是否存在POC/EXP以及是否有POC/EXP解释两个维度也相应高度绑定且与前面漏洞语言等维度并无直接关联。因此决定7个维度划分成两部分:
-
文件名、漏洞编号、厂商或产品名、编程语言、是否有漏洞成因分析、危险函数名
-
是否有POC/EXP、是否有POC/EXP解释
提示词设计原则:设计提示词时,我们主要考虑了以下原则
-
分步设计:将分析规则拆分为不同步骤,确保分析流程清晰,每个关键点都被充分覆盖。
-
优先级规则:针对单一输出维度设置优先级规则,确保在多种可能情况下,始终提取最相关的内容。
-
提供具体案例:面对`是否存在解释`类型的任务时,大模型总倾向于认为解释是存在的,我们需要给定更加精细化的案例,告诉模型什么程度的解释才是足够的。
-
临时输出维度:针对某些复杂规则,增加一些临时输出维度辅助判断。
-
规范化输出:提供结构化的 JSON 输出,便于脚本进一步处理。
下面依次对这两部分的提示工程模块进行分析。
1.4.3.1 第一部分提示词撰写
需要分析的内容包含以下字段:
1.Vulnerability ID:漏洞编号(CVE ID)。
2.Vendor or Product Name:供应商或产品名称。
3.Programming Language:使用的编程语言。
4.Backtrace Lanuage:调用栈中的编程语言(可选)。
5.Is Cause Analysis:是否包含漏洞原因分析。
6.Dangerous Function:危险函数。
有的文章中不提供危险函数,但是提供了POC代码和stack trace调用栈信息。此时,大模型倾向于认为POC代码语言是漏洞触发点语言;还有一些危险函数表明是C语言函数,但是调用栈中却存在C++特征;为了规避这些误区,让大模型检测出存在漏洞的代码的实际编程语言,我们在此处多定义了一项Backtrace Lanuage字段,用于分析调用栈中的编程语言信息。
接下来,我们要求大模型必须按照我们设计的步骤一步一步进行分析,否则模型可能在单词分析中“囫囵吞枣”,漏掉某些步骤与信息,然后是提供给大模型的具体判断规则:
分析文章内容中包含的漏洞编号(Vulnerability ID):1、如果有多个 CVE ID,选择与上下文最相关的。2、如果没有明确的 CVE ID,输出 NULL。以及判断相关性时,需关注 CVE ID 所在段落与漏洞描述是否一致。
确定文章内容中漏洞的供应商或产品名称(Vendor or Product Name):1、从 vender_list 中匹配。2、若有多个供应商,选择最靠近 CVE ID 信息的供应商。3、如果找不到供应商,输出 NULL。4、根据出题规则在大模型返回运行结果后进行进一步处理,对于vender_list中存在依赖关系的项目,优先选择涵盖范围更大的厂商名或者原文中有直接引用的厂商名。
确定文章内容中漏洞的编程语言(Programming Language):1、从 language_list 中匹配。2、如果涉及多个语言,选择最接近漏洞触发点或危险函数的语言。3、IoT 产品的默认语言为 "C"。
确定文章内容中漏洞的调用栈语言(可选)(Bactracek Lanuage):1、如果调用栈包含 C++ 特征(如 :: 或 .cpp 文件名),输出 C++。2、默认值为 `NULL`,表示没有后端。
判断文章内容中是否存在危险函数(Dangerous Function):1、优先考虑代码中直接提到的危险函数(如 strcpy, memmove)。2、如果没有直接提到,尝试寻找相关信息。3、若无危险函数信息,输出 NULL。
判断文章内容中是否有漏洞原因分析(Is Cause Analysis):1、如果文章描述了靠近危险函数的代码,输出 TRUE。2、如果仅有攻击代码(如 PoC),没有漏洞成因点相关描述,输出 FALSE。
1.4.3.2 第二部分提示词撰写
在开始分析之前先对大模型的输出进行相关定义,需要分析的内容包含以下字段:
1. Is Related:是否与漏洞挖掘相关。
2. POC/EXP Presence:是否包含漏洞概念验证代码或漏洞利用代码。
3. POC/EXP Explanation:是否包含独立的漏洞代码解释。
这里添加了一个Is Related维度,对应题目中 “若文章与漏洞挖掘无关或者不涉及具体的漏洞,则全部为NULL”这一要求,如果Is Related为FALSE,则所有维度全部置NULL。增加维度是由于我们发现如果在其他输出维度中增加这一要求,不同规则的要求将会互相干扰竞争,造成非预期输出效果。然后与前述步骤相似地,要求大模型,按照我们设计的步骤一步一步进行分析。
然后是提供给大模型的具体判断规则:
分析文章内容是否与漏洞挖掘相关 (Is Related):判断文章是否与漏洞挖掘相关。如果文章未涉及漏洞或漏洞挖掘技术,后续关于 POC/EXP 的分析将失去意义。
-
相关:如果文章讨论了某个具体漏洞(例如,提到 CVE ID),或者探讨了漏洞的发现、利用或缓解的技术,则返回TRUE。
-
不相关:如果文章仅讨论网络安全的其他内容(如病毒传播)而未涉及具体漏洞或漏洞挖掘,则返回FALSE。
检查是否包含 POC/EXP (POC/EXP Presence):
-
包含:如果文章包含功能性代码段、指令或命令,且这些内容能够演示如何利用漏洞、执行攻击或导致系统崩溃,则返回TRUE。
-
不包含:如果文章不包含这些内容,则返回FALSE。
检查是否存在 POC/EXP 的解释 (POC/EXP Explanation):
-
包含:解释需满足以下条件:
1.字段解释:明确描述 POC/EXP 中的多个字段或参数,例如 HTTP 头、输入参数、函数参数等。
2.与代码块分离:解释必须独立于代码块,不能把代码本身当作解释。
3.全面性:涉及 POC/EXP 的多个字段,不能只解释单个字段或依赖通用命令(如 curl 或 telnet)。
4.代码注释:若注释为逐字段解释,也可视为解释。
-
不包含:如果解释不满足上述条件,或完全没有解释。
1.4.4 步骤4:投票模型与结果复核
为什么使用投票模型:大模型存在一个普遍问题幻觉现象。即指模型在生成内容时,可能会提供不准确、不真实甚至完全虚构的信息。反映在本题中,在POC、EXP等内容的判定时,大模型的判定结果会出现极大偶然性与随机性
投票模型的引入:可以有效提高准确性与一致性,一方面,可以针对大模型的误判,通过多次提问,取多数结果,来增加提示工程的稳定性。另一方面,部分隐藏性内容(如是否存在对POC的解释)在单次提问中不一定显示准确结果,需要多次提问来进行复核。
我们的策略主要包括以下几点:
-
针对较为直观的漏洞信息如CVE编号等,首先进行两次提问,如果两次提问不一致,进行第三次提问,并最终选择第三次结果,以此降低不确定性。
-
关于是否存在POC以及对POC的解释,由于这一部分大模型的判定显示了更大的随机性,我们进行了四次判定。根据实际尝试经验,大模型倾向于将牵强的POC与解释算作存在解释,只有较低概率下能够正常判断出解释牵强不算解释这样的情况,因此,我们这里选取了带优先级的投票模型:进行四次投票,大模型的判定结果只要出现一次有POC但无解释,优先输出此结果,如果出现没有`POC`但有解释的悖论情况,将有解释矫正为无解释(以防万一,实际测试时并未出现悖论情况)。
-
针对文章是否与漏洞相关,考虑到判断与漏洞无关时全部输出维度都将为NULL,将会遗漏很多重要信息,我们要求四次投票中至少存在两票时才判断与漏洞无关。
除此之外,我们发现大模型在一些输出维度上可能超额完成任务,比如寻找危险函数时找到例如反序列化函数formatter.Deserialize、任意文件读写危险函数fopen等出题时并未被考虑到的函数。为了匹配题目情况,我们也对结果进行了进一步复核,如果厂商、编程语言与危险函数的输出结果不再预期列表之内,判定这一结果并不被赛题需要,并将输出结果修改为NULL。
1.4.5 步骤5:赛题要求之外的额外信息提取
考虑到在实际漏洞分析和漏洞利用过程中使用到的信息远不止步骤2中所述7个维度,在此部分我们对分析维度进行扩展,进一步利用大模型提取版本号、缓解建议、提取PoC或EXP等信息,从而有效提升我们提取的信息量。首先定义维度信息:
1.Software Version Number: 受影响的软件版本号。
2.Mitigation Recommendations: 漏洞的缓解措施。
3.Extracted POC/EXP: 文章中的漏洞概念验证代码(POC)或漏洞利用代码(EXP)。
分析文章中受漏洞影响的软件版本号(Software Version Number):软件版本信息是确认漏洞是否影响特定系统的重要依据。如果文章中存在 "Affected Version(s)"或"Tested Version(s)",那么:
-
仅有一个字段:直接使用该字段的内容。
-
两个字段同时存在:优先使用 "Affected Version(s)"。
-
如果两个字段都不存在,返回 NULL。
提取/总结文章中的漏洞缓解措施(Mitigation Recommendations):提供缓解建议有助于快速采取防护措施。如果文章包含"Suggested Mitigations"字段或者"Mitigations"字段则直接提取其内容。如果该字段不存在:
-
文章中提取并总结相关缓解建议。
-
如果无相关内容,返回NULL。
提取文章中的漏洞概念验证代码(POC)或漏洞利用代码(EXP)(Extracted POC/EXP):提供 POC/EXP 有助于复现漏洞行为,具体的:
-
如果文章包含 "Proof-of-Concept"字段则直接提取其内容,通常为 Python 代码。
-
如果该字段不存在,返回NULL。
如下所示,大模型对上述三个维度具有良好的提取效果:
1.4.6 html图片信息提取
我们浏览了赛题提供的网站上的一些其他博客,发现一些博客中很多代码在图片中。针对赛题本身,我们可以忽视这些信息。但是在实战中,这样的信息同样有价值。因此,我们使用paddle框架改写了我们的html文件信息提取脚本,该脚本可以识别html中的图片,提取其中文字,并将图片中的文字信息拼接到原有信息的末尾作为补充信息。实际使用时,只需直接替换test.py脚本中的extract_text_from_html函数即可在解析html时解析图片信息,进而将信息传递给后续处理步骤。
一个使用样例如下:
示例网页中包含这样一张图片,图片上显示这一漏洞是formatter.Deserialize函数触发的反序列化漏洞。不提取这一图片时,我们无法获取这一危险函数的上下文。
调用改进后的HTML网页提取函数后,程序会识别html中的图片链接,获取图片并解析文本,最终将文本拼接起来作为补充输入,函数运行结果如上图所示。
放大可以看到,函数成功提取到包含危险函数formatter.Deserialize的这段代码。
1.5 小结
经过测试,我们发现大模型强大的语义理解能力使其可以很好地完成信息提取任务,甚至超额完成部分任务。另一方面,我们发现大模型总是倾向于针对牵强的结果做出肯定性回答。在使用True/False这种二元分类方法,使用提示过程判断这种是否存在某种情况的问题时,有很多误判将会难以排除。
结合本题题目背景,我们认为在实战中更加合理的运用大模型方法是扬长避短。例如“是否存在POC/EXP解释”这一项目,即使输出是或者否,程序使用者仍然需要去网站上查看提示,这其实既不方便,又没有充分利用好大模型的总结能力。赛题中为了统一标准,设置维度是便利的,但在实战中,我们其实可以更进一步,直接要求模型输出它提取出的解释POC/EXP的描述,或者对其进行总结。这样便可以扬长避短,充分发挥模型总结语义的优势,规避其倾向于做出肯定回答的缺陷,对于使用者而言也更便捷有效。
最后,感谢出题人们的及时反馈与帮助,感谢我们的指导老师彭国军教授对我们的关心与指导。彭老师高度关注我们的比赛情况,经常询问做题情况并且在解题思路上给予了我们很多宝贵的建议。也非常感谢本赛题精巧的情景设计,这一问题背景具有很高实用价值,使用大模型进行威胁情报提取既方便快捷,又精确高效。我们认为对应的解题方法在后续实用场景中也能带来很大的帮助。
2.1 题目背景
漏洞挖掘是网络安全工作中不可或缺的一环,但传统的审计方法耗时耗力,且静态分析技术存在一定的局限性。随着人工智能技术特别是大模型的发展,通过对代码中的语义进行深度分析,实现更为精准的漏洞挖掘已经成为可能。这种新型的技术手段不仅提高了漏洞检测的准确性,还极大地提升了工作效率。本题要求选手自行编写程序,并结合大模型技术自动化识别出漏洞样例中存在的安全隐患。具体任务包括:知识提取、代码分析、漏洞识别、误报消除等。通过本次比赛,参赛者不仅能够积累漏洞模式增强自身的漏洞挖掘能力,还能深入了解大模型在漏洞检测领域的应用前景。这不仅有助于提高个人的网络安全技术水平,也为未来网络安全工具的研发提供了新的思路。
2.2 实验/项目环境
-
操作系统:Ubuntu 22.04 / Windows 10
-
开发语言:Python 3.10
-
第三方库:langchain
2.3 技术实现的核心思路
1、根据文件总大小对待分析文件进行排序
2、过滤无关文件类型,然后根据编程语言进行函数切分
3、运用多种方法分类进行漏洞查找
(1)针对代码量过大的工程,使用基于提示工程的初步筛查提取可能包含漏洞的种子函数
(2)根据函数切分结果与初筛结果提取种子函数内容,根据不同漏洞类型,调用不同子模块进行二次筛查
-
针对漏洞模式较为简单的漏洞类型,使用提示工程进行漏洞筛查
-
针对漏洞样例种类丰富的漏洞类型,使用RAG技术进行漏洞筛查
-
针对其他漏洞类型,结合两种思路,根据测试效果择优选择
2.4 技术实现的具体步骤
2.4.1 步骤1:环境配置
python或python虚拟环境运行:使用pip安装langchain、langchain_openai,配置test.py中的data_dir、answer_dir、key_env和base_env,然后使用python调用test.py运行
使用docker运行:使用下述代码导入镜像,挂载数据集目录与输出目录,提供API_key并运行镜像
2.4.2 步骤2:对待分析文件进行排序
本赛题给出的待分析文件包含多种代码类型,部分反汇编C语言代码文本量很长,部分python工程则包含众多文件。由于token存在限制,分析这些子目录时往往会占用较长时间。
由于赛题总数据集未知,我们无法确定此类复杂数据的所占比重,为了防止镜像运行时间不足,在进行分析之前,我们根据文件大小对待分析文件进行了排序,先分析总大小较小的子目录,再分析总大小较大的子目录。
2.4.3 步骤3:过滤无关文件并根据编程语言进行函数切分
由于题目给定的部分代码文件过长,赛题API存在限制,我们需要对内容进行切分才能进行后续处理。经过讨论我们认为,针对不同代码类型进行函数级切分是很有必要的。随意切分会截断函数,干扰后续处理判定;函数级切分则能够保留函数内部所有信息,并且可以在后续步骤中重新拼接各函数,进而实现提取初筛种子函数进行二次筛查的效果。
参照题目给予的测试数据集,我们发现其代码种类包括:反编译伪代码,C,C++,java,python,go,js等。我们针对每一类出现过的代码类型,分别使用一个脚本进行了函数切分处理。下面我们以C相关代码类型为例来详细介绍我们切分代码的实现逻辑。
伪代码、C、C++:C语言类的代码函数级切分较为复杂,其函数签名可能存在多行情况,且容易与条件判断混淆。因此我们使用正则表达式匹配函数签名,确保能够识别多种函数声明格式;同时引入栈机制处理大括号匹配,确保函数体范围提取准确无误,即使存在嵌套大括号。具体涉及的步骤如下:
1.文件解析与逐行处理:程序读取C/C++源代码文件的所有行,并按行逐步解析内容。每行内容通过strip方法去除多余空白符,以便后续正则匹配和逻辑判断,逐行处理方式确保能够应对函数签名和函数体分布在多行的情况。
2.函数签名检测:使用正则表达式^[ws*&]+[w*]+s*([^)]*)s*{?$匹配函数签名,支持以下情况:
-
含返回值(如int、void)。
-
含指针或引用(如int*、const char&)。
-
参数列表可为空或包含具体参数(如()或(int a, char b))。
-
正则匹配成功时,标记当前函数解析状态,开始记录当前函数代码块。
3.多行函数签名处理:对于函数签名跨多行的情况(如长参数列表或返回值与函数名分行),程序支持持续拼接行内容,直到检测到大括号`{`为止,保证即使签名较复杂,函数代码块仍能正确提取。
4.栈机制处理函数体范围:使用栈(stack)数据结构来匹配大括号的范围:遇到`{`时,入栈。遇到`}`时,出栈。当栈为空时,表示函数体的大括号范围完全匹配,函数解析完成。此方法有效处理了函数体内嵌套代码(如if/else、for/while、嵌套结构体等)的场景。
5.函数存储与结果管理:提取的完整函数代码块存储在列表functions中。每个函数的代码块以字符串形式保存,保持其完整性,便于后续分析或保存。
6.目录递归与批量处理:支持递归遍历输入目录,通过os.walk查找所有以.c、.cpp、.h、.cc结尾的文件。对每个文件逐一调用函数提取逻辑,并生成独立的结果目录以保存提取内容。批量处理能力适合大型项目代码库的解析需求。
类似的,Go语言主要通过使用不同的正则表达式^funcs+((w+s+*?w+)s+)?w+(.*)s*{? 匹配 Go 的函数定义。
PHP语言通过正则表达式匹配类定义和函数定义:类定义的正则表达式^classs+w+s*{,匹配以class开头并带大括号的类定义。函数定义的正则表达式^functions+w+s*(.*)s*{,匹配以 function 开头的 PHP 函数定义。
Java语言使用简单的启发式方法检测函数签名,通过关键字(public、private、protected、void、int 、String 等)、括号、大括号等来判定函数的起始。
JAVASCRIPT语言:JSP文件一般长度较短,并且函数嵌套严重,因此放宽了切分粒度,直接进行了文件级切分,读取文件内容进行拼接,输出拼接后的文件列表以代替其他模块的函数列表。
Python语言主要通过抽象语法树来进行解析:先ast 模块解析文件内容,将源代码转换为抽象语法树,然后遍历语法树提取函数:ast.walk、ast.FunctionDef、ast.get_source_segment,最后进行函数名与代码存储,将提取到的函数存储为字典格式。
顶层API接口:在完成了上述函数切分工作后,在test.py文件中通过引入各切分脚本并且调用其中顶层API接口,即可获得子目录下所有文件中的函数列表,列表中每一项都是切分后的完整函数体。在后续分析时,我们可以根据token限制拼接部分函数体进行初步筛查,也可以根据筛查结果在列表中查找并提取对应函数体,抽取函数子集合进行二次筛查。
2.4.4 步骤4:针对代码量过大项目,使用提示工程的来初步筛查
每种类型下的用例所包含的代码长度不一,数量不一。有些较短的代码片段切分出来的函数较少,而有些较长的代码片段切分出来的函数较多。如果我们将较长的代码片段下的所有函数全部输入到模型中,会导致以下两种问题:
1.输入内容远超过最大 tokens 限制,需要分批次输入。
2.大模型输出不确定性强,不同批次处理结果的判定标准存在潜在差异。
为了解决上述问题,我们使用基于提示工程的初步筛查提取可能包含漏洞的种子函数,淘汰掉与漏洞无关的其他函数,再在后续阶段对种子函数进行分类二次筛查。初筛的主要目的是减少输入内容,因此筛选规则限制较宽,以确保不会遗漏种子函数。
初筛阶段的具体实现如下:test.py中的llm_api_step1函数是初筛阶段调用大模型的接口,对应prompt位于test.py文件的头部位置。我们将根据prompt内容介绍初筛时的关注重点:
-
SystemPrompt的目的是让模型代入漏洞分析者的身份,告诉模型我们将要分析的漏洞类型,便于模型从漏洞分析的角度分析我们提供的代码片段。
-
HumanPrompt的目的是提供目标代码编程语言和对应漏洞类型,让模型分析给定代码片段,提取出可能包含漏洞的种子函数,返回种子函数列表,列表中的函数将用于下一步的二次筛查。
在代码分析方面,我们为模型提供了两种分析角度:函数调用链、是否有安全检查,并告诉模型不要局限我们所提供的角度,努力从该漏洞的各类角度分;由于提供的代码片段往往不完整,遇见函数体不明的情况也要自行分析,分析过程要尽可能的大方彻底。这样做的目的是保证初步筛查不会遗漏重要的种子函数。
在格式要求方面,为了返回的正确的种子函数列表,我们使用StructuredOutputParser.get_format_instructions()限制了输出格式为json,并告诉模型不要输出无意义、过于冗余的结果。我们的SystemPrompt和HumanPrompt的用语描述具有普适性,保证prompt适用于挑战二中所有的漏洞类型。
由于赛题API存在使用次数限制,我们也进行了一定的鲁棒性处理(如超时处理、错误处理)。在SystemPrompt和HumanPrompt的结合提示下,经测试,模型可以成功对输入响应并返回结果,该阶段顶层调用接口如下所示:
2.4.5 步骤 5:根据函数切分结果、初筛提取的种子函数、不同漏洞类型,调用不同子模块进行二次筛查
在初步筛查阶段,我们得到了可能包含漏洞的种子函数列表。借助代码切分函数的返回结果,我们可以将种子函数对应的函数体取出拼接,并且拼接与之相关的调用链函数,然后将上述输入传递到二次筛查中,以得到最终的危险函数。
为实现更加细粒度的漏洞筛查,并且方便在比赛结束后针对不同漏洞类型分别扩展框架,我们对不同漏洞类型进行了分类筛查。经过大量实验测试,我们决定对漏洞模式较为简单的Arbitrary_file_access、Authentication
_bypass和Integer_overflow三种漏洞类型使用基于提示工程的二次筛查,而对漏洞样例种类丰富的Buffer_overflow、Command_injection和others三种漏洞类型使用RAG技术进行漏洞筛查,下面我们将分别描述这两个方案的选择原因与实现方法。
2.4.5.1 针对较为简单的漏洞类型,使用提示工程进行漏洞筛查
一开始,我们参考了开源框架vuln_hunter的思路,准备使用提示工程方法,为每一类漏洞设计专用提示词,进而进行细粒度漏洞筛查。但是在实践后我们发现,不同漏洞类型与提示工程的适配程度存在一定区别。
在我们的测试中,Arbitrary_file_access漏洞使用提示工程方法具有最佳效果,Authentication_bypass漏洞也有一定效果,其余漏洞类型使用此方法时的效果则不太理想。即使如此,我们依然在此方向上进行了大量探索,并为每一种漏洞类型设计了我们认为最符合赛题要求的一版提示词,下面我们将分别对其原理进行描述。
在二次筛查阶段,为了更加精确地识别漏洞,我们对HumanPrompt进行了细化。我们为模型提供了人工总结归纳出的分析角度(即先验知识),不同的先验知识维度标志着函数具有不同程度的危险行为,每种危险行为的置信度不同。总置信度最高的函数代表具有最高的危险性,模型将按总置信度倒序排序,并输出危险的函数列表及其总置信度。
这里以Authentication_bypass的HumanPrompt为例进行说明,我们让模型分析提供的种子函数代码片段中的所有函数,识别潜在漏洞,并对每个函数进行评分(置信度)。值得注意的是,我们需要对每个种子函数进行独立的评分。针对该漏洞我们人为总结了可能存在危险行为的做法,并将其归纳为四个评判标准:
-
缺少身份验证检查(20分):特权操作未验证凭据或会话。
-
验证逻辑问题(20分):例如接受部分凭据或逻辑错误。
-
会话管理问题(20分):如未验证令牌或会话标识符不安全。
-
后门机制(40分):硬编码凭据或调试模式等绕过机制。
每个标准的危险程度不同,对应的分值也不同,模型需要逐条按照标准进行静态代码分析,计算每一条的得分和总得分,输出所有函数的名称和总得分结果。最终以有效json格式展示分析结果,确保分析逻辑清晰、全面且避免过度泛化。类似的,其他几类漏洞置信度规则为:
Arbitrary_file_access漏洞类型的评估规则:
-
确定是否存在文件 I/O 函数(20分):如 open()、fopen()、read()、write()、fseek() 等。
-
路径遍历(20分):是否有文件 I/O 函数处理用户控制的文件路径。
-
不安全路径处理(20分):是否存在用户输入连接到文件路径的情况。
-
考虑缺乏权限检查(20分):是否未检查文件访问权限。
Integer_overflow漏洞类型的评估规则:
-
输入控制的整数(10分):用户输入或外部数据影响的整数未经验证。
-
缺少边界检查(10分):整数在数组索引、循环等使用时未验证范围。
-
不安全的算术操作(20分):整数参与可能导致溢出的运算。
-
内存分配问题(40分):整数影响内存分配但未验证,可能导致溢出。
Command_injection漏洞类型的评估规则:
-
命令执行函数的使用(10分):检查是否存在命令执行函数,例如 “system()”、“exec()”、“popen()”、“Runtime.exec()” 或“ProcessBuilder”。
-
未验证的用户输入(20分):评估用户输入是否直接影响命令执行,而未经过验证或清理。
-
权限操作(20分):确认函数是否执行了权限相关操作(如 $permission),这些操作可能影响命令注入功能。
-
Shell 元字符的使用(20分):检测函数是否允许用户输入与命令直接拼接,从而可能包含 “;”、“&&”、“||”、“|”、“$()”或反引号等 Shell 元字符。
Buffer_overflow漏洞类型的评估规则:使用上述方法进行缓冲区溢出检测时误报很多。赛题提供的大模型接口倾向于将所有内存拷贝函数都视为风险操作,因此我们在下一节给出了我们针对此类漏洞的特定解决方案,但是在此也给我们最初的评估规则:
-
危险的缓冲区操作:不安全的函数如“strcpy”、“strcat”、“memmove”、“sprintf” 等容易导致缓冲区溢出。
-
不安全的标准输入读取:从“stdin” 直接读取到缓冲区的函数(如“gets” 或 “sscanf”),如果没有适当检查,会存在风险。
-
格式化字符串处理:使用 “%s”格式读取字符串内容到缓冲区的操作,例如 “sprintf(a, "%s", b)”。
-
数组越界:超过数组声明大小的访问,通常发生在循环或索引处理不当时。
-
堆漏洞:与堆操作相关的缓冲区操作问题,如不安全使用“memcpy” 或 “memmove”。
-
类型混淆:数据类型被误用以绕过长度检查,例如将有符号值作为无符号长度参数使用。
others漏洞类型的评估规则:由于others下漏洞子类型过多,我们对其进行了再次分类。由于各种类型评估标准不方便统一,在该类型下我们没有再采用置信度评分方法,而是让模型输出最危险的函数,再在顶层接口处重新规范输出,保证json内容与其他类型一致。
-
双取(double-fetch):
1.关注涉及内核操作多次交互的函数,例如“copy_from_user()”、“get_user()”、“copy_to_user()” 和“put_user()”。
2.如果函数使用了内核内存分配(如“cursor_alloc”),但延迟使用分配的资源,这种模式很可能存在漏洞。
-
SQL注入:
1.检查是否使用了危险函数,例如“mysql_query()”。
2.分析用户输入到SQL查询中,检测输入是否经过正确的验证或清理。
-
条件竞争(race condition):
1.验证是否存在TOCTOU(先检查后使用)的情况。
2.判断是否存在锁函数的不恰当使用。
释放后使用(UAF):
1.检查资源的使用情况,特别是在“free()”等函数释放资源后又进行操作的场景。
-
格式化字符串漏洞:
1.检查用户输入是否直接用于格式化函数参数,例如在“printf()”、“fprintf()”、“sprintf()”等,且未进行适当的验证或清理。
-
CSRF/SSRF漏洞:
1.查找与HTML渲染、JavaScript生成或DOM操作相关的高风险函数。
2.确认是否存在可能导致跨站请求伪造(CSRF)或服务器端请求伪造(SSRF)攻击的场景。
在设置完上述规则后,我们单独给出了others类漏洞的输出要求:
-
仔细分析每个函数,找出包含漏洞触发点的具体函数。
-
提供漏洞函数的名称及其对应的漏洞类型。
-
如果未识别出漏洞,则不输出任何结果。
2.4.5.2 针对复杂漏洞类型,使用RAG技术增强漏洞筛查
在尝试使用提示工程进行缓冲区溢出漏洞检测时,发现无论给出多么严格的提示词限制,AI还是会认为所有包含内存拷贝调用的函数都是具有潜在缓冲区溢出风险的。这种总认为存在潜在隐患的分析标准导致了大量误报,使得在比赛与具体运用中都难以起到较好效果。例如,snprintf(a,0x100,"%s",b)函数调用根据漏洞挖掘经验一般不会导致缓冲区溢出,疏于考虑长度限制的开发者会直接使用sprintf或者用变量传递长度参数,一旦常量长度参数存在,其开发者一般便会参照目的缓冲区长度对其进行设置。但是即使char a[100]定义语句就在这行代码的前一句,题目调用的大模型API还是会倾向于认为这句语句存在漏洞风险(这一困境也可能与模型有关,我们尝试提问了gpt-4o,它更倾向于将snprintf界定为可以防止溢出的安全的字符串处理函数)
因此,我们尝试调研了其他大模型漏洞挖掘方法,发现一篇arxiv论文LProtector: An LLM-driven Vulnerability Detection System提到可以输入使用检索增强生成技术技术RAG,把漏洞触发点的代码输入给大模型,让它学习漏洞代码模式,然后查找其他安全隐患。
我们经过讨论后认为,既然题目中某些漏洞类型包含丰富的用例,例如缓冲区溢出类型中包含了跨函数调用流、整数溢出导致的缓冲区溢出、strlen导致的长度限制失效等多种情况,我们可以把这些案例视为先验知识,运用RAG思路使大模型检索漏洞代码模式,并进一步匹配具有相似模式的未知安全隐患。
我们观察到题目提供的Bof漏洞用例很多都是IoT设备的httpd程序反汇编代码,而这一思路在真实漏洞挖掘中其实也具有一定价值。IoT设备中往往存在代码复用,已知CVE中的漏洞代码可能在同一厂商的其他设备中同样存在并且没有得到修复,检索代码模式的方案可以更加精准地定位到此类潜在风险,规避模型“自认为”的一些漏洞模式带来的误报,实现更加精确的检索,并且也具备找到不在漏洞模式库中的明显缓冲区溢出漏洞的生成能力。
具体实现:在对应脚本bof_memory.py中,我们首先设计Prompt,让模型完成以下两种任务:
-
输入以Examples开头的示例模式时:
1.模型需要对示例代码中存在的缓冲区溢出漏洞进行总结。
2.在prompt强调保留中文解释(我们发现使用中文反而让AI判定时更能区分解释与代码)和函数名的完整性,使AI重视其中对于漏洞模式的总结。
-
输入以New Content开头的新内容时:
1.对于新的代码段,模型需要根据先前总结的漏洞模式独立分析每个函数的漏洞风险,输出最可能存在漏洞的函数及其漏洞置信度。
除此之外,prompt中还进行了以下处理:
1.强调一段输入不可能既是示例模式又是新内容,防止AI进行误判。
2.强调不包含特定模式的函数漏洞置信度应为0,减少误报内容。
3.规定输出模式,并且根据赛题要求,要求找到最匹配漏洞现有模式的函数后将其余函数的置信度置0。
由于langchain框架进行有记忆调用时,需要把记忆内容算作token的一部分,ConversationBufferMemory这一记忆API会占用过多提示词。因此,我们选用ConversationSummaryMemory这一API记录记忆信息。另一方面,为了避免多次调用API导致总结内容被过于精简,我们在每次调用check_bof这一API接口时将以往记忆信息情况,要求AI重新生成记忆内容。具体接口相关代码如下:
使用这一漏洞挖掘思路时,一方面给定现有模式后,AI会更加精确的匹配现有模式,从而提高给定案例识别的准确率,减少误报。另一方面,我们也发现对于一些没在测试数据集模式中的IoT漏洞,这一识别模式同样可以进行识别,说明这一思路在准确度更高的同时也同样具备找到未知漏洞的能力。
2.4.5.3 顶层API设计
主文件test.py将会分别引用每个漏洞类型的检测脚本API接口,获取相同格式的json结果,并将结果中置信度得分高的危险函数写入结果文件。由于赛题中存在反汇编伪代码,这些代码往往过长,一至两个函数便会达到token限制。因此,我们不得不在初筛后仍然文本过长时多次调用这一API函数,导致误报率最初较高。经过讨论,我们在输出时也进行了限制,每个子目录最多输出4个危险函数,以此限制误报的影响。实战中如果需要更加全面地筛查与输出,可以删除`global length_limit`变量的相关引用,解除这一输出限制。
2.5 小结
本赛题是一个综合任务,七天时间内要求针对至少8种编程语言,至少11种(5类+others的6种子类+others其他可能类型)不同漏洞进行漏洞挖掘,我们没有太多时间针对某一类特定任务进行深入探索。
搭建初步筛查+分类复核的框架时,我们本来计划扩展vuln_hunter,使其支持更多的编程语言与函数类型。然而,由于整数溢出等漏洞类型中关键代码不好判别,vuln_hunter根据危险函数调用情况预先筛除部分python文件不予评判的思路并不支持本赛题过多漏洞种类判断的任务。因此我们为了适应赛题,选择先进行函数切分,然后让大模型进行种子函数初筛,这花费了大量分析时间,也耗费了更多token。在后续的二次筛查阶段,我们用于细化每一类漏洞检测方案的时间也因而是有限的。但另一方面,正因是这种综合类赛题,在比赛期间,我们深入了解了不同漏洞类型的原理,为每一类编程语言都编写了函数切分脚本,也尝试了两种不同思路的分类漏洞挖掘脚本撰写。后续无论深挖哪一类漏洞,我们都可以从现有的工作出发,进行更加细粒度的扩展:
-
拆分函数类型与代码类型,继续扩展基于提示工程的分类筛查方法,实现与vuln_hunter框架相似的工程。
-
如有更加全面漏洞信息库资源,可以扩展使用RAG技术的筛查方法,使其检索漏洞库中更全面的模式,实现更好的识别效果。
-
反汇编代码往往冗长且可能存在误差,如果提供二进制文件,可以使用angr框架将代码提升为vex中间语言,结合先前工作中的静态数据流分析方法进行漏洞初筛,替换现有初筛方法,使大模型能够针对更少代码进行更加精细化的复核。
最后,感谢出题人们的耐心答疑与反馈,出题人们耐心而细致的反馈给了我们很大的帮助。感谢我们的指导老师彭国军教授对我们的指导与帮助。彭老师高度关注我们的比赛情况,经常询问做题情况并且在解题思路上给予了我们很多宝贵的建议。
参考文献
[1].https://github.com/PaddlePaddle/Paddle
[2].https://beautiful-soup-4.readthedocs.io/en/latest/
[3].https://blog.csdn.net/cyj972628089/article/details/138215974
[4].https://waytoagi.feishu.cn/wiki/YVWLwlkaBidigokk3JfcmTfCnVf
[5].https://blog.csdn.net/cyj972628089/article/details/138215974
[6].https://arxiv.org/abs/2411.06493
[7].https://docs.python.org/zh-cn/3/library/ast.html
[8].https://mp.weixin.qq.com/s/EJw7-VPEseLEueRmQhy8Vg
-点击查看更多赛道解题报告-
感谢合作伙伴的助力 让我们走得更高更远
原文始发于微信公众号(DataCon大数据安全分析竞赛):DataCon2024解题报告WriteUp—漏洞分析赛道
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论