ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

HTTP请求方式详解:从CTFHub改方法到405/501排查

HTTP请求方式详解:从CTFHub改方法到405/501排查

打开靶场页面,地址栏里把网址敲进去、回车,屏幕上就这么一行字——请使用某某请求方式访问。很多人第一反应是刷新、换浏览器、清缓存、甚至怀疑自己网络有问题,折腾半小时才发现,问题根本不在"访问得对不对",而在于你压根没用那个方法去访问。HTTP 协议里的"请求方式"这道题,本质上考的不是什么高深漏洞,而是你有没有真正意识到:浏览器地址栏只会替你发一种请求,那就是 GET。这篇文章就把 CTFHub 上这道 HTTP 协议 - 请求方式 的题目掰开揉碎讲一遍,从题面怎么读、工具怎么选、报错怎么排,一直聊到方法语义背后的设计逻辑。刚接触 Web 的新手能照着做出来,做过几道题但一直靠"抄命令"过关的朋友,也能在这里补齐"为什么这么改"的那部分认知。

1. 题面读法:为什么地址栏敲回车永远过不了这关

先把一件事说透:HTTP 请求报文的第一行叫做请求行,格式是方法 请求目标 协议版本,例如GET /index.html HTTP/1.1。这一行是客户端"决定"的,不是服务端分配的。你在浏览器地址栏输入 URL 然后回车,浏览器内部写死的逻辑就是构造一个 GET 请求发出去,没有任何界面让你改这个动词。所以当题目要求"用某个特定方法访问"时,你用地址栏怎么试都是错的,因为发出去的报文第一行压根没变过。

这就引出了读题的第一步:把题面拆成三个要素——目标地址、要求的方法名、是否需要附带参数或请求头。CTFHub 这类练习平台的题干通常短得离谱,一两句话加一个链接就完了,正因为短,特别容易被一眼扫过去。我见过有人把题干里的方法名当成页面标题,有人把它当成需要提交的 flag 格式,还有人直接在 URL 后面加?method=xxx当查询参数传,结果当然是不对的。题目说的是"请求方式",那就是报文第一行的第一个词,不是参数、不是路径、不是请求头字段。

再说说这道题为什么会被放在 HTTP 协议这个分类下面。它的教学意图很明确:让你亲手碰一次"请求方法是可以被客户端任意指定的"这个事实。这件事在真实系统里的意义比题目本身大得多——很多内部接口的权限校验只挂在 GET 和 POST 上,PUT、DELETE、PATCH 这些方法的路由压根没做鉴权,攻击面就是这么来的。你在这道题里学会改一个动词,将来做接口测试、做安全评估的时候,就会下意识地去问一句"这个路径换个方法请求会怎样"。这种条件反射,比记住某条命令重要得多。

1.1 题干里的三个信息,一个都不能猜

拿到题目先把这三样东西抄在草稿里,别靠脑子记。第一是完整的目标 URL,注意是 http 还是 https,注意有没有端口号,注意路径结尾有没有斜杠——这几个细节在后面排查 404 的时候能救命。第二是题目明确要求的方法名,一字不差地抄下来,包括大小写。HTTP 的方法名是大小写敏感的,Get和GET在规范层面是两个不同的方法标识,虽然大部分服务端框架会做兼容处理,但靶场为了考你细心,很可能就是不做兼容。

第三是有没有额外的附加条件,比如"需要携带某个请求头"或者"需要提交某个参数"。有的题目会在方法名之外再叠一层,比如要求你用某个方法并且带上特定的 Referer,或者要求在 Cookie 里塞一个值。这类附加条件通常写在题干第二句或者页面上的小字里,特别容易漏。我的习惯是先把题面整段复制到一个文本文件里,逐句读一遍,把动词、名词、URL 全部标出来,确认没有第二层要求再动手。

1.2 先确认你在请求什么,再谈怎么改

有个很常见的顺序错误:一上来就打开抓包工具改方法,改完发送,拿到 404,然后开始怀疑工具。其实正确的顺序是先确认目标地址本身是通的。用最朴素的方式,浏览器访问一次那个 URL,看看返回什么。如果浏览器访问就是 404,那说明你抄的地址有问题,跟方法没关系;如果浏览器访问返回 200 但内容是"请使用某某方法",那说明路由匹配上了,只是方法不对,这时候改方法才有意义。

