ARTICLE DETAIL

资讯详情

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

Python后端爬虫专题25:当seed_url由用户填写——防御SSRF、恶意重定向与超大响应

Python后端爬虫专题25:当seed_url由用户填写——防御SSRF、恶意重定向与超大响应 Python后端爬虫专题25当seed_url由用户填写——防御SSRF、恶意重定向与超大响应上一篇练习完整答案完整脱敏验证importjsonfromjobradar.observabilityimportJsonEventLogger lines:list[str][]loggerJsonEventLogger(lines.append)logger.event(login_failed,request{password:pss,Authorization:Bearer secret,headers:{Cookie:sidsecret},body:x*500,})payloadjson.loads(lines[0])encodedjson.dumps(payload)assertpssnotinencodedandBearer secretnotinencodedandsidsecretnotinencodedassertpayload[request][body]x*200…fetch 两次 attempts2、successes1、failures1、total0.4average_seconds0.2。失败率告警分子是解析失败详情数分母是进入解析的详情响应数排除主动取消和 304持续十分钟且样本量达到阈值再触发先查 task_id、source host、error_type、snapshot_id、部署版本与状态码分布不查凭据。攻击路径这不是普通URL校验若 API 接受任意 seed攻击者可让 JobRadar 请求http://127.0.0.1、云元数据地址、数据库管理面板或公司内网 DNS。响应内容可能通过错误、快照、时间差泄露。这叫服务端请求伪造 SSRF“只允许 http/https”远远不够因为危险目标也使用 HTTP。JobRadar 第一闸是显式 host allowlist而不是黑名单。只有课程target.test或 Composetargetlab被允许target.test.evil.example不相等target.testevil.example的 hostname 是 evil.example。主机统一 casefoldURL 必须有合法 scheme 与 hostname禁止嵌入用户名密码。DNS检查为什么在请求前还要做一个表面公开的域名可以解析到 127.0.0.1、10/8、172.16/12、192.168/16、链路本地、回环、保留或多播地址。CrawlPolicy.check_resolved_addresses遍历解析出的全部地址默认拒绝任何非全局地址。课程容器targetlab解析为 Docker 私网所以只有它同时出现在allowed_private_hosts才例外。cd project.\.venv\Scripts\python.exe-m pytest tests\test_http_client.py::test_fetcher_checks_dns_result_before_sending_request-q测试给允许域名配置一个私网解析结果并记录 transport 调用期望策略在发送前拒绝真实网络次数为零。它保护的是“域名通过但地址危险”这条容易遗漏的分支。DNS rebinding 更棘手检查时解析公网、连接时换为内网。高风险生产系统应让实际连接使用已验证 IP 并正确设置 Host/TLS SNI或通过受控 egress proxy 再做解析与网络层 ACL应用检查不能替代容器/云网络出站限制。每一次重定向都重新过闸允许 URL 可以 302 到内网或另一个域。HttpFetcher 关闭 HTTPX 自动重定向读取 Location 后用 urljoin 得到目标再重新执行 URL policy 与 DNS policy且限制最大跳转数。只验证初始 seed 相当于大门有保安、后门敞开。重定向时也要谨慎转发 Authorization/Cookie课程公开抓取不设置这些头。授权 Session 跨域跳转应直接停止。循环重定向与过多跳转属于失败不无限跟随。响应太大也是攻击面直接response.content会把任意大文件载入内存。HttpFetcher 使用流式读取逐块累计一旦超过max_response_bytes抛ResponseTooLarge并停止。Content-Length 若已超过上限可提前拒绝但不能只信它服务端可缺失或撒谎最终仍要数实际字节。压缩炸弹还涉及解压后大小HTTP 库可能自动解码限制应针对应用实际获得的字节并在反向代理/egress 层再设上限。Content-Type 也要验证职位解析器不应把 2GB 视频当 HTML。allowlist不是一份永久静态配置每个条目应有业务所有者、授权依据、允许路径、是否允许私网、过期时间和复审。*.example.com过宽子域可能被其他团队或用户接管尽量列精确主机。路径级策略和 robots 再限制可访问区域。拒绝消息不要回显内网响应内容或解析出的敏感地址细节给普通用户内部安全日志可以记录经过控制的信息。多租户环境还要限制每个租户可选来源不能所有人共享全局 allowlist。四层防御如何组合应用层验证 scheme/host/DNS/redirect/sizeHTTP 层设置超时和有限重试容器网络只允许到必要出口云防火墙拒绝元数据与内网段授权层控制哪些租户能发任务。任何一层都可能有实现错误多层让一次错误不直接变成事故。本篇完整URL访问策略本模块只做纯策略判断不发请求。HttpFetcher 负责在正确时机调用它API 负责在入队前先挡一次Worker 在真实 DNS 解析后再挡一次。爬虫的目标范围与 robots.txt 访问策略。fromdataclassesimportdataclass,fieldfromipaddressimportip_addressfromurllib.parseimporturlsplitfromurllib.robotparserimportRobotFileParserclassPolicyViolation(ValueError):URL 超出任务创建者明确授权的采集范围。dataclass(frozenTrue)classCrawlPolicy:在网络请求之前执行的 allowlist 和字面 IP 防线。allowed_hosts:set[str]allowed_private_hosts:set[str]field(default_factoryset)defcheck_url(self,url:str)-None:partsurlsplit(url)ifparts.schemenotin{http,https}ornotparts.hostname:raisePolicyViolation(crawl URL must use HTTP(S) and contain a host)ifparts.usernameorparts.password:raisePolicyViolation(crawl URL must not contain credentials)hostparts.hostname.casefold()allowed{value.casefold()forvalueinself.allowed_hosts}ifhostnotinallowed:raisePolicyViolation(fhost is not allowed:{host})try:addressip_address(host)exceptValueError:returnprivate_allowed{value.casefold()forvalueinself.allowed_private_hosts}if(address.is_privateoraddress.is_loopbackoraddress.is_link_localoraddress.is_reservedoraddress.is_multicastoraddress.is_unspecified)andhostnotinprivate_allowed:raisePolicyViolation(fprivate or reserved address is denied:{host})defcheck_resolved_addresses(self,url:str,addresses:set[str])-None:复检 DNS 结果阻止受信域名解析到内网或元数据地址。self.check_url(url)hosturlsplit(url).hostnameasserthostisnotNoneprivate_allowed{value.casefold()forvalueinself.allowed_private_hosts}ifnotaddresses:raisePolicyViolation(fhost resolved to no addresses:{host})forrawinaddresses:addressip_address(raw)unsafe(address.is_privateoraddress.is_loopbackoraddress.is_link_localoraddress.is_reservedoraddress.is_multicastoraddress.is_unspecified)ifunsafeandhost.casefold()notinprivate_allowed:raisePolicyViolation(fhost resolved to private or reserved address:{host}-{address})dataclass(frozenTrue)classRobotsRules:把 robots.txt 的允许结果和爬取间隔绑定到一个来源站。parser:RobotFileParser user_agent:strorigin:strcrawl_delay:float|Noneclassmethoddeffrom_text(cls,text:str,*,user_agent:str,origin:str)-RobotsRules:parserRobotFileParser()parser.set_url(origin.rstrip(/)/robots.txt)parser.parse(text.splitlines())delayparser.crawl_delay(user_agent)returncls(parserparser,user_agentuser_agent,originorigin.rstrip(/),crawl_delayfloat(delay)ifdelayisnotNoneelseNone,)defcan_fetch(self,url:str)-bool:partsurlsplit(url)originf{parts.scheme}://{parts.netloc}iforigin.casefold()!self.origin.casefold():returnFalsereturnself.parser.can_fetch(self.user_agent,url)本篇课后练习判断并解释target.test.evil.example、target.testevil.example、127.0.0.1、允许域302到内网四种输入在哪道闸被拒绝。写一个 MockTransport 返回超大分块响应的完整测试断言 ResponseTooLarge 且未解析 HTML。为 Compose 部署设计应用、容器网络、云防火墙三层出站规则明确 TargetLab 私网例外为什么只能用于课程。下一篇将把这些安全分支与正常采集放进分层测试体系。
返回列表