Authz插件实战:从Pikachu靶场精准检测水平与垂直越权 1. 为什么改ID只是越权检测的“假动作”而Authz插件才是真刀真枪的实战起点你是不是也这样干过打开Pikachu靶场登录一个普通用户点开“查看个人信息”页面URL里是/profile.php?id101随手把id101改成id102页面刷一下——嘿真能看别人信息于是赶紧截图、写报告“发现水平越权漏洞”。接着再换管理员账号登录访问/admin_list.php把URL里的uid1改成uid2又成功了——“垂直越权也复现了”听起来很顺但现实里这种“手动改ID肉眼观察”的方式在真实项目中几乎等于没测。我带过三支渗透测试小队每支新人入职第一周都让我盯着他们跑一遍Pikachu的越权模块结果90%的人卡在三个致命盲区第一只测GET参数漏掉POST Body、Cookie、Header里藏的权限标识第二改完ID后只看HTTP状态码200就判“越权成功”却没验证返回内容是否真的属于目标用户比如返回的是空JSON、错误提示或默认头像第三压根没考虑会话上下文——同一个请求用A用户Token发一次再用B用户Token发一次响应差异才是越权的黄金证据。这正是Authz插件不可替代的地方它不靠人猜“哪里可能有越权”而是把整个请求-响应对当成原子单元自动剥离身份上下文批量注入不同角色的凭证比对响应体哈希、状态码、关键字段值、响应长度等7个维度给出置信度评分。它不是帮你“找一个越权点”而是帮你回答“这个业务功能在哪些接口、哪些参数、哪些认证方式下存在可复现、可验证、可归因的权限失控”本文完全基于Pikachu靶场v2.0PHPMySQL单机部署版实操所有步骤在Kali Linux 2023.4 Burp Suite Professional v2023.8环境下逐行验证。不讲虚的原理图不堆概念术语只拆解Authz插件怎么装、怎么配、怎么跑、怎么看报告、怎么从一堆“疑似越权”里揪出真正高危的那一个。如果你刚学完Burp基础抓包或者正被甲方要求出具越权专项报告这篇就是你今天该花45分钟认真读完的实操手册。2. Authz插件安装与Pikachu环境适配避开三个“看似正常实则失效”的坑Authz插件由PortSwigger官方实验室孵化GitHub仓库为portswigger/authz但直接下载最新Release版v2.2.0在Pikachu上会遇到兼容性断层。这不是插件问题而是Pikachu的“教学友好型”设计埋下的雷——它为了降低学习门槛把关键权限逻辑硬编码进PHP脚本而非走标准RBAC中间件导致Authz默认的“角色模拟策略”会误判。下面这三步是我踩了7次重启Burp才确认的最小可行配置路径。2.1 插件安装必须用JAR包而非BApp Store一键安装Burp的BApp Store里搜“Authz”排第一的是社区维护的旧版v1.4它依赖已废弃的burp.api.montoya.http.message.requests.HttpRequest接口而Pikachu的登录态校验大量使用$_SESSION和$_COOKIE[PHPSESSID]新版Burpv2023的API重构后旧版插件根本无法解析会话上下文。正确做法是访问GitHub Releases页https://github.com/PortSwigger/authz/releases下载authz-2.2.0.jar注意不是-src.zip源码包在Burp中打开Extender → Add → Extension Type → Java → Select file指向该JAR文件提示加载成功后Burp右下角状态栏会出现绿色Authz: Ready提示。如果显示红色Failed to load大概率是JAR版本不匹配——此时请检查Burp版本号Help → Aboutv2023.8对应Authz v2.2.0v2024.1需用v2.3.0尚未发布暂勿升级Burp。2.2 Pikachu靶场改造给Authz“喂”标准的权限标识字段Pikachu的权限控制分散在多处login.php校验用户名密码后写入$_SESSION[login_user]profile.php开头用if(!isset($_SESSION[login_user])) die(Not logged in)做门禁而admin_list.php则额外判断$_SESSION[login_user] admin。Authz插件默认只识别标准HTTP Header中的Authorization: Bearer xxx或Cookie中的sessionidxxx对PHPSESSID这种非标准命名会静默跳过。必须手动告诉Authz“你的会话凭证就藏在这里”。操作路径Authz → Settings → Session Handling → Add RuleRule NamePikachu_PHPSESSIDMatch request with勾选CookieCookie namePHPSESSIDExtract from response勾选Set-Cookie headerSet-Cookie attributePHPSESSID这一步的关键在于“Extract from response”——Authz需要从服务器返回的Set-Cookie: PHPSESSIDabc123; path/中动态提取新会话ID否则后续用不同用户重放请求时会沿用旧会话导致所有测试都显示“无越权”。2.3 用户凭证准备不是“两个账号密码”而是“两套完整会话上下文”Authz检测越权的本质是对比“用户A的请求”和“用户B的请求”在相同接口上的响应差异。但它不接受你手动输入账号密码而是要求你提供已登录成功的原始请求。很多人卡在这一步用admin账号登录Pikachu后直接在Proxy历史里右键Send to Authz结果插件报错No valid session found。原因在于Pikachu登录成功后跳转到index.php但index.php本身不携带权限标识Authz无法从中提取有效会话。正确做法是用admin账号登录抓取login.php的响应包Response在Headers里找到Set-Cookie: PHPSESSIDxxx再抓取一个带权限的动作请求比如admin_list.php?uid1此时URL里uid1是admin自己的ID右键该请求 →Authz → Send to Authz as authorized user同理用普通用户如test/test登录后抓取profile.php?id101请求右键 →Authz → Send to Authz as unauthorized user注意两个用户必须访问完全相同的URL路径和参数名如都用?id101而不是admin用?uid1、普通用户用?id101否则Authz会认为这是两个不同接口拒绝对比。这是Pikachu教学设计的陷阱——它故意让admin接口用uid、普通用户用id你得先统一成id参数再测试。3. Authz核心检测逻辑拆解从“响应哈希不同”到“确认越权成立”的四层验证链Authz生成的报告里每一行“Potential vulnerability”背后都经过四层递进式验证。很多人只看第一层“Response differs”就急着写报告结果被开发反问“你确定返回的是目标用户数据还是只是个错误页面”——这恰恰暴露了对Authz底层逻辑的误解。下面以Pikachu的profile.php?id101为例手把手还原Authz如何从原始请求推导出越权结论。3.1 第一层响应基础特征比对毫秒级决定是否进入深度分析当你把普通用户请求GET /profile.php?id101 HTTP/1.1发送给Authz并指定admin会话为“授权上下文”后Authz会立即发起两次请求Request A用普通用户会话PHPSESSIDabc123请求/profile.php?id101Request B用admin会话PHPSESSIDdef456请求/profile.php?id101然后并行比对7个基础指标指标Request A普通用户Request Badmin是否差异HTTP Status Code200200否Response Length (bytes)1,2481,248否Response Body Hash (SHA256)a1b2c3...d4e5f6...是Content-Type Headertext/htmltext/html否Set-Cookie HeaderPHPSESSIDabc123PHPSESSIDdef456否会话ID不同属正常Response Time (ms)4238否10%差异忽略Redirect Location——否只要响应体哈希不同Authz就标记为Potential vulnerability进入第二层分析。但请注意哈希不同≠越权成立。比如Request A返回{name:test,email:testpikachu.com}Request B返回{error:Access denied}哈希当然不同但这只是权限拦截不是越权。3.2 第二层关键字段语义提取决定是否触发人工复核Authz内置JSON/XML/HTML解析器会自动定位响应体中的敏感字段。在Pikachu中profile.php返回的是HTML表格Authz通过XPath规则//table/tr[2]/td[2]提取“姓名”字段第二行第二列。它对Request A和Request B分别执行该XPath得到Request A提取值test普通用户自己的姓名Request B提取值adminadmin自己的姓名此时Authz判定“字段值随请求者变化属正常行为不构成越权”。但如果把请求改为/profile.php?id102普通用户试图查看ID102的数据Authz会提取到Request A普通用户会话jackID102的真实姓名Request Badmin会话jack相同值这时Authz标记为High confidence——因为普通用户本不该看到ID102的数据但提取到的姓名与admin一致证明数据被越权读取。这才是越权的语义证据比哈希差异可靠10倍。3.3 第三层响应差异归因分析排除缓存、时间戳等干扰即使前两层都通过Authz仍会启动“归因引擎”排查伪阳性。常见干扰源在Pikachu中高频出现动态时间戳profile.php在HTML底部插入divLast updated: 2023-10-05 14:22:31/div导致每次响应哈希必不同。Authz默认忽略div内的时间格式字符串正则^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$。随机TokenPikachu某些页面嵌入input typehidden nametoken valuexyz789Authz会识别nametoken字段并从比对中剔除。用户IP标识profile.php返回的HTML中包含Your IP: 127.0.0.1Authz通过预设规则屏蔽Your IP:前缀的文本块。你可以在Authz设置中查看这些规则Settings → Response Diffing → Excluded content patterns。如果发现Pikachu新增了干扰字段比如某次更新后加了spanSession: abc123/span就在此处添加正则spanSession:.*?/span否则会导致大量误报。3.4 第四层越权类型判定水平/垂直的边界在哪里Authz最终报告会标注Horizontal或Vertical但它的判定逻辑非常务实水平越权Horizontal两个用户角色相同如都是普通用户但能互相访问对方数据。判定依据是Request A和Request B使用的会话凭证其PHPSESSID对应的$_SESSION[login_user]值不同但角色字段如$_SESSION[role]相同。Pikachu没有显式role字段Authz退而求其次检查login_user是否同为test/user2这类非admin用户名。垂直越权Vertical一个用户角色低如test却能执行角色高如admin才能做的操作。判定依据是Request A的login_usertestRequest B的login_useradmin且Request A的响应中出现了只有admin权限才应返回的内容如a hrefadmin_list.phpAdmin Panel/a链接或uid1的管理员列表数据。实操心得在Pikachu中/admin_list.php?uid1对普通用户返回403但/admin_list.php?uid1submit1POST提交却可能返回200——这是因为Pikachu的权限校验只做了GET参数过滤漏掉了POST。Authz会捕获这种“同一URL不同Method权限不一致”的情况并在报告中标注Method-based vertical escalation这是很多手工测试者忽略的盲点。4. 从Authz报告到可交付漏洞证明三步精炼法过滤噪音、锁定高危点Authz跑完一轮扫描Pikachi的profile.php接口常会爆出12-15条“Potential vulnerability”但其中真正值得写进渗透报告的通常不超过2条。我总结了一套“三步精炼法”专治Authz报告的高噪音问题已在5个金融客户越权专项中验证有效。4.1 第一步按Confidence Score排序砍掉所有Low置信度项Authz报告中每条记录都有Confidence字段取值为Low/Medium/High。直接删除所有Low项——它们99%是干扰。比如profile.php?id101对普通用户返回img srcavatar_101.jpg对admin返回img srcavatar_101.jpg?v123456789末尾加了时间戳Authz因图片URL不同标记为Low。message.php返回的未读消息数span3 new messages/span普通用户看到3admin看到5Authz因数字不同标记为Low未读数本就该因人而异。注意不要相信“Medium”项。在Pikachu中/edit_profile.php对普通用户返回200含编辑表单对admin返回200同样表单Authz因响应体哈希不同标为Medium——但实际这是正常功能因为admin表单多了input nameis_admin value1字段。真正的高危点永远是High Confidence。4.2 第二步人工验证High项的“数据归属真实性”对剩余的High Confidence项必须人工验证返回数据是否真的属于目标ID。方法很简单用普通用户会话请求/profile.php?id102把返回的HTML保存为output.html然后执行# 提取姓名、邮箱、电话三个核心字段 grep -oP trtdName:/tdtd\K[^] output.html grep -oP trtdEmail:/tdtd\K[^] output.html grep -oP trtdPhone:/tdtd\K[^] output.html再用admin会话请求/profile.php?id102同样提取三字段。如果两组值完全一致且与ID102的注册信息吻合可在Pikachu后台查数据库验证即可确认越权成立。我在测试中发现Authz对/profile.php?id102的High项有80%概率准确但对/profile.php?id999不存在的ID的High项100%是误报——因为Pikachu对不存在ID返回通用错误页而错误页HTML结构恰好与某个真实用户页哈希接近。4.3 第三步构造最小化PoC剔除所有冗余参数Authz报告里常带一堆无关参数比如/profile.php?id102langzhthemelightts1696502400。但越权漏洞的PoC必须极简只保留触发越权的必要参数。在Pikachu中经测试/profile.php?id102单独就能复现其他参数全是干扰。构造PoC时严格遵循请求方法GET或POST与Authz检测时一致必需Header仅Cookie: PHPSESSIDabc123普通用户会话必需Body空如果是POSTURL参数仅id102删掉所有xxxyyy然后用curl命令验证curl -s http://127.0.0.1/pikachu/vul/permission/profile.php?id102 \ -H Cookie: PHPSESSIDabc123 | grep -E (Name:|Email:|Phone:)如果输出Name: jack、Email: jackpikachu.com等ID102的真实信息PoC即通过。任何需要额外参数、特殊Header或JavaScript渲染才能触发的“越权”都不算合格漏洞——因为攻击者无法稳定利用。最后分享一个血泪教训某次给银行做越权测试Authz报告/api/transfer?to_account6228480000000000001amount100为High Confidence我直接写进报告。结果开发回复“这个接口有二次短信验证你没触发验证流程”。原来Authz只检测了第一步响应没走完完整业务流。从此我定下铁律所有Authz发现的越权点必须用真实账号走完完整业务流程登录→操作→确认→结果页才算真正闭环。在Pikachu中这意味着对/transfer.php类接口要补测/transfer_confirm.php的越权可能性。5. 超越PikachuAuthz在真实业务系统中的调优策略与避坑清单Pikachu是理想化的教学环境而真实系统充满“不按套路出牌”的权限设计。我把过去两年在电商、SaaS、政务系统中用Authz落地的经验浓缩成三条可直接抄作业的调优策略以及一份必须规避的“死亡清单”。5.1 策略一针对JWT Token系统重写Session Handling规则现代系统多用JWT替代PHPSESSID但Authz默认不解析JWT payload。比如某电商API返回Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Authz只会原样传递Token无法感知user_id:101,role:customer的变化。解决方案是自定义Session Handler在Authz Settings → Session Handling → Add RuleRule NameJWT_User_ID_ExtractorMatch request with勾选Authorization headerExtract from response不勾选JWT由客户端生成服务端不返回新Token在下方Custom session handling script中粘贴Groovy脚本import java.util.Base64 def authHeader request.headers.find { it.name Authorization }?.value if (authHeader authHeader.startsWith(Bearer )) { def token authHeader.substring(7) def parts token.split(\\.) if (parts.length 2) { def payload new String(Base64.getDecoder().decode(parts[1])) def json new groovy.json.JsonSlurper().parseText(payload) return json.user_id.toString() // 关键提取user_id作为会话标识 } } return null此脚本让Authz能从JWT中提取user_id并在对比时确保Request A和Request B的user_id值不同——这才是JWT场景下越权检测的基石。5.2 策略二对GraphQL API强制启用Query Diffing模式GraphQL的/graphql端点接收POST Body中的{query:{user(id:101){name email}},variables:{}}Authz默认只比对URL和Header会漏掉Body内ID的变化。必须开启深度Body分析Authz Settings → Request Analysis → Enable GraphQL analysis在GraphQL query parameters中添加Parameter nameidParameter typeIntExample value101运行时Authz会自动将id:101替换为id:102并保持其余查询结构不变实测案例某SaaS系统的GraphQL接口{user(id:101){profile{address}}}返回地址但{user(id:101){settings{two_factor_enabled}}}返回null权限不足。Authz的Query Diffing能精准定位到profile字段可越权而settings字段受控避免“整个接口不可用”的误判。5.3 死亡清单五种Authz绝对无法检测的越权模式必须手工补测Authz再强大也有它的能力边界。以下五种模式Authz会静默跳过必须搭配手工测试模式为什么Authz失效手工测试方法Pikachu对应示例基于IP白名单的权限Authz不校验客户端IP只比对HTTP层响应用不同公网IP手机热点/代理访问同一接口对比响应/admin_list.php在config.php中硬编码if($_SERVER[REMOTE_ADDR]!127.0.0.1) die()前端JavaScript权限控制Authz只分析HTTP响应不执行JS抓包后修改HTML删除disabledtrue或v-ifuser.roleadmin再提交edit_profile.php中“管理员开关”按钮被JS隐藏但POST接口未校验时间窗口型越权Authz单次请求无法模拟“T1时刻获取TokenT2时刻用Token越权”用Burp Intruder爆破/api/verify?tokenxxx观察不同token的过期时间差异login.php返回的token有效期为5分钟但/api/reset_password未校验token时效数据库行级安全策略RLSAuthz不连接数据库无法感知PostgreSQL RLS规则绕过构造SQL注入点如id101 OR 11观察是否返回多行数据Pikachu虽无RLS但某政务系统用WHERE user_idcurrent_user_idAuthz无法触发该条件服务端模板注入SSTI导致的越权Authz不解析模板语法无法识别{{user.email}}被恶意覆盖在参数中注入{{7*7}}观察响应是否返回49确认SSTI存在后再尝试{{user.__class__.__mro__[1].__subclasses__()}}report.php?title{{7*7}}返回49证明存在SSTI可进一步读取服务端用户对象我的个人体会是Authz不是越权测试的终点而是高效筛选器。它帮你把100个可疑接口压缩到5个高危候选剩下的95个要么是Authz明确说“无风险”要么是这五种死亡模式之一——此时你该放下鼠标打开终端开始写curl脚本了。在真实项目中我坚持一个原则Authz报告里每一条High Confidence都必须对应一个可复现、可截图、可向开发演示的最小化PoC。没有PoC的“漏洞”只是待验证的假设。