这个先后顺序看着是废话,实际上能省掉大量时间。我自己就吃过亏:某次题干里的路径末尾带了个斜杠,我复制的时候漏了,然后折腾了二十分钟改方法、改头、换工具,最后发现是地址少了一个字符。从那以后我养成了一个习惯——先用浏览器原样访问一次,把返回的状态码和页面特征记下来,作为基准;改完方法之后再对比,如果状态码从 200 变成 405,说明方法确实被服务端识别了,只是不被允许;如果从 200 变成 404,那大概率是路径被改坏了。

1.3 这类题在考什么,不在考什么

得说清楚一点:这道题不考漏洞利用,不考绕过,它考的是你对 HTTP 报文结构的掌控力。你不需要懂什么注入、什么反序列化,你只需要能让客户端发出一个"第一行不是 GET"的请求。所以别把它想复杂了,也别去看一堆花里胡哨的教程,那些讲一半跑去讲别的漏洞的文章,只会让你更迷糊。

但它也不是纯粹的"体力活"。真正值得琢磨的是:为什么服务端会针对不同方法返回不同的结果?这背后是路由匹配机制。绝大多数 Web 框架在注册路由的时候,是把"路径 + 方法"作为一个联合键来匹配的,/api/user配 GET 和配 PUT 是两条完全独立的路由记录。所以你换方法,等于在问服务端"你有没有为这个路径注册这个方法"。理解了这一层,你就明白为什么有的路径换方法返回 405,有的直接返回 404 了——前者是路径注册了但方法没注册,后者是路径本身就没注册,或者框架压根没把这种方法当成合法方法。

2. 把请求方式改掉的三条路:从控制台到命令行

方法有好几种,我不推荐只学一种。原因很简单:不同场景下工具的顺手程度差别很大,只会一种的话,遇到工具被限制、被拦截、或者环境不让你装东西的时候就会卡死。下面三条路我平时都在用,各有各的适用面。

2.1 浏览器控制台里的 fetch:最轻的一条路

如果目标页面允许你在同源环境下执行脚本,那最省事的方式就是在开发者工具的 Console 里发一个 fetch 请求。打开页面,F12 进 Console,输入类似这样的代码:

fetch('/目标路径', { method: '题目要求的方法名', credentials: 'include' }).then(r => r.text()).then(console.log);

这里的method字段就是请求行里的那个动词,浏览器会原样放进报文第一行。credentials: 'include'的作用是带上当前域下的 Cookie,有些题目会在会话里做校验,不带 Cookie 就白改。

这条路的好处是零安装、天然同源、自动带 Cookie。缺点是浏览器对少数方法名有硬性限制,比如 CONNECT、TRACE、TRACK 这三个是被规范禁止在 fetch 里使用的,你写上去会直接抛错。好在靶场题目要求的方法名通常是普通标识符或者自定义词,不在这三个之列,所以基本不受影响。另外注意一点:如果你在控制台里手敲,别用中文引号,也别把方法名敲成小写,这两点是最常见的低级错误。

再补一个细节:有些同学会用XMLHttpRequest来发,那个也能改方法,但写法更啰嗦,而且open(method, url)的第一个参数同样受上面那三个限制。日常我更推荐 fetch,代码短,出错信息也清楚。

2.2 curl:把请求行握在自己手里

命令行工具里,curl 是控制力最强的。核心参数就一个-X,用来指定方法:

curl -i -X 题目要求的方法名 "http://目标地址/路径"

-i的作用是把响应头一起打印出来,这个参数非常重要,因为这道题的关键信息很可能藏在响应头里,而不是响应体里。我曾经遇到过响应体是空的、flag 在自定义响应头里的情况,如果不加-i,屏幕上啥都看不到,还以为自己失败了。

如果要带请求体,加-d;要带自定义头,加-H;要保留 Cookie 做连续请求,用-c存、-b读;想看完整的请求和响应交互过程,加-v,它会把你实际发出去的请求行原样打印出来,这是排查"我明明改了为什么没生效"的终极手段:

curl -v -X 题目要求的方法名 -H "Cookie: 你的会话值" "http://目标地址/路径"

-v输出里以>开头的行是客户端发出的,以<开头的行是服务端返回的。你盯着那个>后面第一行看,是不是你想要的请求行,一目了然。这一招我在排查各种"改了没反应"的问题时用得最多。

还有一个容易忽略的点:-X只改方法名,不会自动处理重定向。如果服务端返回 302,curl 默认不会跟,而如果你加了-L让它跟随,重定向之后的方法可能会被改成 GET。这是 HTTP 规范里遗留的行为,下面第 5 节会细讲,这里先记一笔:排查阶段先别加-L,看清楚服务端到底返回了什么再说。

2.3 拦截工具:看得见才改得准

第三条路是用请求拦截工具,在请求发出前或者发出后把它拦下来,手动编辑报文第一行。这类工具的价值在于可视化——你能清清楚楚看到完整的请求报文,包括所有请求头、Cookie、请求体,然后像编辑文本一样改掉第一行的动词,再放出去。

操作流程大致是这样:先把工具的拦截功能打开,让浏览器的请求走它的监听端口;然后在浏览器里正常访问一次目标地址(这时候发的是 GET);请求被拦下来之后,找到报文第一行,把GET替换成题目要求的方法名;放行,看响应。

这条路最适合的场景是题目有附加条件。比如要求你同时带上某个特定的请求头、或者需要维持一个已经登录的会话、或者需要在多个请求之间保持状态。用命令行拼这些参数容易漏,用拦截工具直接在已有请求上改一个词,其他东西原封不动,出错概率最低。

不过它也有坑。第一,如果工具开了 HTTPS 解密,你得先信任它的证书,否则浏览器会报证书错误,你会以为是靶场的问题。第二,有些工具在转发时会规范化请求,比如自动把方法名转成大写、自动补Content-Length、自动处理Connection头,这些规范化大部分时候是好事,但极少数情况下会掩盖你的错误——比如你本来敲错了大小写,工具帮你转成大写反而对了,你就永远不知道自己错在哪。所以我的建议是:先用 curl 练手,把请求行的概念刻进脑子,再用拦截工具提效,顺序别反。

3. 405 与 501 的分野:报错信息背后的服务端结构

改完方法发送,最常见的两种失败是 405 和 501。很多人把这两个混为一谈,其实它们指向的问题完全不同,看懂了能省下大量瞎试的时间。

405 Method Not Allowed的意思是:这个路径存在,但你用的方法它不接受。这是一个应用层的回答,说明请求已经到达了业务路由,路由表里有这条路径,只是没有为你这个动词注册处理函数。这时候响应里通常会带一个Allow头,列出这个路径实际支持哪些方法。这个头简直是服务端主动递给你的说明书,看到它就应该顺着试。

501 Not Implemented的意思是:服务端不认识或者不支持你这个方法。这通常是更底层给出的回答,说明请求可能压根没进到业务代码,在服务器或者中间件那一层就被判了死刑。如果你的方法名是随手编的、拼错的、或者用了某个服务器默认不实现的方法,就容易拿到这个。

对比一下就很清楚:

状态码谁给出的回答含义你的下一步
404路由层路径本身没匹配上检查 URL、斜杠、大小写
405业务路由路径对了,方法不对看Allow头,换方法
501服务器/中间件方法名不被识别检查拼写、大小写、是否被中间层拦
400解析层报文格式有问题检查请求行、头、体的格式

这张表我建议存下来,做接口测试的时候天天用得上。判断顺序永远是先看状态码落在哪一档,再决定往哪个方向查,别一上来就瞎改。

3.1 Allow 头是服务端递过来的说明书

接着说Allow头。它的格式是逗号分隔的方法列表,比如Allow: GET, POST, HEAD。看到这个,你至少知道两件事:路径是对的,以及服务端认可哪些动词。如果题目要求的方法不在这个列表里,那你得想想是不是搞错了目标路径,或者题目要的其实是另一种交互方式。

