首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >渗透测试中 403 与 401 绕过的18种常见方法

渗透测试中 403 与 401 绕过的18种常见方法

原创
作者头像
HexC2
修改于 2026-11-10 10:37:55
修改于 2026-11-10 10:37:55
462
举报
文章被收录于专栏:渗透测试专栏渗透测试专栏

访问控制失败通常不是一处判断写错,它通常有两处判断不一致——一处负责拦,一处负责放,中间没有人对账。

下面十八个靶场同属一个入口:/admin。前十个返回 403,后八个返回 401。每一关的复现都从访问开始,记录服务器的返回,再逐步定位判断依据。本文共18个靶场,每个靶场都有独立的环境进行漏洞复现

在线靶场:https://hexc2.com/

图片
图片

一、只认请求头的来源判断

靶场地址:https://hexc2.com/laboratory/targets/120

访问 /admin,返回 403。页面提示该面板仅允许内网访问,响应头中 Server: Apache/2.4.54 (Debian),Content-Type: text/html,没有其他可用的版本或路径线索。

判断依据在服务端,且与网络层相关。尝试在请求头中声明来源:

代码语言:javascript
复制
httpGET /admin HTTP/1.1
Host: 64.90.22.173:32864
X-Forwarded-For: 127.0.0.1
第一关 Burp 请求
第一关 Burp 请求

请求头末尾增加了 X-Forwarded-For,其余请求头保持原样

状态码从 403 变为 200,响应体中出现 STATUS AUTHORIZED 与 FLAG hexc2{access-l01-demo}。

第一关页面
第一关页面

管理面板返回,flag 位于第四行

服务端使用 $_SERVER['HTTP_X_FORWARDED_FOR'] 作为来源判断依据。该值来自请求头,属于客户端可控数据;真实来源记录在 TCP 连接的 REMOTE_ADDR 中,无法通过请求头改写。以 X-Real-IP 替代同样有效。

部署链路中需要注意的是 nginx 的 $proxy_add_x_forwarded_for,该变量为追加行为,客户端提交的值会被保留在最前。后端若取第一个 IP,等同于采信客户端声明。

修复时应仅使用 REMOTE_ADDR;存在反向代理时,由代理覆盖该头,应用侧只信任代理写入的值。

二、精确匹配漏掉的等价路径

靶场地址:https://hexc2.com/laboratory/targets/121

访问 /admin 返回 403,响应体是一段固定文案,不包含路径或框架信息。测试相邻路径形态,观察拦截是否随路径变化:

代码语言:javascript
复制
httpGET /admin/ HTTP/1.1
Host: 64.90.22.173:32865
第二关 Burp 请求
第二关 Burp 请求

请求行结尾由 /admin 改为 /admin/,其余不变

第二关页面
第二关页面

面板返回,flag 位于第四行

拦截规则采用精确字符串比对($path === '/admin'),而 Web 服务器在将请求交给应用前会执行路径规范化:合并多余斜杠、解析 /./ 段。三种变体在应用层均解析为 /admin,但在拦截层与原字符串不相等。

需要注意 //admin 在该环境不成立。Apache 将双斜杠前缀视为绝对 URI 的起始,按主机名解析,请求不会落入管理面板分支。

修复方式为在规范化之后执行判断,或基于路由解析结果鉴权,而非原始 URL 字符串。

三、大小写语义不一致

靶场地址:https://hexc2.com/laboratory/targets/122

/admin 返回 403。将首字母改为大写:

代码语言:javascript
复制
httpGET /Admin HTTP/1.1
Host: 64.90.22.173:32866
第三关 Burp 请求
第三关 Burp 请求

请求行中的 admin 改为 Admin

第三关页面
第三关页面

面板返回

拦截层使用 str_contains($path, 'admin'),区分大小写;路由层使用 stripos,不区分。两个函数处理同一字符串,得出不同结论。

这一差异在同源环境下容易被忽略,在 Windows/IIS 上则更为常见:文件系统本身不区分大小写,而规则按小写编写。

