
打开靶场页面地址栏里把网址敲进去、回车屏幕上就这么一行字——请使用某某请求方式访问。很多人第一反应是刷新、换浏览器、清缓存、甚至怀疑自己网络有问题折腾半小时才发现问题根本不在访问得对不对而在于你压根没用那个方法去访问。HTTP 协议里的请求方式这道题本质上考的不是什么高深漏洞而是你有没有真正意识到浏览器地址栏只会替你发一种请求那就是 GET。这篇文章就把 CTFHub 上这道 HTTP 协议 - 请求方式 的题目掰开揉碎讲一遍从题面怎么读、工具怎么选、报错怎么排一直聊到方法语义背后的设计逻辑。刚接触 Web 的新手能照着做出来做过几道题但一直靠抄命令过关的朋友也能在这里补齐为什么这么改的那部分认知。1. 题面读法为什么地址栏敲回车永远过不了这关先把一件事说透HTTP 请求报文的第一行叫做请求行格式是方法 请求目标 协议版本例如GET /index.html HTTP/1.1。这一行是客户端决定的不是服务端分配的。你在浏览器地址栏输入 URL 然后回车浏览器内部写死的逻辑就是构造一个 GET 请求发出去没有任何界面让你改这个动词。所以当题目要求用某个特定方法访问时你用地址栏怎么试都是错的因为发出去的报文第一行压根没变过。这就引出了读题的第一步把题面拆成三个要素——目标地址、要求的方法名、是否需要附带参数或请求头。CTFHub 这类练习平台的题干通常短得离谱一两句话加一个链接就完了正因为短特别容易被一眼扫过去。我见过有人把题干里的方法名当成页面标题有人把它当成需要提交的 flag 格式还有人直接在 URL 后面加?methodxxx当查询参数传结果当然是不对的。题目说的是请求方式那就是报文第一行的第一个词不是参数、不是路径、不是请求头字段。再说说这道题为什么会被放在 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只改方法名不会自动处理重定向。如果服务端返回 302curl 默认不会跟而如果你加了-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 ...。看起来有点小题大做但当你要反复试十几次的时候这个小动作能省掉好几次低级失误。