这里有个实操技巧:如果Allow里有 OPTIONS,你可以先发一个 OPTIONS 请求探探路。OPTIONS 的语义就是"询问这个资源支持什么操作",服务端返回的Allow头会给你一份完整的说明。这比一个一个试要快得多,而且在真实项目里做接口梳理时也是标准动作。

不过要注意,OPTIONS 的响应经常被中间层改写或者直接返回一个通用结果,尤其是跨域场景下,它会被 CORS 预检机制接管,返回的是 CORS 相关的头,而不是业务路由的Allow。所以看到 OPTIONS 返回的东西跟预期不符,别急着下结论,那可能只是预检响应,不是真正的路由信息。

3.2 那些在到达业务代码之前就被吃掉的方法

还有一种情况值得单独说:方法在中间层就被拦了。典型的中间层包括反向代理、Web 应用防火墙、CDN 边缘节点。它们对 HTTP 方法的处理策略各不相同,有的默认只放行 GET、POST、HEAD,其他一律拒掉;有的会把不认识的方法直接返回 501;还有的会静默丢弃请求体。

这种情况下的表现是:你明明改对了方法名,本地curl -v也确认发出去的就是那个方法,但服务端返回的东西跟你在浏览器里看到的完全不是一回事,或者干脆连接被重置。遇到这种,先怀疑中间层。排查手段是逐层对比——直接请求源站(如果题目给了直连地址)和请求域名,看结果是否一致;或者在拦截工具里看响应头的Server、Via、X-Cache之类的字段,判断请求到底经过了哪些环节。

在靶场环境下,这道题一般不会搞得这么复杂,但把这个思路建立起来是有必要的。真实项目里因为一层代理导致方法被吞掉的情况太常见了,我见过好几次"本地测通了上线就挂",最后查出来是网关的白名单没放开 PUT。

4. 方法语义的底层账本:幂等、安全与缓存

题目做出来了,别急着关掉。这道题最好的延伸价值,是把 HTTP 方法背后的语义账本理一遍。理解了这层,你以后设计接口、评审接口、排查接口问题,判断力会完全不一样。

4.1 安全和幂等:两个用来做决策的词

HTTP 规范给方法贴了两个标签:安全和幂等。安全指的是"这个请求不会改变服务端资源的状态",幂等指的是"同样的请求执行一次和执行 N 次,对服务端状态的最终影响是相同的"。

  • GET:安全、幂等。只读,不改状态。
  • HEAD:安全、幂等。跟 GET 一样,但只返回头,不返回体。
  • OPTIONS:安全、幂等。问能力,不改状态。
  • PUT:不安全、幂等。整体替换资源,重复执行结果一致。
  • DELETE:不安全、幂等。删了再删,结果还是"没有",所以幂等。
  • POST:不安全、不幂等。每次提交都可能创建新资源,重复提交就是多条记录。
  • PATCH:不安全、不幂等。局部更新,某些实现下重复执行结果可能不同。

这组标签不是学术游戏,它直接决定工程决策。比如"这个接口能不能安全地重试",答案就写在幂等性里:PUT 和 DELETE 可以放心重试,POST 不行,得靠业务层的幂等键去兜底。再比如缓存,代理和浏览器默认只缓存安全方法的结果,你用一个 POST 去做查询,缓存层直接无视你,性能怎么调都上不去。

我见过不少项目把查询做成 POST,理由是"参数太长 URL 放不下"。这个理由在个别场景下成立,但代价是丢掉了整套缓存体系和语义约定,后续要在网关做基于方法的限流、审计、缓存策略时全部失效。如果参数真的长到 URL 装不下,更合理的做法是用 GET 配一个短 ID,让服务端去查长参数,而不是把语义整个换掉。

4.2 缓存、CORS 与 OPTIONS 预检的联动

方法语义还跟另外两件事强绑定。第一是缓存。浏览器和中间代理判断一个响应能不能缓存、能缓存多久,会看方法、状态码、Cache-Control、Vary等一堆东西的组合。GET 和 HEAD 的响应默认具备可缓存性,其他方法基本默认不缓存。

第二是跨域。当你在一个域下用脚本去请求另一个域,且方法不是简单方法(简单方法只有 GET、HEAD、POST,且请求头受限),浏览器会先自动发一个 OPTIONS 预检请求,问对方"我能不能用这个方法和这些头来请求你"。预检通过了,真正的请求才会发出去。这就解释了为什么你在控制台里用 fetch 改方法时,有时候会先看到一条 OPTIONS 请求,然后才看到真正的请求。

