上一章我们刚把 Web 应用拆成浏览器、HTTP 请求、Cookie、API、网关和后端服务。那些知识像一盒零件:你已经知道请求怎么走、状态放在哪里、服务端会根据什么做决定。可一旦真的面对一套应用,新的问题马上就来了:先看登录,还是先看接口?发现一个陌生子域名,要不要继续?扫描器报了高危,能不能直接写进报告?开发说已经修好,我们又该怎样确认?
很多人第一次做测试,会把流程理解成“收集信息—跑工具—验证漏洞—交报告”。这条线看起来利落,实际很容易失控。因为 Web 应用不是一堆互不相干的 URL,它有用户角色、业务状态、服务依赖和不断变化的版本。测试也不是比谁发出的请求更多,而是在有限时间里回答一组清楚的问题:哪些资产值得保护,系统本来依靠什么控制保护它,我们怎样用最小动作判断控制是否真的有效?
本章会沿用一个虚构靶场 learnbox.test。它是一套专门为课程搭建的在线学习平台,所有账号、课程、文件和订单都是测试数据。后文出现的每个动作都默认已经得到书面授权,只发生在约定环境里。我们不追求“打穿”,而是把范围、地图、假设、验证、证据、报告、清理和复测连成一条能交接的工作链。
渗透测试最容易被忽略的技术前提,其实不是工具,而是授权。你会枚举子域名,不代表每个发现的域名都能测;你拿到了教师测试账号,也不代表可以继续尝试管理员权限;你在预生产环境里获准检查文件上传,也不代表可以把同样动作搬到生产环境。
说得直接一点,技术能力描述的是“能不能做到”,授权范围决定的是“可不可以去做”。两者只要有一个没对上,测试就应该停在纸面分析或协调阶段。
一句“帮我们看看网站有没有问题”无法支持安全测试。真正可执行的范围至少要从下面几个方向写清楚。

这里有个很实用的检查方法:把你准备执行的动作写成一句完整的话——“在什么时间,从什么来源,以哪个身份,对哪个资产,用多大强度,观察什么结果”。这句话里只要有一项无法在规则中找到依据,就先不要发请求。
交战规则要解决的是测试期间真实会发生的冲突。例如,防守团队收到告警时,怎样判断流量来自测试者还是来自真正的攻击者;测试者意外看到真实个人信息时,保留多少现场、通过哪条渠道上报;新发现的服务看起来和目标有关,但不在资产清单里,谁有权把它加入范围。
一份好用的规则还会写清下面这些细节:
发现范围外入口、真实数据或可能影响可用性的异常时,正确动作通常是保存最小现场、停止相关测试并联系授权人。不要为了补齐证据继续扩大访问。边界不清时暂停,不会让测试显得保守;它说明测试过程仍然受控。
测试计划里经常藏着一些未经确认的前提,例如“预生产和生产配置基本一致”“两个学员账号足以代表普通用户”“旧版 API 已经下线”“缓存不会影响权限判断”。如果这些句子只留在脑子里,最后的结论很容易被说得过满。
更稳妥的做法是给每个假设安排一个状态:已由系统资料确认、可在测试中核对、暂时无法确认。无法确认的假设会变成报告限制,而不是被悄悄当成事实。
完成标准也要在开始前约定。它不应该是“至少找到三个高危”,而可以是:范围内入口已经建档;高价值业务流程已有测试矩阵;每条确认发现都能复核;未覆盖区域写明原因;测试产生的数据完成清理;高风险问题按约定升级;修复后有清楚的复测条件。这样即使没有发现漏洞,评估也有可说明的覆盖结果。
我们当然可以把工作分成准备、建图、建模、设计、验证、报告、复测几段,但不要误以为它们只能从左往右走一遍。真实项目更像一张状态图。
例如,你在正常浏览教师工作台时发现它还调用了一套旧接口,于是要回到范围确认和攻击面地图;你验证课程权限时发现“草稿”和“已发布”走的是两条服务链,于是要回到威胁模型;开发修了单文件下载,复测又发现批量列表沿用旧的授权逻辑,于是要重新定位根因。回头不是流程失败,没有记录自己为什么回头,才会让项目失去控制。

