curl--proto选项深度解析:用协议白名单精准控制传输边界
【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl
本篇技术指南围绕 curl 命令行工具的--proto(以及配套的--proto-redir、--proto-default)选项展开,讲解如何通过逗号分隔的协议列表与+、-、=三种修饰符,精确限定 curl 允许发起与重定向跟随的协议集合,从而提升脚本与自动化场景下的安全性与可控性。读完本文,你将掌握协议白名单的完整语法、底层解析与校验机制,并能在真实命令中熟练组合使用。
为什么需要限制协议
curl 是一个使用 URL 语法传输数据的命令行工具与库,支持 DICT、FILE、FTP、FTPS、GOPHER、GOPHERS、HTTP、HTTPS、IMAP、IMAPS、LDAP、LDAPS、MQTT、POP3、POP3S、RTMP、RTMPS、RTSP、SCP、SFTP、SMB、SMBS、SMTP、SMTPS、TELNET、TFTP、WS、WSS 等众多协议(协议清单见 tests/data/data1706-1.md)。
协议众多带来便利,也带来攻击面:一个看似无害的 URL 可能是file://读取本地文件、gopher://探测内网端口,或smb://触达内网共享。--proto正是为此设计的协议白名单开关——限制一次传输允许使用的协议集合,让脚本在不可信输入面前保持最小权限。它自 7.21.0 加入,属于connection curl类别,一次调用只接受单个参数(Multi: single),完整定义见 docs/cmdline-opts/proto.md。
语法总览:从左到右的协议列表
--proto的参数是一个逗号分隔的协议列表,每个元素是协议名或关键字all,可选地以零个或多个修饰符开头:
curl --proto <protocols> <URL>协议按从左到右的顺序依次求值。列表中的每一项都是一个“指令”,作用于同一个协议集合,因此书写顺序直接影响最终结果。三种修饰符的语义如下:
| 修饰符 | 语义 | 说明 |
|---|---|---|
+ | 允许 | 在已允许的协议基础上追加该协议,这是不带修饰符时的默认行为 |
- | 禁止 | 从已允许的协议集合中移除该协议 |
= | 仅允许 | 重置为只允许该协议(忽略此前已允许的集合),但可被后续条目继续修改 |
原文档中的三个核心示例完整对应上述语义:
# 使用默认协议集合,但禁用 ftps curl --proto -ftps $URL # 先全部禁止,再单独放行 https 与 http —— 只允许 http 和 https curl --proto -all,https,+http $URL # '=' 重置为只允许 http 和 https(效果同上) curl --proto =http,https $URL注意第三个示例的等价性:-all,https,+http通过“先清空、后追加”实现白名单,而=http,https用=一步完成重置再追加,两种写法结果相同。
修饰符的底层求值逻辑
在源码层面,--proto的参数解析发生在 src/tool_getparam.c:命令行收到--proto后,将nextarg交给proto2num()处理,并把结果存入config->proto_str。真正的求值实现在 src/tool_paramhlp.c 的proto2num()中:
- 首先以默认协议集合(
built_in_protos,即当前构建实际支持的协议)预填充一个协议集(protoset); - 然后按逗号切分字符串,逐个 token 处理;
- 对每个 token 读取首字符判定动作:
=→set(重置)、-→deny(移除)、+→allow(追加)、无修饰符 → 默认allow; - 若 token 是
all:deny时清空整个集合(protoset[0] = NULL),allow/set时复制全部内置协议; - 若 token 是具体协议名:
deny从集合中清除该协议;set先清空集合再追加;allow直接追加; - 全部处理完后按字母序排序(源码注释说明这是为了满足 CI 测试的稳定输出要求)。
一个容易踩的坑是把修饰符写在协议名中间。测试用例 tests/data/test322 专门验证了这一点:命令curl --proto +-http(修饰符粘连写成了+-)会触发错误输出curl: unrecognized protocol '-http'并以退出码 2 失败。可见每个协议项只能有一个动作修饰符,且必须位于该项的最前面。
未知协议与未编译协议:警告而非报错
这是--proto最实用的设计之一:未知或未被编译进 curl 的协议只会产生警告,不会中断命令。
原文档明确说明:出现未知或禁用协议时会产生 warning,这样脚本可以安全地依赖“禁用危险协议”这一能力——即使某个协议压根没被编译进当前 curl,禁用它的写法也不会导致整条命令报错退出。反过来说,如果你在列表中写了拼写错误或根本不存在的协议名,proto2num()会打印unrecognized protocol 'xxx'并返回参数错误(见 src/tool_paramhlp.c),这与“合法但未编译”的协议是两回事。
那么白名单在 libcurl 内部是如何生效的?在传输建立阶段,lib/url.c 的url_set_conn_scheme()会做位掩码校验:
if(scheme->run && (data->set.allowed_protocols & scheme->protocol) && (!data->state.this_is_a_follow || (data->set.redir_protocols & scheme->protocol))) { conn->scheme = scheme; return CURLE_OK; }即:URL 的 scheme 必须命中allowed_protocols位集合;若当前请求是重定向跟随(this_is_a_follow),还必须同时命中redir_protocols集合。校验失败时返回CURLE_UNSUPPORTED_PROTOCOL(错误码 1),并输出诸如Protocol "ftp" is disabled的提示。这条调用链完整串联了命令行解析(src/tool_getparam.c)→ 选项传递(src/config2setopts.c 通过CURLOPT_PROTOCOLS_STR设置)→ libcurl 字符串转位掩码(lib/setopt.c 中的protocol2num())→ 传输前校验(lib/url.c)。
多次使用:效果等价于拼接
--proto可以在一条命令中多次出现,效果与把所有协议项合并进同一次调用完全一致。例如下面两种写法等价:
curl --proto =http --proto +https $URL curl --proto =http,https $URL这得益于proto2num()天然的状态累积式设计——它操作的是同一个协议集合,后续条目继续在前序结果上求值。在--next分隔的多 URL 场景中,你也可以在各自的--next段里分别指定--proto,互不干扰(该选项本身是Multi: single,即单次调用内只接受一个参数,但整条命令行可以重复出现)。
关联选项:重定向与默认协议
--proto-redir:单独限制重定向跟随
--proto只管“发起传输时允许的协议”,而跟随重定向时允许的协议由--proto-redir单独控制(定义见 docs/cmdline-opts/proto-redir.md),语法与--proto完全一致,但有一条硬性规则:被--proto禁止的协议,--proto-redir无法重新放行(Protocols denied by --proto are not overridden by this option)。
默认情况下,curl 在重定向时只允许 HTTP、HTTPS、FTP 和 FTPS(自 7.65.2 起)。显式指定all或+all会放开全部协议,但这在安全上是不明智的。一个常见的加固写法是:
curl --proto-redir =http,https --follow http://example.com测试用例 tests/data/test1245 专门验证了“--proto的 deny 必须压过--proto-redir的 allow”:服务器返回 301 重定向到ftp://...,命令同时使用了--proto与--proto-redir进行配置,最终重定向目标 FTP 被拒绝,证明了两层白名单的优先级关系。
--proto-default:为无 scheme 的 URL 补协议
--proto-default用于给缺少 scheme 的 URL指定默认协议(定义见 docs/cmdline-opts/proto-default.md),自 7.45.0 加入。协议名不区分大小写且不带://后缀:
curl --proto-default https ftp.example.com其行为要点如下:
- 指定未知或未支持的协议会直接报错
CURLE_UNSUPPORTED_PROTOCOL; - 它不改变默认代理协议(代理仍是 http);
- 不设置该选项时,curl 会基于主机名“猜测”协议(见 docs/cmdline-opts/url.md 的相关说明);
- 默认协议不能设为
ipfs或ipns,这两种 scheme 必须显式写在 URL 里。
实现上,src/config2setopts.c 展示了关键逻辑:解析 URL 时使用CURLU_GUESS_SCHEME尝试猜测,但当配置了proto_default时改用CURLU_NO_GUESS_SCHEME,一旦返回CURLUE_NO_SCHEME就取默认协议补上。测试用例 tests/data/test1146 验证了--proto-default file配合无 scheme 的文件路径可以正常读取本地文件。
实战组合:一条安全加固的命令模板
把三者组合起来,可以得到一个适合处理不可信 URL 的加固模板:
curl --proto =http,https \ --proto-redir =http,https \ --proto-default https \ --max-redirs 3 \ "$URL"该命令的效果是:只允许 HTTP/HTTPS 发起传输,重定向时也只跟随 HTTP/HTTPS,无 scheme 的 URL 一律按 HTTPS 处理,重定向最多跟 3 次。即便传入ftp://、file://或gopher://之类的恶意 URL,也会在 lib/url.c 的位掩码校验处被直接拦截并返回CURLE_UNSUPPORTED_PROTOCOL,从根本上杜绝了协议降级攻击面。
小结
--proto用“从左到右求值 + 三种修饰符”的简洁语法,为 curl 的协议能力提供了精细的开关控制:+追加、-移除、=重置,配合all关键字可以实现从“默认集合微调”到“严格白名单”的任意粒度;未知或未编译协议只告警不报错的设计,让脚本可以无条件地执行禁用动作;而 src/tool_paramhlp.c 的proto2num()、lib/url.c 的url_set_conn_scheme()以及 tests/data/test322、tests/data/test1245、tests/data/test1146 等测试用例,则为这套语义提供了完整的实现与验证闭环。在自动化脚本、安全扫描和 Web 服务聚合等场景中,将--proto与--proto-redir、--proto-default搭配使用,是控制 curl 传输边界最简单也最可靠的手段。
【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考