这里有个非常实用的排查点:如果你用 fetch 改方法一直失败,控制台报跨域错误,先打开网络面板看看是不是预检没通过。预检失败的响应通常带着Access-Control-Allow-Methods头,里面列出了对方允许的方法,如果题目要求的方法不在里面,那就不是你能在客户端解决的,得换思路(比如换同源页面、或者用不触发预检的方式)。这个细节很多人不知道,白白在 fetch 的参数上纠结半天。

理解了这层联动,你再回头看那道题,视角就不一样了:你改的不仅是一个词,你是在改变这个请求在整个 HTTP 生态里被对待的方式——能不能被缓存、会不会触发预检、能不能被安全重试。一个小动词,牵着一整条链路。

5. 踩坑复盘:那些看起来像题目做错、其实是工具在捣鬼

这部分是我最想写的。题目本身不难,难的是各种"看起来改了但没生效"的假象。下面这几个坑我或者身边的人基本都踩过,按出现频率排序。

5.1 302 重定向把方法洗成了 GET

这是最阴的一个坑。HTTP 规范里,302 和 301 这类重定向状态码,历史上很多客户端在跟随重定向时会把方法从 POST(或其他方法)改成 GET,并且丢掉请求体。后来的规范用 307 和 308 明确了"跟随重定向时保持原方法",但 302 的旧行为在很多客户端里依然保留。

所以现象就是:你发了一个目标方法,服务端返回 302,客户端自动跟过去,跟过去的那个请求变成了 GET,最后你拿到的响应是 GET 的结果。你以为是方法没改对,其实是改对了但被重定向洗掉了。

排查方法很直接:先别让工具自动跟随重定向。curl 不加-L,拦截工具里关掉自动跳转,先看第一次请求返回的状态码和Location头。如果是 3xx,手动把方法带到那个 Location 上再发一次。这个动作做一次,你对重定向的理解就到位了。

顺带说一句,这个坑在真实业务里造成的事故不小。比如某个提交接口在登录态失效时返回 302 跳到登录页,客户端自动跟随,POST 变成 GET,请求体丢了,用户看到的是"提交成功"(其实是登录页返回的 200),数据根本没进去。这类问题排查起来特别费劲,因为日志里全是 200。

5.2 大小写、路径斜杠和看不见的空白字符

第二个高频坑是字符层面的。方法名大小写敏感这事前面说过,但实际操作中最容易出问题的是从网页复制粘贴带进了不可见字符。题干里的方法名后面跟了个不换行空格,或者复制时带了个零宽字符,肉眼完全看不出来,但服务端收到的就是另一个字符串。表现是拿到 501,你怎么查都查不出问题。

我的处理方式是:手敲,不复制。方法名通常就几个字符,手敲一遍五秒钟,比事后排查半小时划算。如果一定要复制,复制到编辑器里,打开"显示不可见字符"看一眼,或者用echo -n "你的方法名" | wc -c数一下字节数,跟预期长度对不对得上。

路径斜杠的问题前面提过一次,这里再强化一下:/a/b和/a/b/在不少框架里会被当成两个不同的路由,有的框架会自动做重定向,有的直接 404。复制 URL 的时候,把整个地址粘进去之后,仔细看一眼结尾。同样的道理,URL 里的?后面如果带了参数,别漏掉,也别多加。

5.3 工具默认行为在悄悄改你的请求

第三个坑来自工具本身。拦截工具在转发时会做一堆规范化:自动补Host、自动算Content-Length、自动处理Connection、某些情况下自动把方法名转大写。这些行为大部分时候是帮忙,但排查阶段会误导你。

比如你敲了个小写的方法名,工具帮你转成大写发出去,服务端认了,你就以为方法名不区分大小写——下次换个工具就翻车。再比如你手写了一个Content-Length,工具根据实际体长度又改了一遍,你就搞不清到底是哪个值生效了。