这张表的重点不是文档数量,而是可交接。假如测试者临时离开,接手的人能否知道哪些请求已经做过、用的什么账号、环境是什么版本、哪些结果只是线索、下一步为什么值得做?能回答,说明工作状态是显性的;回答不了,说明判断还困在个人记忆里。
几个位置很适合强制停下来确认:
每道门都允许得到“现在不继续”的结论。比如环境正在发布,测试暂停;扫描器只根据响应头猜测版本,先标成线索;修复还没部署,状态应是等待复测,不是提前关闭。把“不继续”设计成正常状态,团队才不会被“既然开始了就做完”推着走。
如果一上来就跑目录扫描,通常会得到一长串路径。它们看起来很丰富,却回答不了最关键的问题:谁在什么状态下使用这些路径,它们保护的是什么,服务端做了哪些安全决定。
攻击面不是 URL 清单。它是所有可交互入口、可到达状态、信任边界和后端依赖的组合。要画准这张地图,先把上一章学过的 Web 请求知识用在正常业务上。
先使用约定的测试账号,像正常用户一样完成一条业务链:登录、浏览课程、打开资料、提交测试作业、退出。浏览器开发者工具或代理记录 HTTP 方法、路径、参数、请求头、Cookie、重定向、响应状态和前端随后发出的 API 请求。
此时先别急着改参数。我们要回答的是:
基线是后续判断差异的参照。如果不知道正常响应应该是什么样,就很容易把缓存页面当成越权,把异步任务尚未完成当成绕过,或者只看到前端提示“失败”,却漏掉后端其实已经改变了数据。
下面是 learnbox.test 的第一版地图。它不追求一次填满,而是把已知事实和待确认问题分开。

这里故意把“草稿”和“已提交”分开。许多问题并不发生在页面入口,而发生在状态切换上:草稿能否被别人提交,已归档资源是否还可以通过旧地址访问,退出后的请求是否仍被接受。只列 /api/assignments,这些差异全会消失。
正常浏览、系统文档、前端代码、接口说明和团队访谈可以帮助理解应用,这类信息通常影响较小。主动枚举、批量抓取、模糊测试和带状态变化的请求则会直接接触目标,必须单独受规则约束。
地图中的每一条线索最好标出四项:从哪里看到、何时看到、可信度多高、怎样确认。例如前端旧代码里出现 /api/v1/,只能说明某个版本曾引用它;当前路由文档和正常流量都没有出现时,应标记成“历史线索”,不能直接写成“仍在运行的旧接口”。如果主动确认不在授权强度内,就把它留在限制里。
建图的完成标志不是“搜到再也没有新路径”,而是主要业务流程、角色、状态、入口和依赖已经能支持下一轮风险假设;剩余未知项有明确状态。Web 应用路径可能随发布不断变化,地图本来就需要更新。
地图告诉我们“系统有哪些可接触部分”,威胁模型继续追问“哪项资产需要保护,谁可能接近它,系统在哪里决定相信谁”。这一步能把“每个参数都改一遍”变成有理由、有顺序的测试。
在线学习平台的资产不只是数据库。未发布试题需要保密,成绩需要保持完整,作业提交需要可用,教师批注需要能追溯到正确身份,选课关系决定谁可以接触哪些内容。会话状态、审计记录、找回账号流程和服务配置同样是资产或保护资产的控制。
如果一开始只列“注入、跨站脚本、越权、文件上传”,很容易变成按漏洞名打勾。换成资产视角后,问题会具体许多:
浏览器把 courseId 放进请求,并不意味着它真的拥有这门课。前端把按钮隐藏,也不意味着后端接口不可调用。网关确认“这个人已经登录”,也不能替代资源服务判断“这个人能否读取这份文件”。
我们要沿着请求链寻找这些决定:身份在哪里建立,角色在哪里解析,资源归属在哪里查询,输入在哪里转换,跨服务调用使用谁的身份,缓存以什么键区分用户。每当数据或身份从较不可信的一侧进入较可信的一侧,就要问:这里依据什么接受它?这个依据能不能被客户端控制?后续服务会不会误把上游的“已认证”理解成“已授权”?

