
Curlbash_detect 是一个很有意思的概念验证它用来判断一个 HTTP 请求到底只是 curl 在下载内容还是被curl | bash这种一键安装脚本模式带起来执行了。很多人第一次看到这个项目名称会以为又是某种命令行工具实际上它解决的是服务端日志和风控里一个很实际的痛点当用户执行curl https://example.com/install.sh | bash时服务端能收到 curl 发起的一次下载请求但很难从这次请求里看出来这个脚本之后会不会被 bash 执行。如果不能识别你就无法区分普通访问、脚本下载、以及自动化批量安装行为。下面我会从请求特征、检测思路、最小示例和部署边界几个角度把这个 PoC 拆开讲清楚。如果你平时会维护下载服务器、写安装脚本或者做 Web 安全审计这篇内容应该能提供不少参考。1. 为什么会有“curl | bash 识别”这种需求1.1 一键安装脚本模式到底有多常见curl | bash这种安装方式比很多人想象中更普遍。编程语言运行时、开发工具、云原生组件、AI 应用安装器很多都采用“用户复制一条命令粘贴到终端执行”的交付方式。常见的形态就是curl -fsSL https://example.com/install.sh | bash就算不写bash写成sh也不少见。用户侧看起来是一条命令实际过程被管道拆成了两个阶段curl 负责把远程脚本拉到本地bash 或 sh 负责逐行解释执行。这种模式对用户很友好对发布方也很方便不需要维护复杂包仓库不需要处理跨平台依赖只需在服务端维护一个脚本文件。但问题也随之而来服务端拿到一次 HTTP 请求时怎么知道这次请求来自普通下载、来自浏览器访问、还是来自一次正在执行的一键安装curl | bash的下载请求在外观上和其他 curl 请求几乎一样服务端默认情况下根本无法区分。除了安装脚本curl 还经常出现在各种自动化场景里。API 调试、表单提交、文件上传、对象存储访问、代理交互都会用 curl。也正因为 curl 太常见服务端如果只看 User-Agent几乎不可能判断这次请求背后发生了什么。1.2 服务端区分不了“下载”和“执行”的麻烦服务端区分不了“下载”和“执行”会带来几个实际麻烦。第一个是统计不准确。脚本发布方想了解安装量通常会统计脚本的下载次数。但如果用户使用curl -O下载到本地再手动执行或者用浏览器访问脚本地址下载次数会虚高。反过来如果用户把脚本缓存到本地第二次执行时不再下载服务端又看不到这次安装。只统计下载次数很难反映真实执行情况。第二个是风控和审计困难。如果你运营的是公网下载服务需要识别恶意爬取、批量下载、自动化执行行为但你拿到手的只是请求 IP、User-Agent、时间戳。今天有大量安全设备会把curl | bash当作高危行为部分终端在用户执行这类命令时甚至会弹出类似allow this bash command的确认提示。这说明平台已经意识到这种模式的风险但服务端仍然缺少统一、可靠的识别手段。第三个是配置策略不好做。如果你希望给高风险脚本加一层二次确认比如当用户直接curl | bash执行时返回一个提示页面或要求额外参数你必须先知道这次请求是不是“要被执行的”。否则你把所有 curl 请求都拦截普通下载也会受影响。Curlbash_detect 这个项目的出发点就是解决这个信息缺口。它不打算重新发明 HTTPS、不打算替代签名校验而是通过一个轻量的概念验证证明服务端可以识别出“当前脚本正在被 shell 执行”。1.3 它定位是 PoC不是完整防护需要先明确Curlbash_detect 是一个 proof of concept不是生产级防护工具。它的核心贡献是提供一个可复现的检测思路而不是给你一套可以直接上线的 WAF 规则或安全组件。如果你打算直接拿它来保护生产环境大概率会觉得不够完善。因为它需要服务端动态生成脚本内容需要在脚本里注入额外逻辑还需要回调接口来确认执行状态。这些行为在生产环境里都要考虑兼容性、性能、日志、权限和用户隐私。但作为研究起点它非常值得看。尤其是你维护下载服务、写自动化安装脚本、做安全审计或者想给自己团队的内部工具增加“实际安装率”统计时这个 PoC 的思路可以给你很多启发。接下来我会先拆解请求链路再给出一个最小示例最后讨论部署时容易踩的坑。2. 先拆解 curl 和 bash 在请求链路中的特征2.1 一条 curl | bash 命令实际发生了什么要理解 Curlbash_detect先看一条curl | bash命令到底拆成了几个阶段。用户输入命令后shell 会做管道解析启动 curl 进程向目标 URL 发起 HTTP GET 请求。curl 接收响应体把内容写到标准输出。管道把标准输出交给另一个进程的标准输入。启动 bash 进程从标准输入读取脚本内容并执行。这里最关键的一点是curl 和 bash 是两个完全独立的进程。curl 下载完成并退出后bash 才开始真正读取和执行脚本。HTTP 请求只在第一阶段发生服务端只能感知到 curl 的那一次请求无法看到 bash 的执行过程。如果脚本内部又发起新的请求比如去下载另一个文件、调用一个 API、上报统计数据那么这些新请求同样来自独立的进程。它们和第一次 curl 请求之间唯一的关联就是脚本内容本身。换句话说服务端要想知道“这个脚本后来被执行了”必须在脚本内容里想办法埋下一个可追踪的信号。单靠请求头、IP、时间戳很难得出可靠结论。2.2 服务端能看到的请求头和请求路径服务端收到一次脚本请求时能观察到的信息通常包括这样几类信息类型示例能说明什么User-Agentcurl/8.0.1说明请求来自 curl但无法判断之后是否执行Accept/curl 和很多自动化工具都会带Referer无很多场景为空浏览器直接访问也可能为空Cookie无没有登录态无法关联用户IP 和端口203.0.113.5:54321只能定位来源不能说明执行意图TLS 指纹不同客户端差异可以做辅助但不是可靠依据请求路径/install.sh说明目标文件不能说明执行方式你可以写规则拦截 UA 以curl开头的请求但这种方式误报率很高。原因很简单用户可以用curl -A伪造任意 User-Agent企业内部代理也可能重写 User-Agentwget、Python requests、Postman 导出的 curl 命令表现又各不相同。如果你希望通过请求头判断“这个请求将被 bash 执行”几乎不可能。因为 curl 进程本身并不知道后面有没有管道它只是完成一次普通下载。真正有执行意图的是 shell 和 bash而它们并不会出现在第一次 HTTP 请求里。2.3 真正可靠的检测思路脚本回调和环境标记既然请求头不够可靠Curlbash_detect 这类 PoC 通常会把思路转向“脚本内容回连”。核心做法可以这样理解服务端收到对脚本文件的请求时不直接返回静态脚本。服务端动态生成一段新的脚本在原有脚本前面插入一段“标记”逻辑。标记逻辑会携带一个随机 token在脚本被 bash 执行时通过 curl、wget 等工具回访服务端的一个回调接口。服务端收到回调请求并验证 token 后就可以确认这个脚本确实被执行了。这种思路本质上是挑战-应答机制。服务端通过脚本内容在用户侧放了一个标记只有真正执行脚本时标记才会被触发。如果用户只是下载、查看、转发都不会触发回调。和请求头检测相比回调方式更接近事实。它不依赖 User-Agent 是否包含特定字符串也不依赖 IP 是否稳定。它只依赖一个判断脚本有没有被 shell 执行过。当然回调方式也有自己的前提目标机器上要有 curl 或 wget脚本执行环境要能访问回调地址防火墙不能拦截。这些在后面的部署实战中会变成重要排查点。3. 用最小示例跑通 Curlbash_detect 的核心流程3.1 环境准备和前置条件跑通这个 PoC 不需要很高的机器配置普通开发机就够。建议准备一个 Linux 或 macOS 环境Windows 用户可以用 WSL 或 Git Bash 做大部分测试。Git Bash 本身提供了常见命令行工具但行为细节和 Linux 略有差异后面会专门说。需要的依赖很简单Python 3.7 以上用于运行示例服务端。Flask用于搭建 HTTP 接口。你可以用pip install flask安装。bash 和 curl用于模拟用户侧执行curl | bash。一个能保存标记状态的地方。示例代码可以用内存字典生产环境建议换 Redis 或数据库。先确认环境可用python3 --version curl --version bash --version不同系统里 curl 版本差异比较大Windows 自带 curl、Git Bash 自带 curl、Linux 发行版自带 curl编译参数和默认行为都不完全一样。实测时如果发现回调不触发第一步要先确认 curl 能正常工作第二步再确认 bash 能执行脚本。3.2 服务端示例返回带标记的脚本下面是一个极简的 Flask 示例。它实现了两个接口/install.sh接收脚本下载请求返回一段注入过标记的脚本。/__cb/token接收 bash 执行后发出的回调。/status查看当前所有 token 的状态。示例代码import secrets from flask import Flask, Response app Flask(__name__) # 仅用于演示生产环境请换 Redis / MySQL / 文件存储 markers {} ORIGINAL_SCRIPT echo start origin install echo do something echo done .strip() app.route(/install.sh) def install_script(): token secrets.token_hex(16) markers[token] pending content ( #!/usr/bin/env bash\n # curl-bash detect marker\n fcurl -fsSL --noproxy * http://127.0.0.1:5000/__cb/{token} /dev/null 21 || true\n ORIGINAL_SCRIPT \n ) return Response(content, mimetypetext/plain) app.route(/__cb/token) def callback(token): if token in markers: markers[token] executed return ok return not found, 404 app.route(/status) def status(): return markers if __name__ __main__: app.run(host127.0.0.1, port5000)这段代码的逻辑很直接。服务端每次返回/install.sh时都生成一个新的随机 token并把 token 放进返回脚本里。bash执行脚本时会先运行标记行向/__cb/token发起一个额外的 HTTP 请求然后再执行原始安装逻辑。这里我加了一个--noproxy *参数。原因是很多用户的终端环境里配置了 HTTP_PROXY 或 HTTPS_PROXY如果回调地址是 127.0.0.1curl 有时也会被代理接管导致本应该访问本机服务端的请求被发到代理上然后失败。--noproxy *表示对所有地址都不走代理能减少这类问题。/dev/null 21 || true也很重要。它让标记请求静默执行即使失败也不影响原脚本退出码。这样用户不会在安装时突然看到一串额外错误输出。3.3 验证命令和结果判断启动服务端python3 app.py然后模拟第一种情况只下载不执行。curl -s http://127.0.0.1:5000/install.sh这个命令会在终端输出脚本内容但不会执行。此时访问状态接口curl -s http://127.0.0.1:5000/status输出应该是类似这样的字典{2a1f...: pending}token 的状态还是 pending说明脚本没有被执行。接着模拟第二种情况使用curl | bash执行。curl -s http://127.0.0.1:5000/install.sh | bash再访问状态接口curl -s http://127.0.0.1:5000/status可以看到对应的 token 变成了{2a1f...: executed}这说明服务端收到了回调并且已经确认这个脚本被 bash 执行了。判断标准很简单如果/status中出现 token 从 pending 变为 executed说明检测链路是通的。如果一直保持 pending说明脚本没有执行或者回调失败了。3.4 核心参数说明这个示例看起来简单但几个参数对结果影响很大。参数作用建议token关联一次脚本请求和一次回调必须随机且不可预测建议用 secrets.token_hex/__cb/ 回调路径接收 bash 执行后上报的标记不要使用容易被爬虫猜到的静态路径--noproxy *避免本地回调被代理拦截实测中很关键/dev/null 21隐藏回调输出避免干扰安装脚本的终端输出|| true保证回调失败不影响原脚本退出码生产环境建议保留其中 token 的随机性非常重要。如果攻击者能猜到回调 URL可以伪造请求把状态提前改成 executed。对于 PoC 来说随机 token 已经够用生产环境还要考虑 token 过期时间、访问频率限制、同 IP 多次回调等。4. 常见干扰项和排查方向4.1 User-Agent 和代理不能直接判断有人可能会问能不能在服务端判断 User-Agent 是curl/x.x.x就认为它是 curl 请求然后配合来源 IP、Referer 做判断可以但只能做辅助不能做核心依据。原因有三个。第一User-Agent 完全可伪造。用户执行curl -A Mozilla/5.0后服务端看到的就是浏览器 UA。脚本里也可以设置curl -A custom-agent。第二企业内部代理会重写请求头。很多公司出口统一走代理服务端看到 User-Agent 可能是一个内部网关的名称curl 的真实版本信息已经丢失。第三wget、Python、其他下载工具也可能带相似的请求头。你没办法只凭 UA 判断后面是 bash 还是 sh。所以在实际部署中不要单独依赖 User-Agent。Curlbash_detect 这种回调思路是通过脚本内容建立关联比请求头可靠得多。4.2 Windows Git Bash 和 WSL 下的行为差异如果你在 Windows 上测试需要留意几个差异。Windows 自带的 curl 是 curl.exe和 Git Bash 里的 curl 不一定是同一个版本。Git Bash 内部虽然有 Unix 风格的路径和管道但涉及到本机回连时Windows 防火墙可能拦截入站请求。如果服务端跑在 Windows 的进程里客户端通过 127.0.0.1:5000 访问通常是允许的但如果服务端监听在 0.0.0.0有时候会触发防火墙弹窗。WSL 的情况不一样。WSL 里执行curl | bash实际调用的是 Linux 版 curl 和 Linux 版 bash行为更接近真实服务器环境。回调地址写 127.0.0.1 时访问的是 WSL 内部的回环地址和 Windows 主机不冲突。实测建议优先在 Linux 虚拟机或 WSL 里测试排除 Windows 网络栈干扰。如果在 Git Bash 里遇到回调失败先看 curl 是否能访问http://127.0.0.1:5000/status。如果连状态接口都访问不了说明服务端启动有问题或端口被占用。如果状态接口能访问但回调失败再检查防火墙和代理变量。4.3 回调失败和服务端状态异常的排查顺序遇到脚本执行后状态没有变化不要急着改代码。按照下面的顺序排查。第一步看服务端控制台日志。Flask 开发服务器会打印每次请求的路径和状态码。如果/__cb/token始终没有出现在日志里说明 bash 执行时根本没有发起回调请求。第二步确认脚本内容真的包含标记行。可以先把下载的脚本保存到本地用cat查看curl -s http://127.0.0.1:5000/install.sh /tmp/install.sh cat /tmp/install.sh如果脚本里没有 token 那一行说明服务端返回的缓存有问题或者你访问的是另一个地址。第三步检查脚本语法。直接用bash -n检查bash -n /tmp/install.sh如果输出语法错误说明把原始脚本直接拼接到标记逻辑时出了问题比如原始脚本包含 EOF、引号不匹配、特殊字符被解析。第四步用bash -x手动执行观察标记命令是否真的跑过bash -x /tmp/install.sh执行时如果看到curl -fsSL ...那一行被打印说明标记逻辑进入了执行流程。如果没看到说明脚本开头可能有 exit、return、set -e 中断了执行。第五步检查 curl 是否真的能访问回调地址。可以手动执行脚本里的标记命令curl -fsSL --noproxy * http://127.0.0.1:5000/__cb/你的token如果服务端返回 ok再访问/status确认状态是否变化。一个很常见的坑是脚本里用了set -e导致标记行被执行时如果失败脚本直接退出后面的原始逻辑也不会执行。示例中用了|| true就是避免这个问题。5. 从 PoC 走向实际部署要考虑什么5.1 把检测结果落成审计日志示例代码用内存字典保存状态只能用于验证思路。一旦要考虑实际部署第一步就是把检测结果落到日志或数据库里。一条完整的检测记录至少应该包含这些字段字段示例说明request_id8f3a...关联请求和回调token2a1f...脚本标记client_ip203.0.113.5请求来源user_agentcurl/8.0.1客户端信息script_path/install.sh请求的脚本路径first_seen_at2025-01-01 10:00:00首次下载时间callback_at2025-01-01 10:00:03回调时间statusexecuted最终状态callback_ip203.0.113.5回调来源 IP有了日志之后你可以分析出很多信息下载请求量、实际执行量、执行率、失败率、批量执行行为、固定 IP 高频下载脚本的次数。这里的判断标准是不能只记一个 executed还要记录时间差。如果下载请求后 10 秒才出现回调说明用户可能手动保存后执行如果 1 秒内回调更符合curl | bash管道直接执行的特征。5.2 误报、漏报和滥用风险回调方案也不是万能的它存在明显的边界。误报方面用户手动下载脚本后用 bash 执行也会触发回调。这时候服务端会认为这是一次“执行”但它不一定来自curl | bash管道。如果你希望精确区分管道形态单靠回调不够还需要结合时间差、脚本内容特征、进程链等信息。HTTP 协议层面通常拿不到进程链所以能做的只有近似判断。漏报方面用户使用wget -O- http://example.com/install.sh | bash、python -c import urllib; ...、直接复制脚本内容到终端都不会触发第一次 curl 请求服务端可以选择用不同策略处理。但 Can 至少原始 Curlbash_detect 的使用场景很窄只针对 curl 请求。滥用风险方面token 逻辑如果设计得不够严谨可能被刷回调。攻击者拿到脚本后可以单独请求回调地址伪造一个 executed 状态。生产环境必须给 token 设置有效期比如 60 秒过期相同 token 只允许回调一次同一个 IP 的请求频率要限制。还有一点很重要这个 PoC 只能证明脚本被执行过并不能证明执行过程是安全的。用户在curl | bash一键安装时其实把系统的执行权限交给了远程服务端。真正降低风险的方式是签名校验、固定版本 hash、HTTPS、最小权限运行而不是靠检测。5.3 后续可以扩展的方向如果要把这个思路做成更完整的小工具可以考虑几个方向。第一把回调逻辑从“注入到原始脚本”改成“包装执行器”。服务端返回的不再是原始脚本而是一个小型包装脚本由它负责下载并执行真正的安装脚本。这样原始脚本内容更干净标记逻辑集中维护可扩展性更强。第二增加超时和重试。回调请求可能因为网络抖动或代理配置失败可以设置 1 到 3 次重试。但要注意不能因为重试阻塞原始安装脚本太久。更好的方式是使用curl --max-time 2最多等两秒就继续执行。第三把脚本下载和执行分离到两条请求链路。第一步下载脚本时返回一个临时 token第二步在脚本内部用 token 换取实际安装逻辑同时在换取过程中上报执行状态。这样可以让 token 更不可预测也更方便做访问控制和限流。第四接入现有日志平台。把检测结果输出成 JSON 格式打进 ELK、Loki 或 ClickHouse再做可视化报表。很多团队关心“安装脚本下载了多少次、最终执行了多少次”这个数据对产品和运维都很有价值。第五结合风控策略。如果服务端发现同一个 IP 短时间内出现多次curl | bash执行可以返回警告页面或者要求额外校验参数。但不能把检测结果当成绝对可信还需要配合 IP 信誉、设备信息、行为频率一起判断。我自己在跑这个示例时最深的感受是Curlbash_detect 解决的核心问题不是“如何识别 curl 这个程序”而是“如何把脚本有没有被执行这个事实从用户侧带回服务端”。检测请求头只是辅助真正有效的是在脚本里埋一个回调标记。如果你准备在自己的下载服务里做类似的埋点先别急着加各种风控策略先确保回调链路在普通用户环境里能稳定跑通然后才是统计、日志和拦截。能识别到curl | bash只是第一步后面怎么用比识别本身更重要。