排查这类问题的办法是用-v看真实发出的报文,别信界面上显示的东西。界面上显示的是"你打算发的",-v打印的是"实际发出去的",两者不一定一致。这个习惯我强烈建议养成,它帮你建立的是一种"以报文为准"的判断方式,而不是"以工具界面为准"的猜测方式。

6. 从一道题到接口设计:方法选择在真实项目里的取舍

题做完了,聊点更远的。这道题之所以值得写一整篇,是因为它指向一个在真实项目里反复出现的问题:方法的选择和校验,经常是最容易被敷衍的一环。

6.1 权限校验只做在 GET 和 POST 上,是常见的疏漏

很多系统的权限中间件是这么写的:拦截请求,根据路径匹配权限规则,然后放行或者拒绝。问题在于,规则表里通常只有"某路径 + GET"和"某路径 + POST"两类条目,PUT、DELETE、PATCH 这些压根没配规则。没配规则会怎样?取决于中间件的默认策略——如果是"默认放行",那就是一个实打实的越权入口;如果是"默认拒绝",那就是功能不可用。

这两种情况我都见过。前者更危险,因为它不报错,安安静静地就把数据改了。做接口梳理的时候,我会专门列一张表,把每个路径支持的方法过一遍,逐个确认鉴权覆盖到了。这个过程枯燥,但它比事后追查事故便宜太多。

6.2 方法覆盖头是个方便但容易埋雷的方案

有些场景下客户端确实发不了 PUT 或 DELETE,比如老式表单、某些受限环境。业界有过一个变通方案:客户端统一发 POST,然后在请求头或者表单字段里带上"真实方法",服务端在入口处读这个字段,把请求方法"覆盖"成真实的值。

这个方案解决了兼容性问题,但引入了新的风险面。因为"方法覆盖"这个动作发生在鉴权之前还是之后,直接决定了安全性。如果鉴权中间件读的是原始方法(POST),而业务路由读的是覆盖后的方法(PUT),那中间就有一道缝——鉴权看到的是 POST 规则,路由执行的是 PUT 逻辑。这个缝隙在很多框架的默认配置里是存在的。

我的建议是:非必要不用方法覆盖;如果必须用,把覆盖逻辑放在最外层,确保鉴权看到的就是最终方法,并且对允许覆盖的方法名做白名单,别让客户端随便覆盖成任意值。

6.3 接口文档里该怎么写方法

最后提一个细节:写接口文档的时候,方法一栏别只写"POST"。把方法的语义意图写清楚——是创建、是整体替换、是局部更新、还是删除。因为读文档的人未必是你,他看到一个 POST 接口,第一反应是"可以重复调用吗""失败了能重试吗""会不会产生脏数据",这些问题的答案就藏在方法语义里。

我自己的习惯是,在接口描述里固定加一行"幂等性:是/否",再配一句"重试策略"。这行字写起来只要十秒,但它能挡掉后续无数次"这个接口能不能重试"的追问。

7. 收尾:我自己的顺手流程

最后说说我自己做这类题的固定流程,纯粹是踩坑踩出来的肌肉记忆,供参考。

先原样用浏览器访问一次目标地址,记下状态码和页面特征,作为基准。然后打开终端,用curl -i发一次原始 GET,确认命令行的结果跟浏览器一致——这一步是为了排除工具差异。接着把-X加上,改成题目要求的方法,看状态码变化:从 200 变成 405 说明方法被识别但被拒,从 200 变成 404 说明路径出问题了,从 200 变成 200 且内容变了,那就是过了。如果拿到 405,就去读Allow头;如果拿到 3xx,先别跟随,手动处理 Location;如果拿到 501,第一反应是检查方法名有没有敲错、有没有不可见字符。

整个过程里,-v是我打开次数最多的参数。它让我每次都看到实际发出去的那一行到底是什么,而不是凭感觉猜。这个习惯养成之后,你会发现大部分"玄学问题"其实一点都不玄,只是你之前没有看报文的习惯。

对了,还有一个小技巧:如果题目要求的方法名比较特殊,你可以先把它存进 shell 变量,避免反复手敲出错,比如M=题目要求的方法名,然后curl -X $M ...。看起来有点小题大做,但当你要反复试十几次的时候,这个小动作能省掉好几次低级失误。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进