修复方式为统一比较语义——检测与路由使用同一套大小写规则,或统一归一化后再比对。

四、解码时机差异

靶场地址:https://hexc2.com/laboratory/targets/123

/admin 返回 403。将路径中的一个字符做 URL 编码:

代码语言:javascript
复制
httpGET /%61dmin HTTP/1.1
Host: 64.90.22.173:32867

%61 对应字符 a。

第四关 Burp 请求
第四关 Burp 请求

请求行中的 a 被替换为 %61

第四关页面
第四关页面

面板返回

拦截层读取 $_SERVER['REQUEST_URI'],该值为未解码的原始 URI,匹配不到 admin;路由层读取 parse_url() 解析后的 path,此时已完成解码,得到 /admin。

问题不在编码本身,而在解码与检测的先后顺序。若前置组件解码、后端不解码,或解码次数不同,表现会随之变化。

修复方式为先解码再检测,且仅解码一次。多次解码不构成修复,%2561 可绕过解码两次的实现。

五、分号之后的路径参数

靶场地址:https://hexc2.com/laboratory/targets/124

/admin 返回 403。在路径后附加分号参数:

代码语言:javascript
复制
httpGET /admin;.css HTTP/1.1
Host: 64.90.22.173:32868
第五关 Burp 请求
第五关 Burp 请求

请求行增加了 ;.css

第五关页面
第五关页面

面板返回

分号之后的内容属于矩阵参数(matrix parameter)。Tomcat、Spring 等容器会将其从路径主体中剥离,作为参数处理,路径本身仍为 /admin。字符串比对看到的是完整字符串,两者不相等。

这一手法常见于 Java 技术栈,PHP 环境相对少见。起作用的是分号语义,与 .css 后缀无关——替换为任意参数名结果一致。

修复方式为在规范化阶段剥离分号及其后内容,或明确禁用路径参数。

六、枚举式黑名单

靶场地址:https://hexc2.com/laboratory/targets/125

/admin 与 /admin/ 均返回 403。追加常见后缀:

代码语言:javascript
复制
httpGET /admin.json HTTP/1.1
Host: 64.90.22.173:32869
第六关 Burp 请求
第六关 Burp 请求

请求行增加了 .json 后缀

第六关页面
第六关页面

面板返回

拦截层枚举了两个精确字符串,未覆盖管理面板的其他路由写法。路由层对后缀不敏感,仍解析至管理控制器。

黑名单的本质局限在于需要穷举全部变体,而变体空间不可穷举。补充 .json 之后,.php、.xml、.bak 仍在空间内。

修复方式为改用白名单:明确公开的路径(首页、静态资源、登录页)之外一律鉴权。同一敏感资源的全部路由变体应共享同一条权限检查。

七、按方法分支的检查

靶场地址:https://hexc2.com/laboratory/targets/126

GET /admin 返回 403。将方法改为 POST,请求体为空:

代码语言:javascript
复制
httpPOST /admin HTTP/1.1
Host: 64.90.22.173:32870
Content-Length: 0
第七关 Burp 请求
第七关 Burp 请求

请求行的方法由 GET 改为 POST

第七关页面
第七关页面

面板返回

拦截逻辑实现于处理 GET 查询参数的分支内,POST 分支未挂载同等检查。路由层不区分方法,仍将请求交给管理控制器。

同类问题在 WAF 中表现为:仅检查 URL 查询串,不解析请求体。将参数移入 body 即可绕过。

修复方式为按路由鉴权,所有方法进入同一入口执行同一判断,避免「某方法无需检查」的例外设计。

八、代理与 WAF 读取不同字段

靶场地址:https://hexc2.com/laboratory/targets/127

访问根路径 / 返回 403。在请求中添加代理重写头:

代码语言:javascript
复制
httpGET / HTTP/1.1
Host: 64.90.22.173:32871
X-Original-URL: /admin
第八关 Burp 请求
第八关 Burp 请求

