从0到5个CVE:新手三个月漏洞挖掘实战路线
我到现在还记得第一次在CVE列表里看到自己提交的漏洞编号被收录时的心情。那一刻不是觉得自己厉害而是“原来这条路真的能跑通”。三个月5个CVE放在真正的漏洞挖掘大佬面前不值一提但对一个白天上班、晚上啃代码的人来说已经是最好的正反馈。我想把这段经历拆开讲讲尤其是那些没人提前告诉我的认知和流程细节。文章不打算讲“一夜挖洞成神”重点是你如何从0开始用三个月时间拿到第一批CVE编号顺便避开我走过的弯路。我踩过的坑很多不是技术上的而是方向和方法上的。1. 为什么多数人挖不到漏洞先修正三个错误认知1.1 别把漏洞挖掘当成“找茬”很多新人打开漏洞挖掘的第一步就是拿扫描器对着一个网站乱扫扫出一堆告警就兴奋得不行。最后提交的时候才发现要么是重复报告要么是厂商根本不认可要么是根本没构成安全风险。我刚开始也这样干过连续两周几乎零产出后来才意识到漏洞挖掘不是“找茬”而是“理解程序预期行为”。什么意思开发者在写代码的时候脑子里会假设输入是合理的用户名就是一段短字符串、sort参数就是asc或desc、文件名不会带路径分隔符。漏洞挖掘者要找的就是这些假设被打破的点。所以我不再看某个参数“看起来危险”而是问自己如果我是这个项目的开发者我会在哪里偷懒比如图省事直接拼接SQL、用字符串拼接方式组合命令行、相信自己前端已经做了校验就不再在后端过滤。抱着这种视角每个功能模块都变成了“找偷懒点”的侦探游戏。还有一个容易被忽略的点别只盯着“高危”漏洞。对于初学者来说XSS、越权、信息泄露同样有价值尤其是逻辑漏洞往往比传统注入更容易被发现而且同样可以被分发CVE编号。先建立起“足够小的确定性”再慢慢往高价值漏洞走这条路反而更稳。1.2 选错目标等于白干目标选择的优先级可能比技术能力还重要。我见过不少朋友一上来就打头部互联网厂商的漏洞赏金项目一个月下来一个有效报告都没有信心被打没。不是说不能打而是对于三个月想拿到5个CVE的新人来说这种方式性价比太低。怎么选我当时用一个非常朴素的筛选标准代码量最好控制在1万到20万行之间语言必须是我熟悉的项目最好有一些用户基础但又不至于庞大到无从下手维护者响应速度别太慢。按这个标准我锁定了几个开源CMS、开源留言板和开源论坛程序专门研究它们的插件系统和老版本代码。老版本是金矿。很多开源项目在某个历史版本之后修了一堆漏洞但你可能从下一个主要版本继续审计那些被修复的问题会以“变种”形式出现在其他功能里。我的第一个CVE就是这么来的某开源CMS的旧版本存在文件包含问题负责人修了其中一个入口但同一个模板解析函数里还有另一条调用链没处理我顺着旧补丁找到了新漏洞。选对了目标等于站在了最容易出成果的土地上。1.3 没搞清CVE的申请门槛不少人对CVE有误解觉得必须打出RCE或者严重反序列化才能申请。实际上CVE编号的分发逻辑是“可被确认的安全缺陷”重点在“可确认”。也就是说一个能够被稳定复现、能说清楚影响面、并且被维护者认可的问题就有机会获得编号。我拿到的5个CVE里有3个是SQL注入1个是路径穿越导致的信息泄露还有1个是越权读取。关键是要搞清楚目标项目归属于哪个CNA也就是编号管理机构。大型开源基金会都有自己的CNA普通项目可能挂在某个第三方CNA下面。最直接的办法是看项目仓库里的SECURITY.md文件或者查项目主页的漏洞披露政策。如果项目完全没有任何安全响应渠道也可以直接联系维护者请他们帮忙确认漏洞再由他们协助申请编号。这一点后面我会单独展开。2. 三个月冲刺的完整时间线从0到5个CVE2.1 前两周确定主攻方向和攻击面我的三个月计划一点都不复杂但每个阶段都有明确产出目标。前两周完全没碰漏洞做的是“攻击面测绘”。我选定了PHP语言体系下的几个开源项目把它们全部拉到本地搭环境打开错误日志然后做了一件事把项目里所有接收用户输入的入口文件列出来。这一步看起来笨但非常有用。入口清单就是我的地图。我会用代码搜索工具找出所有出现$_GET、$_POST、$_REQUEST和$_FILES的文件然后逐个人工判断这个参数会不会深入到底层。很多新手拿到一个大项目就开始从头读代码读一个星期还困在框架的初始化逻辑里。我采用相反思路从输入点到输出点反向找路径。比如一个文件上传功能HTML表单字段名是userfile我就直接搜userfile在PHP代码里怎么被处理很快就能看到它的去向。两周结束时我手里有三个项目的入口列表并且为每个项目画了一张简单的数据流表入口文件、参数名、经过哪些过滤函数、最终流向哪个敏感操作。这份文档后来成了我所有漏洞发现的底稿。2.2 第三到六周用“危险函数清单”批量扫代码第三周开始进入正式挖掘。我的方法特别“笨”筛出所有危险函数然后逐个看上下文。所谓危险函数就是那些容易导致注入、命令执行和文件操作的函数比如代码执行类的eval、assert、preg_replace配合/e修饰符命令执行类的system、exec、shell_execSQL操作类的直接查询方法文件操作类的include、require、file_put_contents以及文件上传相关函数。我写了个很简单的搜索命令把这些函数全部列出来然后一行一行看。真正有价值的不是函数本身而是数据怎么到达它们。举个例子有个后台导出功能文件名参数直接拼接到了导出路径后面$filePath $exportDir . / . $fileName . .csv; $command python3 exporter.py . $filePath; system($command, $output);$fileName来自用户上传的参数只做了trim没有任何过滤。我当时立刻想到Windows、Linux下的命令分隔符问题于是尝试在文件名中插入分号和命令。第一次测试就被执行了。这个洞后来成为我第一个CVE也让我彻底明白危险函数不可怕可怕的是可控数据抵达危险函数的路由没有被拦截。第五周、第六周我继续用同样思路在另外两个项目里翻找又连续拿到了两个SQL注入。这一阶段的产出完全验证了“批量选点 人工确认上下文”的可行性。时间窗口主攻方向当时产出第1-2周攻击面梳理、入口点统计3份入口清单第3-6周危险函数上下文审计3个CVE级别漏洞第7-12周后台逻辑和认证流程重点突破新增2个CVE累计5个2.3 第七到十二周系统梳理后台功能和认证逻辑到了第七周我发现能挖的“简单函数漏洞”明显变少了于是调整策略开始重点看后台功能和认证逻辑。后台的导出、批量操作、权限判断经常藏着问题。这段时间我盯上了一个第三方插件系统它的插件激活接口没有校验来源用户的权限任何人只要能发起一个请求就能启用后台插件配置接口再通过配置接口写入恶意参数最终形成存储型XSS。与其说是技术突破不如说是耐心突破。前六周我把精力集中在危险函数上后六周则是一个页面一个页面地过后台功能清单。我专门做一个记录表每看一个功能就写下三件事这个功能需要什么权限、用户输入到了哪里、处理完以后是否把结果输出到其他页面。权限判断缺失就是这么被发现的一个管理接口被写在了无需登录的公共路由文件里。最后两周我又通过同样的“功能清单遍查法”补上了两个越权和存储型XSS凑齐了5个CVE。这条时间线的最大感受是三个月并不长但每个阶段目标非常清楚不会今天想打代码审计明天又想挖逻辑洞。节奏稳定产出才能稳定。3. 真正有用的四个挖掘技巧代码审计、参数追踪、补丁分析、日志对比3.1 代码审计起步从危险函数和用户输入入口下手很多新人觉得代码审计很难其实本质就是两件事找到不可信输入跟踪它的去向直到被使用或者反过来找到危险函数反推哪些输入能抵达它。我更喜欢后一种因为可以直接看到敏感操作的上下文更容易判断漏洞的“可触发性”。我是这么做的先用代码搜索引擎把项目里所有的系统调用、文件操作、SQL拼接点筛出来存成一个待审列表。然后给每个点打分有没有外部参数参与、有没有过滤、过滤是否完整、触发路径是否需要特殊权限。打分高的优先看打分低的先放一边。这套流程很机械但它可以让我在一千多个文件里不会迷失。看危险函数时要特别注意PHP中很常见的“重复过滤”。有时候一个函数在公共层做了一次过滤但项目里存在多条调用路径其中一条绕过了公共层直接走到敏感函数。我的第二个SQL注入就是这么来的新写的API文件没有包含公共过滤器直接把自己拼的查询语句送进了数据库。后来我养成了习惯每个敏感点都会确认它前面的过滤函数到底来自哪个文件、是否一定被执行。3.2 参数追踪把不可信数据流画出来参数追踪的核心是搞清楚“用户的输入能不能一路畅通地流到危险点”。我不用专业代码审计工具一张纸一支笔就能完成。我会找一条具体的功能链路比如用户提交了一个URL参数然后问自己这个参数到哪个变量有没有被重新赋值有没有经过编码在哪里被输出或执行举一个实操例子。某个后台功能在导出数据时会读取一个外部传入的模板文件名。普通请求是登录用户点击导出 → 模板名写入订单查询条件 → 查询完成 → 结果回显到页面。但当我追踪模板名这个参数时发现它并没有像其他参数一样经过escapeshellarg()处理而是直接拼接到了文件读取函数里。虽然模板名参数被白名单限制在了两个固定值但白名单只是前端复选框后端完全没有再校验。只要构造绕过前端就能读取服务器上任意文件。这就是参数追踪的价值你会发现很多项目里的过滤是“碰巧存在”而不是“被设计成这样”。这类追踪的产出通常很稳定因为每一个HTTP参数都可能是一条独立的供应链。只要入口足够多总会遇到一两个断掉的链条。3.3 补丁分析从版本差异里找漏网之鱼补丁分析是最容易被新人忽略的技巧但对我而言它几乎是稳定出CVE的流水线。当一个开源项目修复漏洞后修复commit通常会写出哪里改动了。我会拿着这个commit和上一版本对比看开发团队到底修补了什么。看补丁不是目的目的是找“同模式问题”。开发者在修一个问题时通常只修了报出来那一处而相同的编码习惯会出现在项目的其他角落。比如一个项目修掉了搜索功能里的SQL注入那么文件上传、排序、分页、筛选这些用到类似字符串拼接SQL的地方很可能还带着同样的坏味道。我的5个CVE里至少有两个是通过补丁分析发现的。一个是项目版本发布公告里提到修复了“搜索功能中存在的SQL注入”我翻到对应commit后去搜其他接口里的SQL拼接方式看到分页参数照样没做参数化于是顺着这个模式拿到了新的漏洞。另一个是修复命令注入时只修了Linux命令参数但系统同样跑Windows命令我换了Windows分隔符之后又能执行。补丁分析本质上是在跟开发者“比耐心”他把所有同类问题都修完了你就没得挖但你永远可以赌他不会修完。3.4 日志对比利用崩溃日志定位隐含逻辑漏洞第三个技巧相对小众但对挖边界型问题非常有效。我面对某些接口时会故意输入一些畸形数据然后把应用日志打开观察程序“临死前”暴露了什么信息。崩溃日志往往会把变量名、文件路径、SQL查询甚至堆栈信息直接打出来相当于程序主动告诉你它的内部结构。举个例子某后台功能接收到我传入的JSON数组但解析后没有判断类型直接把数组当字符串使用了。结果PHP报错日志里出现了“Array to string conversion”的提醒并且打印出了同一条SQL里的其他字段名。顺着这个异常我发现只要把数组长度设置成特定值原本被过滤掉的字段名就会绕过拼接逻辑进入SQL语句。这个漏洞后来被确认为一个低危信息泄露和注入的前置条件。日志对比的核心是“看见失败路径”。程序在处理合法输入时的路径开发者通常考虑得比较周全但异常输入会导致程序走到他们从未预想过的分支这些分支往往缺少同样的安全校验。我不再只看“能不能执行命令”而是开始关注“程序报错时暴露了什么细节”积少成多就成了挖洞线索的来源。4. 从发现漏洞到拿到CVE编号提交全流程与避坑4.1 披露策略先厂商后公开顺序不能乱拿到漏洞之后怎么做很多人踩过坑。我的原则只有一句话先私下联系维护者不要公开不要发博客不要在社交媒体炫耀。所有的CVE申请和漏洞确认都是以受控披露为前提的。第一步找到项目的安全联系渠道。现在多数代码托管平台会提供安全通告的提交入口或者项目仓库里有一个SECURITY.md。实在找不到就通过仓库里的commit邮箱找维护者的联系方式发邮件。发邮件的时间点最好是漏洞已经完全复现并且可以稳定输出结论之后而不是只有一个猜测。第二步在邮件里说清楚影响。我当时用的模板很简单目标项目名称、版本号、漏洞类型、受影响的功能模块、复现步骤和PoC、建议修复方案。其中“受影响功能模块”和“复现步骤”最重要。我之前有一次只写了“存在SQL注入”和几行代码维护者根本没法快速验证来回沟通耗了两周才确认。第三步等待响应。一般开源项目的维护者会在1-2周内回复如果期间没有得到回应我才会考虑通过项目所属的CNA渠道发起漏洞收录申请。注意一点不要用“不修复就公开”来威胁维护者。这既不符合行业规范也会让后续的CVE流程变得非常被动。4.2 最小可复现样本(PoC)怎么写才容易被接受PoC不是代码写得越复杂越酷而是越简单越容易被认可。维护者时间有限一个只有三行curl命令的PoC远比一个需要下载依赖、配置数据库的脚本更容易被快速验证。我在提交时尽量遵循三个原则不使用自动化爆破工具不涉及大批量请求不包含真实用户数据。比如一个SQL注入漏洞的PoC我会这样写curl -X POST https://example.local/app/export \ -H Cookie: session测试会话ID \ -d sort1;SELECT SLEEP(5);-- -同时附上预期响应和实际响应的差异说明为什么这一条请求就证明了漏洞存在。对于需要登录的功能我会说明“请使用任何低权限测试账号”绝不把生产环境的真实账号贴进去。还有一个特别容易被忽略的点在PoC里把漏洞的业务影响写清楚。不是简单说“可以注入SQL”而是说“攻击者可以通过注入读取到orders表中的手机号和邮箱因为这些字段在查询中被拼接”。影响描述越具体维护者确认和分配编号的意愿越高。后来我帮同学看报告时发现他提交的XSS一直没有被确认就是因为只写了“可执行JavaScript”但没有说明它发生于后台能进一步操作管理员的会话导致被当作低危噪音处理。写清楚攻击链报告质量马上不一样。4.3 审核周期与常见退回原因CVE从提交到编号分发没有固定时间有的项目一星期就确认有的能拖两个月。我的经验是越“讲得明白”的漏洞确认越快。审核人员不需要反复追问你自然审核周期就短。退回原因主要集中在四类。第一类是重复报告这个漏洞已经被别人提交或者已经是公开CVE。解决办法是先查一遍项目的历史CVE列表、Security Advisories和用户报告确认目标范围是空白地带。第二类是影响不充分比如一个只能影响当前用户自身的XSS在多数场景下不会被分配编号。第三类是无法复现这一步最可惜明明是真实漏洞但PoC没有写清楚前提条件维护者跟着操作失败了两次就放弃。第四类是被认定为代码质量问题比如只要传入超长字符串就会报错但没有额外安全影响维护者可能认为这属于健壮性问题而不属于漏洞。避免退回最好的方法是做一次“自问自答”攻击者需要什么条件这个漏洞能让他做什么他本来不能做的事如果这两个问题都能给出确定的答案再提交流程也不迟。我每次写完报告都会把文档晾一会儿再假装自己是维护者从零开始照着操作一遍凡是有一点不丝滑的地方就改到通畅为止。5. 复盘那些让我差点放弃的坑和心得5.1 来自“疲劳期”的教训三个月里最难受的不是技术瓶颈而是心理上的疲劳。第六周前后我连续三周没有任何像样的进展每天都对着屏幕翻代码感觉自己像是在做无用功。同一时间另一个项目发布了新版修复了好几个漏洞但我竟然因为盯着当前项目完全错过了那个项目的审计窗口。后来我的调整方式是准备了三四个候选项目一旦当前目标陷入泥潭就切换过去。不是放弃而是让大脑换个环境。很多问题在当前代码里怎么看都看不出来放到另一个项目的代码里思路反而被激活。切换之后再回来往往一眼就能看到之前遗漏的不自然之处。我的5个CVE不是平均分布在三个月里而是集中在两三个“爆发期”其余时间都是漫长的铺垫。5.2 工具与时间投入的真实记录我在工具上真没花什么钱。最常用的是浏览器开发者工具、抓包工具、一个支持正则搜索代码的编辑器外加一份自己整理的危险函数清单。晚上下班后投入两到三个小时周末抽出半天做深度审计。三个月里的大部分漏洞都是在工作日晚上发现的说明持续稳定的投入比一次性熬夜突击有效得多。有一点要特别说明我每天只维护一个“当前线索”在脑子里不会同时跟三条以上的漏洞线索。这让我不会因为项目太多而三心二意。每天的产出都记在一个简单的表格里包括项目名、文件路径、参数名、初步判断、是否值得继续跟。这份笔记让我不会在三天后忘记正在追踪的某个数据流。时间投入产出成果主要工具周一至周五 每晚2-3小时代码审计、入口点记录代码搜索引擎、抓包工具周末 半天补丁对比、PoC验证浏览器开发者工具、本地环境碎片时间写报告、查历史CVE代码托管平台搜索5.3 一点个人建议如果让我重新走一遍我会提前把“写报告的能力”排在和“挖漏洞能力”同等重要的位置。漏洞发现只是第一步把价值传递给维护者才是拿到CVE的最后一道工序。新手不妨从给开源项目提交issue开始练习把错误复现过程写得足够细腻这不仅锻炼表达也让你的名字逐渐被维护者熟悉。另外别太在意数量。三个月拿下5个CVE听起来不错但真正让我成长的是那段“长时间没发现漏洞但依然保持节奏”的经历。目标管理比技术技巧更重要。现在回头看那些弯路其实都是必经之路只不过有人早一点看到方向有人晚一点。希望这篇经验能让刚起步的朋友少走几步弯路早点拿到自己的第一批CVE。