“测试一下有没有越权”太模糊。一个好假设至少包含五项:前置身份、交互入口、目标资产、预期控制、控制失效后的影响。
例如:
学员甲请求课程资料时,文件服务应根据服务端保存的选课关系判断课程归属,不能只接受浏览器传入的课程标识;否则,已登录学员可能读取未选课程的受限资料。
这句话已经告诉我们需要两个怎样的测试账号、准备什么测试课程、哪一个服务端判断值得观察,以及安全行为应该是什么。它还能被证伪:如果服务端稳定拒绝跨课程请求,日志也显示资源级授权生效,那么当前条件下这条假设没有成立。
再看两个状态类假设:
教师提交成绩后,后端应拒绝把已归档课程中的成绩重新改回草稿;否则,页面上没有按钮也无法保护成绩完整性。
用户完成登录后,系统应为已认证状态建立新的会话边界;否则,登录前的旧状态可能被错误延续到登录后。
后一条会把我们自然带到下一章的认证机制。现在先记住写法:不要从“我要试什么招式”出发,而要从“哪个安全控制可能没有守住哪项资产”出发。
风险高不等于马上做强验证。可以用下面几项共同排序:资产价值、所需身份、入口可达性、现有控制信息、影响范围、验证成本、验证本身的业务风险。
最后一项很能说明方法论的作用。它可能值得关注,但验证代价已经超过本次允许强度。把它记录成未验证风险或专项建议,比擅自制造并发压力专业得多。
一条风险假设还不是测试用例。我们需要把身份、资源、动作和业务状态展开,才能看清自己究竟比较了什么。
以课程资料为例,先创建课程甲、课程乙和两份没有敏感内容的标记文件,再绑定不同测试账号。
正向路径证明测试环境和功能本身可用,负向路径才用来观察边界。如果学员甲连自己的课程都打不开,那么“访问课程乙失败”不能证明授权有效,它可能只是环境故障、账号配置错误或文件不存在。
矩阵还会暴露容易漏掉的组合:只测读取没测修改,只比较角色没比较资源归属,只测已发布没测草稿,只看单个接口没看批量入口。路径组合会迅速增多,所以矩阵不是要求穷举,而是帮我们看到取舍,并把未覆盖项说清楚。
“学员甲访问课程乙”还不够复现。完整前置条件可以写成:应用构建版本固定;学员甲已选课程甲且未选课程乙;两门课程均处于已发布状态;课程乙中只有一份团队创建的标记文件;会话刚刚建立;缓存已按约定处理。
观察也不能只看页面提示。至少要同时留意:HTTP 状态、响应体是否包含目标内容、后端是否真的改变状态、审计日志记录了什么、异步任务是否稍后完成。前端弹出“无权限”,但后端已经提交修改,并不算安全拒绝。
每条用例还应写停止条件,例如出现真实资料、响应延迟显著偏离基线、连续异常错误、账号被锁、应用进入发布窗口。停止条件不是项目文档里的装饰,它应能让执行者在压力下立即判断要不要继续。
工具很适合做重复工作:收集已授权入口、比较大量响应头、按固定速率执行相同检查、保存原始请求、标出差异。它不擅长理解“教师可以修改自己课程,但不能修改同部门其他课程”这样的业务语义,也不知道某个订单、文件或账号是不是测试数据。
因此可以采用三步:人工走通业务并建立基线;自动化执行已经设计好的低风险重复组合;人工复核异常并回到业务状态解释结果。扫描器给出的名称、风险等级和版本推断都只是线索。没有经过环境确认、稳定复现和边界判断,就不能直接变成漏洞结论。
“工具没有报警”只能说明工具在当前配置、身份和覆盖范围下没有报告问题,不能推出应用安全。业务逻辑、资源归属和跨步骤状态往往需要人先理解,再设计针对性的比较。
看到异常响应时,人很容易兴奋:再多取几条数据,是不是更有说服力?继续换几个账号,是不是能证明影响更大?这正是测试最需要克制的时刻。
验证的目标不是展示能走多远,而是取得足够证据支持风险判断和修复。能用团队自建的一条记录证明资源边界失效,就不要读取真实用户记录;能用两个测试账号证明角色混淆,就不要枚举更多账号;能通过一次可回滚的状态变化和日志确认写入,就不要留下持久化访问。
一个现象升级为确认发现前,至少要排除这些常见干扰:
排除噪声不等于无限重复请求。你可以先核对测试数据、账号、请求关联标识和服务端日志,再用很少的次数确认结果是否稳定。
先确认基线成立。合法身份访问自己的测试资源应成功,非法组合应按设计失败;如果正向路径本身不可用,先修复环境或标记限制。
再确认异常可以重复。固定环境版本、账号、资源状态和请求条件,以很小次数复现,排除缓存、网络抖动和偶发错误。
接着确认异常跨越了安全边界。比较两个受控身份、两个资源归属或两个业务状态,说明差异来自控制失效,而不是普通功能缺陷。
然后只用自建数据证明必要影响。证据一旦足以让责任团队复现和定位,就停止扩大读取、修改或执行范围。
最小证据并不等于证据薄弱。它要求证明链条更干净:正常路径是什么,改变了哪个受控条件,安全边界在哪里,实际结果怎样偏离预期,影响由哪项测试资产体现。与其堆一百条数据,不如把这一条链讲清楚。
如果跨课程访问被服务端稳定拒绝,这条假设没有确认。记录测试条件、覆盖组合和观察结果,然后继续下一项。不要把它写成“系统不存在越权”,也不要因为没有漏洞就删除记录。
没有确认可能有多种原因:控制确实有效,测试身份不足以到达相关路径,某些状态未覆盖,环境与生产不同,或者时间窗口太短。把原因和限制写出来,下一轮评估才知道是在重复工作,还是补充新的条件。
截图很直观,但单独一张截图通常不够。两天后再看,如果不知道它来自哪个环境、什么账号、哪个资源状态、哪次请求,就无法稳定复现,更无法和服务端日志对上。
活动日志回答“测试期间做过什么”。它可以包含统一时区的时间、测试来源、目标、测试身份、动作类别、结果摘要和证据编号。即使没有发现漏洞,也应该有活动记录。它帮助团队接力,也帮助防守方把测试流量与真实异常区分开。
漏洞记录回答“哪项安全控制被什么证据证明失效”。一条发现可以按下面的字段整理。