请求行为 /,X-Original-URL 指向 /admin

第八关页面
第八关页面

面板返回,页面标题包含 via X-Original-URL 标记

WAF 检查请求行,读到的路径是 /;反向代理读取 X-Original-URL,将其作为转发目标,后端实际收到 /admin。两个组件对同一请求的解释不一致。

此问题在 IIS + ARR 组合中较为典型,X-Rewrite-URL 具有相同效果。

修复方式为在代理层剥离客户端可控的重写头,并将鉴权置于最终路由之后。

九、以静态目录前缀放行

靶场地址:https://hexc2.com/laboratory/targets/128

访问 /admin 返回 403。测试以 /static/ 开头的路径:

代码语言:javascript
复制
httpGET /static/../admin HTTP/1.1
Host: 64.90.22.173:32872

客户端需保留 .. 不被提前规范化,命令行使用 curl --path-as-is,或在 Repeater 中直接发送原始路径。

第九关 Burp 请求
第九关 Burp 请求

路径前缀为 /static/,经规范化后指向 /admin

第九关页面
第九关页面

面板返回

放行条件为「路径以 /static/ 开头」。该请求满足前缀条件,但规范化后脱离静态目录,指向 /admin。判断依据与执行结果并非同一份数据。

浏览器地址栏通常会自动折叠 ..,因此同一路径在浏览器与命令行中的结果可能不同,这是客户端提前规范化所致,非服务端差异。

修复方式为规范化后使用 realpath 校验目标仍位于允许目录内,不以前缀匹配作为访问控制依据。

十、协议版本落入兼容分支

靶场地址:https://hexc2.com/laboratory/targets/129

GET /admin 使用 HTTP/1.1 返回 403,响应头正常。将协议版本降为 1.0:

代码语言:javascript
复制
httpGET /admin HTTP/1.0
Host: 64.90.22.173:32873

命令行对应 curl --http1.0,Repeater 右上角的下拉框可切换版本。

第十关 Burp 请求
第十关 Burp 请求

请求行结尾的 1.1 改为 1.0

第十关页面
第十关页面

面板返回,页面标注 HTTP/1.0

过滤规则基于 HTTP/1.1 的请求结构实现,1.0 请求落入未处理的兼容分支,默认放行。

这是解析差异在协议层的体现。同类场景中更常见的是 Transfer-Encoding: chunked:WAF 不解析分块编码的请求体,后端正常解析,检查范围与实际内容不一致。

此类问题的共同点是「无法解析即放行」。状态码上仅表现为少一次 403,实际是整个请求绕过了检查。

修复方式为检测组件与后端采用一致的协议解析实现;无法完整解析的请求应直接拒绝,而非放行。

十一、出厂凭据

靶场地址:https://hexc2.com/laboratory/targets/130

访问 /admin 返回 401,响应头包含 WWW-Authenticate: Basic realm="Admin Console",说明启用 HTTP Basic 认证。

浏览器会弹出认证框,但该认证在协议层仅为一行请求头。尝试出厂凭据:

代码语言:javascript
复制
httpGET /admin HTTP/1.1
Host: 64.90.22.173:32874
Authorization: Basic YWRtaW46YWRtaW4=

YWRtaW46YWRtaW4= 解码后为 admin:admin。

第十一关 Burp 请求
第十一关 Burp 请求

Authorization 头中为默认凭据

第十一关页面
第十一关页面

认证通过,面板返回

系统预置了弱口令,部署时未强制修改,且登录失败无次数限制。

在 Basic 认证场景中,浏览器弹窗与手动添加请求头效果一致,抓包与重放不受弹窗影响。

修复方式为部署流程强制修改默认口令,或不预设固定口令,初始化时生成随机值写入一次性文件。

十二、认证范围与路由范围不一致

靶场地址:https://hexc2.com/laboratory/targets/131

/admin 返回 401 并触发认证。对路径做变形:

代码语言:javascript
复制
httpGET /admin/..;/ HTTP/1.1
Host: 64.90.22.173:32875

客户端需保留原始路径,使用 curl --path-as-is 或在 Repeater 中直接发送。

第十二关 Burp 请求
第十二关 Burp 请求

路径为 /admin/..;/,未携带 Authorization

第十二关页面
第十二关页面

面板返回,未触发认证

认证规则挂在 /admin 这一 location 上。路径变形后请求落在认证区域之外,而后端路由经过解析仍指向管理面板。

鉴权按字面路径匹配,路由按语义解析,两者覆盖范围不一致。

修复方式为在路由解析之后执行鉴权,或采用默认拒绝、显式放行的白名单模型。

十三、认证未覆盖的方法

靶场地址:https://hexc2.com/laboratory/targets/132

GET /admin 返回 401。将方法改为 OPTIONS:

代码语言:javascript
复制
httpOPTIONS /admin HTTP/1.1
Host: 64.90.22.173:32876
第十三关 Burp 请求
第十三关 Burp 请求

请求行的方法为 OPTIONS

第十三关页面
第十三关页面

面板返回,响应头包含 Allow: GET, POST, OPTIONS

认证逻辑以方法为条件实现,仅覆盖 GET 与 POST,其余方法直接进入业务处理。

响应头中的 Allow 字段由服务器生成,直接列出了支持的方法,可作为后续测试的参考。

修复方式为先行鉴权、再按方法分派,不保留「该方法无需鉴权」的例外。

十四、遗留认证通道

靶场地址:https://hexc2.com/laboratory/targets/133

/admin 返回 401。携带 Authorization 头访问时返回 403,说明该通道被显式禁用。转而测试 Cookie 通道:

代码语言:javascript
复制
httpGET /admin HTTP/1.1
Host: 64.90.22.173:32877
Cookie: auth=YWRtaW46YWRtaW4=
第十四关 Burp 请求
第十四关 Burp 请求

凭据改由 Cookie 传递

第十四关页面
第十四关页面

面板返回

系统在加固主认证通道的同时,保留了基于 Cookie 的旧通道,凭据格式仍为 base64 编码的用户名与口令。

多个认证入口并存时,安全性取决于最弱的一条。主通道的限制不作用于旧通道。

修复方式为下线遗留通道;过渡期内为旧通道设置失效时间,并与主通道复用同一套校验逻辑。

十五、来源白名单作用于认证流程

靶场地址:https://hexc2.com/laboratory/targets/134

访问 /admin 返回 401,提示该面板仅对内网开放。该提示与第一关相同,仅状态码不同。

添加来源头:

代码语言:javascript
复制
httpGET /admin HTTP/1.1
Host: 64.90.22.173:32878
X-Forwarded-For: 127.0.0.1
第十五关 Burp 请求
第十五关 Burp 请求

与第一关使用同一请求头

第十五关页面
第十五关页面

面板返回

判断依据仍为客户端可控的请求头,与第一关同源。

403 与 401 的差异仅在响应语义:前者表示禁止访问,后者表示需要身份。二者都可能由同一个来源判断触发,绕过手法一致。

修复方式为将网络层访问控制交由网络层实现(安全组、VPN、mTLS),应用层不以请求头判断来源。

十六、备份目录暴露仓库配置

靶场地址:https://hexc2.com/laboratory/targets/135

该靶场无登录入口,直接从目录探测开始。请求备份目录下的仓库配置:

代码语言:javascript
复制
textGET /backup/.git/config     200
第十六关 Burp 请求
第十六关 Burp 请求

仓库配置可被直接读取

响应为纯文本,其中 [remote "origin"] 段的 url 字段包含明文凭据:

代码语言:javascript
复制
ini[remote "origin"]
url = https://admin:LetMeIn2026@git.internal/hexc2/panel.git

以该凭据访问管理面板:

代码语言:javascript
复制
httpGET /admin HTTP/1.1
Host: 64.90.22.173:32879
Authorization: Basic YWRtaW46TGV0TWVJbjIwMjY=
第十六关第二步
第十六关第二步

使用泄露的凭据通过认证

第十六关页面
第十六关页面

面板返回

.git 目录被 Web 服务器作为静态资源对外提供。仓库配置中携带凭据的 remote URL 通常为免密推送而设置,一旦配置目录可读,凭据随之泄露。

此路径与注入类漏洞无关,属于文件暴露导致的凭据失守,危害等同于直接获得管理权限。

修复方式为禁止 Web 访问 .git、备份目录及 .env 等敏感路径;凭据交由 CI 的 secret 管理,不写入 remote URL。

十七、未纳入鉴权的接口

靶场地址:https://hexc2.com/laboratory/targets/136

从前端页面观察,管理功能位于 /admin,访问该路径需要认证。检查同域下的接口路径:

代码语言:javascript
复制
httpGET /api/v1/internal/panel HTTP/1.1
Host: 64.90.22.173:32880
第十七关 Burp 请求
第十七关 Burp 请求

请求未携带任何凭据

第十七关页面
第十七关页面

响应为 JSON,role 字段为 administrator

响应体:

代码语言:javascript
复制
json{"code":0,"role":"administrator","flag":"hexc2{access-l17-demo}"}

同一业务能力暴露了两个入口,页面路由实施了鉴权,接口未纳入权限检查范围。

攻击面按入口计算,不按功能计算。只要存在一个未鉴权的入口,其余入口的加固不构成有效防护。

修复方式为建立接口清单,网关层统一鉴权,后端接口默认要求认证并按白名单放行。

十八、登录失败的响应时延

靶场地址:https://hexc2.com/laboratory/targets/137

/admin 返回 401,用户名错误与口令错误的响应内容完全一致,无法从文案判断账号是否存在。

对两组请求计时:

代码语言:javascript
复制
bashtime curl -u admin:wrong http://64.90.22.173:32881/admin
time curl -u nobody:wrong http://64.90.22.173:32881/admin
第十八关 Burp 请求
第十八关 Burp 请求

确认用户名后使用正确口令

第十八关页面
第十八关页面

面板返回

admin 一组稳定多出约 350ms。服务端对存在的用户名执行了 usleep(350000),不存在的用户名提前返回,两条分支耗时不一致。

口令均为 wrong,唯一变量是用户名,时间差暴露了后端执行到哪一分支。据此可确认有效用户名,将爆破空间由「用户名 × 口令」压缩至「口令」一维。

确认用户名后使用 admin:admin123 通过认证。

修复方式为两条分支执行等时处理:口令错误同样执行口令哈希运算或固定延时,响应内容与状态码保持一致。

小结

十八个靶场的入口相同,绕过点分布在协议、路径、方法、凭据与响应时序上。

其中十四关的成因可归结为同一模式:检查环节与执行环节处理的数据不一致,或检查未覆盖执行路径的全部变体。其余四关分别对应默认凭据、文件暴露导致的凭据泄露、未鉴权接口与侧信道。

排查此类问题时可沿用三条判断:检查读取的是哪份数据;执行读取的是哪份数据;两份数据在什么条件下出现差异。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、只认请求头的来源判断
  • 二、精确匹配漏掉的等价路径
  • 三、大小写语义不一致
  • 四、解码时机差异
  • 五、分号之后的路径参数
  • 六、枚举式黑名单
  • 七、按方法分支的检查
  • 八、代理与 WAF 读取不同字段
  • 九、以静态目录前缀放行
  • 十、协议版本落入兼容分支
  • 十一、出厂凭据
  • 十二、认证范围与路由范围不一致
  • 十三、认证未覆盖的方法
  • 十四、遗留认证通道
  • 十五、来源白名单作用于认证流程
  • 十六、备份目录暴露仓库配置
  • 十七、未纳入鉴权的接口
  • 十八、登录失败的响应时延
  • 小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档