把运维脚本搬上 Linux:五处协议逆向的隐藏坑

举报
yd_246468676 发表于 2026/11/07 00:27:17 2026/11/07
【摘要】 一、先定位脚本的真实入口

一、先定位脚本的真实入口

很多人拿到一份运维脚本,第一反应是看 shebang,然后直接 python xxx.py 就开跑,跑不通就怀疑环境。真正常见的坑是,你根本没在执行你以为的那个文件。举一个真实例子:某备份脚本目录下有 main.py 和 bin/run.py,crontab 里写的是 /opt/ops/bin/run.sh,而 run.sh 第一行是 exec python3 "$(dirname "$0")/../main.py" "$@"。你在终端里改 main.py 改半天没反应,因为实际执行的是 run.sh 转手过去的另一个副本,藏在 /usr/local/lib/ops/main.py。定位入口最可靠的办法不是读脚本,是看进程。先跑

ps -ef | grep -i run.sh

ls -l /proc//cwd

readlink -f /proc//exe

tr '\0' ' ' < /proc//cmdline

/proc//exe 指向解释器,cmdline 里第一段是 python3,第二段才是脚本真实路径,cwd 则暴露了相对路径解析的基准目录。这一招在脚本被 wrapper 套了三层时尤其管用。第二步用 strace 看它到底打开了哪些文件:

strace -f -e trace=openat,execve -o /tmp/tr.log python3 main.py

日志里 execve 那条记录就是最终入口,后面紧跟的一串 openat 会把你没意识到的 import 路径全抖出来。我遇到过 Python 3.6.9 环境下,一个脚本因为 sys.path 里残留了旧的 site-packages,加载了同名但不同版本的 requests,报错是 AttributeError: module 'requests' has no attribute 'Session',版本其实是 2.0.1,而项目要求 2.25.1。整个排查花了 47 分钟,最后靠 python3 -c "import requests; print(requests.file)" 一行定位。入口这关过了,后面谈阻塞点才有意义。

二、用 syscall 还原阻塞点

入口确认后,卡顿到底卡在哪一层,只能靠 syscall 时间线说话。先上带时间戳的跟踪,重点盯 read、write、connect、recvfrom 这几个会真挂住的调用:

strace -f -tt -T -e trace=read,write,connect,recvfrom,select,poll -o /tmp/blk.log python3 main.py

-T 打出每个调用的耗时,一行形如 `connect(3, {sa_family=AF_INET, sin_port=htons(5432), sin_addr=inet_addr("10.20.3.17")}, 16) = -1 EINPROGRESS (Operation now in progress) <0.000021>`,紧跟的 select 上挂着 `<30.001142>`,三十秒整,这就是超时源,不是脚本逻辑慢。我手上那台 CentOS 7 的机器,Python 3.6.9 连着 PostgreSQL,wrapper 里没设 connect_timeout,驱动 psycopg2 缺省直接顶到 30 秒才抛 `psycopg2.OperationalError: could not connect to server: Connection timed out`,运维以为是批处理本身重,实际每次都在等这个连不上的库。定位手法是把 strace 的 -T 输出按耗时排序:

awk -F'<' '//,"",t); if(t+0>1) print t, $0}' /tmp/blk.log | sort -rn | head -20

耗时超过 1 秒的调用全浮到顶,一眼就能把"慢"和"等"分开。另一次是 DNS,`sendto(4, "\24\177\1\0\0\1\0\0\0\0\0\0\3api\7example\3com\0\0\1\0\1", 32, ...)` 之后 recvfrom 卡了 5.002 秒,`/etc/resolv.conf` 里配了两个不可达 nameserver,脚本每个请求都在重复解析。还有日志文件写满磁盘时 write 返回 `ENOSPC`,循环重试把 CPU 打满,看起来像死循环,其实是 strace 里成排的 `write(1, ..., ) = -1 ENOSPC`。把这些挂起点按调用类型归类,阻塞就有了明确归属:网络超时、名字解析、磁盘满、锁等待,而不是一句"脚本很慢"。锁等待看 `flock` 或 `fcntl(F_SETLKW)` 的长时间返回,配合 `lsof -p ` 或 `/proc//stack` 更直观。还原完阻塞点,才谈得上协议层签名为什么对不上。

三、协议签名离线对齐验证

阻塞点定位完,签名对不上往往不是算法写错,而是输入压根不干净。把线上请求的原始字节落盘,离线重算,逐字段对拍,是唯一可靠的对齐手段。先抓样本:在网关侧用 tcpdump 截一包 `tcpdump -i eth0 -s 0 -w /tmp/sig.pcap port 8443`,再从 pcap 里剥出 application 层数据存成 `/tmp/sig.bin`,同时把当时的密钥、时间戳、nonce 从服务端日志里抄出来,三件东西缺一不可。接着离线跑一遍签名函数,把中间量打印出来比对。最常见的坑是编码:Python 里 `json.dumps(payload)` 默认带空格、非 ASCII 会转 `\uXXXX`,而客户端用紧凑序列化,两边 JSON 差一个字节,HMAC 就全错。验证脚本这样写最省事:

raw = json.dumps(payload, separators=(',', ':'), ensure_ascii=False).encode()

print(raw, hmac.new(key, raw, hashlib.sha256).hexdigest())

第二个坑是时间戳单位。服务端记的是 `1699999999` 秒级,移动端发的是 `1699999999123` 毫秒,离线对齐时若拿秒去算,签名永远差一截,对拍时把差值打印出来,差 1000 倍一眼就能看出。第三个坑是参数排序,很多客户端按 key 字典序拼 `k=v&`,但大小写敏感,`AppKey` 和 `appkey` 排序结果不同,得用 `sorted(d.items())` 实际验证。最后是二进制字段:文件上传接口的签名把文件 MD5 拼进去,而离线复现时读的是文本模式,`\r\n` 被转成 `\n`,摘要自然不符,得用 `open(p,'rb')`。对拍通过的标准是离线算出的签名与抓包里的 `X-Sign` 头逐字符相等,若不相等,二分掉 payload 的字段区间,缩小到具体哪个键参与签名、哪个被排除,坑就现形了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。