ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

脚本抓到的和浏览器看到的不一样:先查这四层

脚本抓到的和浏览器看到的不一样:先查这四层 授权与合规声明本文全部操作对象均为自建隔离靶场本机容器或隔离虚拟机涉及安全测试的环节必须以取得合法授权为前提。未经授权的渗透测试违反《中华人民共和国网络安全法》与《刑法》相关条款须承担相应法律责任。本文只讲环境配置、版本对照与靶场隔离不含任何攻击步骤、利用载荷与绕过手法请勿将文中环境指向任何非自有系统。一、同一串地址两份不同的结果1.1 命令行里取到的和浏览器里看到的不是一份一段几乎人人都遇到过的困惑浏览器里打开某个页面内容完整、排版正常把同一串地址复制到命令行用脚本去取回来的却是另一份东西——可能是简版页面可能是请先登录也可能只有一句已经搬到别处去了。同一串地址同一个时刻两份结果。HTTP 的一次交互里请求方向上并不只有要哪个地址这一件事随请求一起走的还有一批头字段它们分别描述我是谁“我想要什么形式”“我带着什么状态”。不同的客户端——命令行工具、脚本、浏览器——默认情况下往这些头字段里填的内容并不相同。所以脚本和浏览器看到的不一样多数时候不是网络出了毛病而是两个客户端默认发出的请求本来就不是同一份。1.2 差异拆成四层身份、偏好、状态、跳转把常见的差异按究竟是哪一层的默认值不同归成四类就得到本文要讲的四层层它在说什么对应的头与机制本文哪一章身份我是哪一类客户端User-Agent第 2 章偏好我期望拿到什么形式的内容Accept第 3 章状态我是否带着此前留下的状态Cookie第 4 章跳转遇到已移动时是否继续跟过去3xx 与Location第 5 章四层的顺序不是随手排的前两层描述这一份请求本身写了什么后两层描述这一份请求之后、这批请求之间发生了什么。查差异时也照这个顺序从前往后看。1.3 本文只做观察不做构造边界先立在这里本文所有动作都是观察既有的响应——把请求已经发出的头、把服务器已经回来的头原样读出来。本文不写请求构造不写把身份换成别的值去取本来取不到的内容也不写绕过站点机制的做法。命令一律只读取头、取首页。凡本机没跑过的命令块都会标一行代码待验证。本章可以带走的一句脚本和浏览器看到的不一样多数不是网络问题而是两类客户端默认发的请求本就不同——差异可以拆成身份、偏好、状态、跳转四层。二、第一层 身份默认的User-Agent是curl/VERSION2.1 curl 默认把自己写成了什么curl 官方 man page 把默认值写得非常直白逐字引文见附表 A 第 2 行By default, curl uses curl/VERSION, such as User-Agent: curl/8.23.0.逐字读默认情况下curl 使用curl/VERSION这一形式例如User-Agent: curl/8.23.0。命令行工具在默认状态下主动报出自己的名字和版本而不是伪装成别的东西——默认值是如实自述不是冒充浏览器。本文引用的这份 man page 自述描述的正是 curl 8.23.0原文注明若你使用更新的版本本页可能未完整记录因此引用时一律写作截至 2026-10-07 的官方 man page台账 C01。User-Agent在语义规范里所在的位置本批复核到的是 §10.1.5台账 Q03。2.2-A管的是这一行写什么默认值可以用-A/--user-agent改写man page 给的几档行为很干净不给这个选项发默认值形如curl/VERSION。给一个字符串发出去的就是那个字符串。给空参数完全移除该头。给一个空格发送一个空值的头。后两档要分清楚**不给这个头和给一个空值不是同一件事。**空参数是把这一行整个拿掉一个空格是把这一行留着但值里什么都没有。写法请求里这一行变成什么不写-A默认值形如curl/VERSION-A 某串字符就是该串字符-A 该头被完全移除-A 该头保留值为空2.3 这一层为什么会让两边不一样关键在服务器这一侧会怎么用这个字段收到User-Agent之后它可能按客户端身份选择返回不同的内容——同一串地址面向不同客户端给出不同形态的响应是很常见的做法。这里必须把话说准**这条线的意义在于服务器可能按客户端身份返回不同内容而不是把值改成浏览器的就能拿到别的内容。**后半句是另一个方向的事本文不写——它属于构造请求去改变服务端给你什么不在观察范围内见第 7 章。本文只钉一件事默认值不同同一串地址就可能落到不同分支上。⚠️代码待验证# 只读观察一看 curl 默认往请求里写了什么 User-Agentcurl-vhttps://example.com/21|grep-i^ user-agent# 只读观察二看移除该头之后请求里还剩不剩这一行curl-v-Ahttps://example.com/21|grep-i^ user-agent# 说明上面两条只读取头不做写操作目标须为本机靶场或取得授权的地址。这两条动作做的其实是一件事把 curl自己发出的请求头原样读出来看默认值和移除之后分别长什么样。第二条预期什么都读不到——完全移除就是这个意思。本章可以带走的一句User-Agent这一层的默认值是如实自述而非冒充浏览器-A 是移除该头-A 是发一个空值两者不是一回事。三、第二层 偏好Accept表达的是意愿不是命令3.1Accept的原话怎么写的第二层是我期望拿到什么形式的内容对应的头是Accept。规范给它的定义是逐字引文见附表 A 第 4 行用户代理用它来表达对响应媒体类型的偏好ABNF 为Accept #( media-range [ weight ] )*/*表示所有媒体类型type/*表示该类型下所有子类型。台账 Q01章号 §12.5.1。逐字读至少能读出三件事第一它是用户代理发出的说明的是客户端自己的偏好。第二它的语法是#( media-range [ weight ] )一个列表每一项是一个媒体范围后面可跟一个可选权重所以这个字段天然能一次写好几项。第三*/*表示所有媒体类型type/*表示该类型下所有子类型。这两个写法是范围的写法不是某种具体格式的写法写*/*不是我要全部格式而是这一项我不挑。3.2 偏好和命令差在哪里这一层最容易读错的地方是把Accept当成一道命令。规范用的词是偏好preference而偏好是请求方向服务器表达的一种意愿不是对服务器下的指令。差别很具体客户端说我偏好这一类是请求不是保证。服务器可以不照办——规范没有把必须按它返回写进去。因此两边不一致时不能直接推断请求没发对。有可能是你发了偏好对方另有安排。这跟第一层性质不同User-Agent是自述身份服务器按身份分流Accept是表达偏好服务器可以采纳也可以不采纳。两层都写在请求里但一个是描述自己一个是提要求。3.3 一行偏好也可能和两边的默认值有关回到最初的现象脚本取到的和浏览器不一样也可能是这一层默认值不同。浏览器会按自己的策略往Accept里放一串偏好列表命令行工具默认放的则是它自己的一套两边默认填的内容不同服务器即使都照办返回的形态也可能不同——差异仍然长在客户端这一侧。各家浏览器往这个头里放了什么本文不逐一列举——那是实现行为随版本变化本机未逐条核过见附表 A 末行。⚠️代码待验证# 只读观察一看 curl 默认往请求里写了什么 Acceptcurl-vhttps://example.com/21|grep-i^ accept# 只读观察二只把响应头取出来看服务器实际回了什么内容类型curl-s-o/dev/null-D- https://example.com/|grep-i^content-type# 说明两条都只读取头不做写操作本文不贴运行结果实际输出以你的环境为准。第一条读的是你发出去的偏好第二条读的是对方回来的内容类型。两行并排看就能判断我偏好的和我拿到的是不是同一件事。本章可以带走的一句Accept表达的是偏好不是命令服务器可以不照办它是提要求而提了要求不等于按此办理。四、第三层 状态脚本默认不带你的会话4.1 脚本和浏览器差在最明显的一处前三章讲的是这一份请求里写了什么这一章讲请求之间留下了什么。浏览器里登录后才有的页面搬到脚本里往往变成请先登录最直接的原因就在这一层脚本默认不带你浏览器里的会话状态。要理解这一点先分清两个方向浏览器在早先某次访问里从服务器那里收到并保存了一份状态之后每次访问同一站点再把它带回去脚本没有经历过那段历史手里也就没有那份状态可以带——它不是被拒绝了它是空手去的。4.2-b两种用法官方口径curl 里负责这一层的是-b/--cookie。man page 给的两种用法是逐字引文见附表 A 第 5 行-b/--cookie data|filename两种用法——①直接把 cookie 数据放进Cookie头发送格式为NAME1VALUE1; NAME2VALUE2这些明确给出的 cookie 会被显式放进所有外发请求②从文件读取并发送。逐字读重点在两处第一可以直接给数据格式是NAME1VALUE1; NAME2VALUE2这些明确给出的cookie 会被显式放进所有外发请求——你写进去的它每一次都带。第二也可以给文件从文件里读取 cookie 再发送。用法给什么man page 的口径用法一直接给数据形如NAME1VALUE1; NAME2VALUE2明确给出的 cookie 会被显式放进所有外发请求用法二给一个文件路径从文件读取 cookie 并发送把这两档和 4.1 接起来看就很清楚默认状态下这一层是空的-b才是要不要带、带什么的开关。4.3 为什么这一层最能解释两份结果口径说清楚脚本默认不带任何会话状态所以它访问登录后才有的页面时得到的是另一种结果——不是页面变了也不是请求发错了而是这一次请求里根本没有那样一份状态。浏览器相反它此前收到过之后每次访问都带上看到的是登录后的那一份。关于 Cookie 的六个属性各自的语义能在哪些主机生效、活多久、走什么通道、脚本接口能不能碰本账号已经另有专文逐条写过本篇不重复——这里只留一句属性语义不在此展开。本文也不逐字引用 Cookie 那一份规范的条文本批未重新核见附表 A 末行的待验证说明。⚠️代码待验证# 只读观察看这次请求里到底有没有带 Cookie 这一行curl-vhttps://example.com/21|grep-i^ cookie# 说明只读取头不做写操作。预期读不到——这正是脚本默认不带会话状态的意思。读不到Cookie这一行就说明这一次请求确实是空手去的。两份结果不一致时先在这里停一下往往比继续往下查更快。本章可以带走的一句脚本默认不带你浏览器里的会话状态所以登录后才有的页面在脚本里会得到另一种结果——这一层的属性语义本篇不展开。五、第四层 跳转脚本默认不跟过去5.1-L的原话对新位置重发请求最后一层不在请求写了什么上而在收到之后怎么办上。curl man page 对-L/--location的说明是逐字引文见附表 A 第 6 行若服务器报告所请求页面已移动到其他位置以Location:头与 3XX 响应码表示-L/--location使 curl 对新位置重发请求与--show-headers或--head同用时所有被请求页面的头都会显示。逐字读三点最重要第一触发条件是服务器报告所请求页面已移动到其他位置而这种报告以Location:头与 3XX 响应码表示——两者一起出现才是这一层说的跳转。第二-L的作用是对新位置重发请求。默认状态下 curl不会替你跑这一趟要不要跟过去由这个开关决定。第三和--show-headers即-i或--head即-I同用时所有被请求页面的头都会显示——途中每一个地址的头都会被列出来而不只是第一个。这一层解释的现象很典型脚本取一个地址回来的不是页面内容而是一小段这个东西在别处浏览器里却直接显示了最终页面。差别不在内容而在要不要跟过去。5.2 3xx 在规范里怎么定性关于这一层的原因HTTP 语义规范对 3xx 这一类状态码的原话是逐字引文见附表 A 第 7 行章号 §15.4表示为完成该请求用户代理还需采取进一步动作其中一类是该资源可能位于另一 URI由Location头给出如 301/302/307/308304 Not Modified属另一类指向先前存储的结果。逐字读到的是用户代理还需采取进一步动作这句定性这类响应的意思不是你要的东西就在这里而是事情还没完你还得再做一步。做不做、怎么做是客户端的事——这就回到 5.1 那个开关浏览器默认做了那一步脚本默认没做。原文另有一段历史说明HTTP/1.0 中 301/302 原本定义为保持方法而当时用户代理在POST 重定向之后按 POST 还是按 GET上发生了分裂最终通行实践收敛为改为 GET台账 Q04。也就是说这一处的通行做法是收敛出来的不是写死的。至于 307/308 的方法保持细节本批未逐字取全本文不展开Q04 备注——只知道它们和 301/302 一样属于资源可能位于另一 URI的那一类。状态码分类本文不做展开。5.3 这一层的不一样长什么样你看到的说明它在哪一层页面内容正常没有跳转或者客户端已经替你跟过去了只有一小段已移动到别处的信息收到了 3xx客户端没有继续跟最终页面外加一串中间地址的头客户端跟了且被要求把每一段的头都显示出来第三行对应的正是 5.1 里所有被请求页面的头都会显示那句话。想看清这一层最省事的做法是只读地把头取出来先看第一段响应里有没有 3XX 与Location。⚠️代码待验证# 只读看第一段响应里的状态码与 Locationcurl-ihttps://example.com/|head-n5完整版四层对照表这一章讲的跳转会不会被自动跟随连同前面三层本文整理成了一张可以对着用的四层对照表放在资料包里扫码即可获取本章可以带走的一句3xx 的意思是你还得再做一步-L决定要不要替你做这一步——脚本默认不跟浏览器默认跟。六、把四层变成一张对照表6.1 四层对照层脚本默认是什么浏览器默认是什么结果会怎么不同怎么只读地看出来身份User-Agent自述为curl/VERSION自述为一串产品与平台信息服务器可能按身份分流返回不同内容读请求头里的User-Agent偏好Accept命令行工具自己的一套浏览器按自身策略放的一串偏好拿到的媒体类型形态可能不同对照发出的Accept与回来的Content-Type状态Cookie不带任何会话状态带上该站此前保存的状态登录后才有的页面变成另一种结果读请求头里有没有带Cookie跳转 3xx 与Location默认不跟过去默认自动跟过去只拿到已移动而不是最终页面读第一段响应里的状态码与Location这张表要防的是一种很常见的归因错误把脚本拿到的和浏览器不一样一律算成被拦了。四行里只有第一行涉及对方按身份分流其余三行都是客户端自己的默认值不同。6.2 只读地把四层各看一遍⚠️代码待验证# 一、只看响应头不看正文-i 把响应头输出到结果里curl-ihttps://example.com/# 二、只要头不要正文-I 用 HEAD 方法取回文档的头curl-Ihttps://example.com/# 三、正文丢掉头单独输出到标准输出只看头的一种写法curl-s-o/dev/null-D- https://example.com/# 四、在头里只看偏好与跳转两层的回应curl-s-o/dev/null-D- https://example.com/|grep-i-e^content-type-e^location# 五、确认这次请求有没有带状态读不到就是没带curl-vhttps://example.com/21|grep-i^ cookie# 说明以上五条全部只读取头或只取首页不做任何写操作目标须为本机靶场或取得授权的地址。这五条命令覆盖的正是四层的观察入口第一条-i台账 C04把响应头输出到结果里和正文走同一路输出——想一次看清状态码 头 正文用它最省事。第二条-I台账 C05只取头用的是 HTTP 的HEAD方法正文一段不给。第三条-s -o /dev/null -D -是正文丢掉、头单独打出来的写法适合只关心头字段的场合。第四条在第三条基础上筛出两个字段Content-Type对应第二层我到底拿到了什么形式Location对应第四层要不要跟过去。第五条回到第三层确认请求里到底有没有状态。6.3 表的用法先定位再判断对照表的用法只有两步先用结果会怎么不同那一列定位到某一层再用怎么只读地看出来那一列去核。定位这一步的关键是别急着下结论——四层里有三层和对方有没有特殊对待你无关。核这一步的关键是只看不改所有命令都停在把头读出来不往请求里加东西也不改服务端状态。本章可以带走的一句四层里只有身份那一层涉及对方按身份分流其余三层都是客户端自己的默认值不同先定位到层再用只读命令去核。七、观察的边界7.1 观察和构造界线在哪本文从头到尾只做一件事观察既有的响应。四层的命令都在读请求已经写了什么“服务器已经回了什么”——被动地看不改任何东西。顺着前面几章很容易滑向两条越界的路一是把身份改成别的值指望换成浏览器的身份就能拿到另一种内容二是带上并非自己应有的状态去取本不该给你的内容。这两条都不在本文范围。原因有两层性质上它们已经不是观察而是构造请求去改变对方给你什么法律上这类动作有明确的法律边界——《中华人民共和国网络安全法》现行第二十九条2025-10-28 修订、2026-01-01 施行就未经授权访问他人系统的行为设有禁令具体条文此处不再展开。本文只把界线画在这里观察是看构造是动本文只写前者。7.2 遇到不一致先查哪一层顺序先看什么怎么看看什么信号1跳转只取头看状态码拿到的是不是已移动一类的 3xx2状态读请求头里有没有Cookie没有说明这一次是空手去的3身份读请求头里的User-Agent是不是默认的curl/VERSION4偏好对照发出的Accept与回来的Content-Type我偏好的和拿到的是不是一回事5上面都没异常停下来重新描述问题是不是把我期待的当成了对方承诺的顺序为什么这么排先看是不是根本没走完跳转再看是不是空手去的状态最后才看有没有说清自己要什么身份与偏好。最常见的两种两份结果恰好落在前两行排除掉之后再往身份与偏好上找命中率低得多但能省掉很多来回。四层全部排查过、仍然对不上时先怀疑问题描述而不是继续怀疑工具。7.3 一条纪律先分清是谁的默认值不同再谈对方做了什么。四层里身份与偏好是这一份请求里写了什么状态与跳转是这一次请求之后、这批请求之间发生了什么。把客户端默认值不同和服务端特殊对待这两件事分开是这一篇最想留下的东西。本文不覆盖的部分也一并交代不写任何请求构造、身份伪装、状态伪造与绕过站点机制的做法不写服务端如何配置这些头字段Cookie 的六个属性语义另有专文本篇不重复各家浏览器与各类客户端实际的默认头值本篇不逐一列举属实现行为本篇未实测。完整版四层对照表这一章那张先查哪一层的顺序表和第六章的四层对照表是一套本文一并放进资料包里扫码即可获取本章可以带走的一句本文只做观察不做构造遇到不一致按跳转 → 状态 → 身份 → 偏好的顺序查先把客户端默认值不同和服务端特殊对待分开。附表 A本文引用事实与官方出处对照表#事实照口径出处本文位置1man page 自述描述的是curl 8.23.0原文注明若使用更新版本本页可能未完整记录引用一律写截至 2026-10-07 的官方 man pagecurl 官方 man page文档自述段第 2 章2逐字By default, curl uses curl/VERSION, such as User-Agent: curl/8.23.0. -A/--user-agent指定 UA 字符串给空参数会完全移除该头给一个空格则发送空值头同第 1 行-A, --user-agent第 2 章3User-Agent位于语义规范§10.1.5本批仅复核章号逐字内容沿用既有已核口径RFC 9110 §10.1.5第 2 章4逐字Accept由用户代理用来表达对响应媒体类型的偏好ABNFAccept #( media-range [ weight ] )*/*表示所有媒体类型type/*表示该类型下所有子类型RFC 9110 §12.5.1第 3 章5逐字-b/--cookie data|filename两种用法——①直接把 cookie 数据放进Cookie头发送格式NAME1VALUE1; NAME2VALUE2这些明确给出的 cookie 会被显式放进所有外发请求②从文件读取并发送同第 1 行-b, --cookie第 4 章6逐字若服务器报告所请求页面已移动到其他位置以Location:头与 3XX 响应码表示-L/--location使 curl对新位置重发请求与--show-headers或--head同用时所有被请求页面的头都会显示同第 1 行-L, --location第 5 章7逐字3xx 表示为完成该请求用户代理还需采取进一步动作其中一类是该资源可能位于另一 URI由Location头给出如 301/302/307/308304 Not Modified属另一类。原文并注明 HTTP/1.0 中 301/302 原本定义为保持方法当时用户代理在POST 重定向后按 POST 还是按 GET上发生分裂最终通行实践收敛为改为 GETRFC 9110 §15.4第 5 章8逐字要点-i/--show-headers把响应头输出到结果里与数据同一路输出同第 1 行-i, --show-headers第 6 章9逐字要点-I/--head只取头HTTP 服务器提供HEAD方法用它可以取回文档的头同第 1 行-I, --head第 6 章10各家浏览器与各类客户端实际的默认头值本文未实测属实现行为本批未逐条核正文不给结论第 3 章、第 7 章本文未实测11Cookie 六个属性的语义本篇不展开本账号已另有专文逐条写透本篇只提属性语义不在此展开且不逐字引用 Cookie 那份规范条文本批未重新核第 4 章12307/308 的方法保持细节待验证 / 本文不展开本批复核未逐字取全Q04 备注正文只写明它们属资源可能位于另一 URI一类第 5 章待验证附表 B术语速查表术语一句话解释头字段随请求或响应一起走的一批描述性字段写明身份、偏好、状态等身份一层由User-Agent承担curl 默认自述为curl/VERSIONUser-Agent自述客户端身份与版本的请求头服务器可能按它分流-A / --user-agent改写 UA 的选项空参数移除该头一个空格发空值头偏好一层由Accept承担表达的是意愿不是命令Accept用户代理用来表达对响应媒体类型偏好的请求头状态一层由Cookie承担脚本默认不带浏览器的会话状态-b / --cookie直接给 cookie 数据或从文件读取给出的会被显式放进所有外发请求跳转一层由 3xx 与Location承担管要不要跟过去Location与 3XX 响应码一起表示所请求页面已移动到其他位置的响应头3xx表示用户代理还需采取进一步动作的一类响应-L / --location使 curl 对新位置重发请求默认状态下不会自动跟-i/-I把响应头输出到结果里 / 只用HEAD方法取头只读观察本文全部动作的形态取头、取首页不构造请求、不改服务端状态代码待验证表示本机未实际运行该命令的标记未实测 / 待验证表示未在本机核过或未取到官方逐字依据的标记写在最后这篇用到的资料写这篇文章时我把 curl 的 man page 从默认User-Agent翻到--location又对着语义规范核了Accept与 3xx 两节。顺手也整理了几份配套的东西四层差异对照表身份、偏好、状态、跳转四层各自的默认值、结果差异与只读观察方法靶场环境对照表DVWA、upload-labs 在 Windows / macOS / Linux 三平台的可行性与推荐路径Web 安全学习路线图从基础打牢到安全管理四个阶段各学什么常用靶场清单每个靶场练什么、适合哪个阶段资料是我自己整理的放在下面这个码上扫码即可获取添加时备注「靶场」优先通过。拿到之后建议先看四层差异对照表那一份先把客户端默认值不同和服务端特殊对待这两件事分开。
返回列表