这里刻意写“已证实边界”,而不是猜测“全部用户数据都能泄露”。事实越清楚,风险判断越可信。
原始记录用于保留当时上下文,应限制访问并按规则保存。分析时使用脱敏副本,去掉 Cookie、令牌、个人信息、内部地址等无关敏感内容。最终报告只放支撑结论所需的片段。
如果文件在团队成员之间移交,要能说清谁在什么时间、通过什么受控渠道接收。必要时为原始文件保留校验值和变更记录,避免后来无法判断内容是否被改动。报告本身会暴露漏洞、系统结构和修复状态,也属于敏感材料,不能随手放进公共聊天或个人网盘。
脱敏不是写报告前最后一步。如果只需要证明“能读取另一门测试课程中的一条标记”,就不该保存完整列表;如果响应意外包含真实用户信息,应按停止规则处理,而不是继续翻页统计影响;如果要截图,应先关闭无关标签和通知,避免把其他项目的信息一起带进去。
测试结束后还要处理代理历史、下载文件、临时脚本输出、测试令牌和本地缓存。哪些材料归档、保存多久、谁能访问、何时销毁,都应与开始前的规则对应。
报告不是测试者的战绩单。业务负责人想知道重要资产面临什么风险,开发想知道哪个控制出了问题,运维和防守团队想知道日志、网关、配置与告警该怎样调整。写作时要让这些人能从同一项发现进入自己的行动。
仍以课程资料为例:
三层分开后,报告不会用推测冒充事实,也不会因为验证克制而显得含糊。系统方可以根据日志、代码和数据关系继续确定实际影响范围。
同样是“读取了另一个对象”,如果对象是公开课程封面,和对象是未发布试题,后果完全不同。风险判断至少要说明:受影响资产、需要什么身份、是否要特殊前提、攻击是否容易重复、已证实影响属于保密性还是完整性、现有检测能否发现、影响会不会跨租户或跨组织。
一个分数可以帮助排序,但不能替代这些信息。也不要把“这次没有确认高危”写成“系统很安全”。渗透测试只是特定时间、版本、范围、身份和方法下的取样观察。
“加强权限校验”几乎不能直接执行。针对前面的资源越权,更有用的建议是:
这样写,修复目标从“堵住当前 URL”变成“恢复资源级授权控制”。根因被修好,复测也知道该往哪些邻近路径扩一圈。
可以写“在两个测试学员账号、两门自建课程和已发布状态下,跨课程读取被服务端拒绝”,不能写“系统不存在访问控制问题”。如果找回账号流程因为邮件测试环境不可用而没有完成,就明确标记未充分覆盖。失败记录、无法访问的页面、版本不一致和排除项都应该出现在限制里。
这种写法不会削弱报告。恰恰相反,它告诉读者结论能用到哪里,下一轮测试应该补什么。
测试结束不等于最后一个请求发完。为了验证业务状态,我们可能创建了账号、课程、文件、订单、会话和临时配置。它们如果留在环境里,会成为脏数据,甚至变成新的安全入口。
开始创建测试对象时就登记所有者、用途、创建时间、预计删除时间和依赖关系。结束后逐项处理:删除测试文件和记录,撤销临时令牌,关闭临时账号,恢复改动过的配置,清除本地下载与不再需要的代理数据。
有些对象不能由测试者自行删除,例如系统日志、审计记录或需要业务团队处理的数据库状态。这时不要假装“已清理”,应记录移交对象、责任人和确认结果。清理状态也属于项目证据。
开发说“已经修了”,只说明可以准备复测。正式开始前要确认修复部署在哪个构建版本,账号和测试数据是否重置,缓存、网关与依赖服务是否同步更新。
如果这些条件变了,先记录差异。否则一个请求从成功变成失败,可能只是账号被禁用、课程被删除或文件不存在,并不能证明授权控制恢复。
稳妥的复测顺序是:
“无法验证”不能写成“已修复”,“风险接受”也不是测试者单方面替业务做决定。每个状态都要绑定版本和证据。

复测的扩展范围仍受原授权约束。修复如果引入了新服务或要求切换生产环境,应先更新范围,再执行验证。闭环不是无限追踪,而是让每一个关闭结论都有足够依据。
现在把前面的做法串起来。learnbox.test 的评估目标是确认学员与教师之间的资源边界,并为下一章的认证测试准备正常会话基线。
双方约定只测试预生产主站、当前 API 和测试文件服务。邮件与支付供应商排除,测试只使用四个虚构账号和自建课程。允许手工比较请求与低频自动化,不允许压力测试、真实数据访问和持久化操作。出现真实数据、错误率异常或发布开始时立即暂停。
测试者先走完学员登录、浏览课程、下载资料、提交作业、退出,以及教师上传资料、发布课程和批改作业的正常流程。地图显示,课程页面先从课程服务取得关系,再从文件服务请求具体文件;教师草稿和已发布资源使用同一文件服务,但请求路径不同。
团队把未发布资料、选课关系、教师批注和会话状态列为关键资产。优先假设是:文件服务可能只确认用户已登录,却没有再次检查课程归属。
为此创建课程甲、课程乙,每门课放一份不同的中文标记文件。学员甲只选课程甲,学员乙只选课程乙。测试矩阵包含合法下载、跨课程下载、教师跨课程修改、退出后重放,并分别覆盖草稿与已发布状态。每条用例都写明环境版本、测试数据、预期拒绝、证据需求和停止条件。
正向基线显示学员甲可以下载课程甲的标记文件。测试者只改变受控请求中的课程标识,文件服务返回了课程乙的标记文本。固定条件后第二次结果一致。测试者没有枚举更多课程,也没有读取真实文件,而是保存了脱敏请求、响应片段、构建版本、测试课程关系和请求关联标识。
防守团队通过关联日志确认:网关验证了会话,文件服务却把“已登录”当成“能访问任意课程”,没有查询选课关系。这个发现因此从一个异常响应落到了明确的控制缺口。
退出后的旧请求则被服务端稳定拒绝。团队记录当前覆盖条件,没有把它写成认证机制绝对安全,因为找回账号、多因素认证和会话更新还没有在本章展开。
开发把资源级授权集中到文件服务的共享策略。复测先确认学员甲仍能读取课程甲,再确认课程乙请求被拒绝。随后检查预览、单文件下载和批量列表,发现前两者已经使用新策略,批量列表仍沿用旧判断,因此第一次状态是“部分修复”。
第二次修改覆盖批量入口后,团队重新检查正常流程、原路径和邻近入口,日志也能关联授权失败。最终删除测试课程与标记文件,撤销会话,按约定归档必要证据并销毁临时副本。
这个过程没有“几分钟攻陷平台”的戏剧性,却更接近真实安全工作。大部分精力花在理解系统、准备数据、比较边界、排除噪声、和防守侧对账,以及把根因写清楚。工具只是其中一环。
下一章会进入认证机制攻击。登录、找回账号、多因素认证、会话建立和退出看起来是不同功能,其实共同回答一件事:系统怎样确认“你是谁”,身份状态发生变化后,各层服务又怎样继续相信这份身份。
开始之前,把本章的方法留在手边:先确认授权和停止条件;建立正常业务基线;从资产、角色与信任边界提出可证伪假设;用测试矩阵控制覆盖;以自建数据取得最小充分证据;把活动、版本、身份和限制记清楚;最后让修复、复测和清理形成闭环。
你可以用下面三道题检查自己是否抓住了重点。
最后与防御侧记录对账。确认请求经过哪些组件、哪一层缺少判断、日志是否捕获到关联信息,再把根因假设交给开发核对。