前面的话:本人项目实际遇上并解决的问题,可能写得不够完善,求大神给个装X的机会,也希望能够给未遇到过此问题的后来者一点提点!
正文:问题是在前一段时间调用一个第三方网站的接口(A接口)时遇上的。
A接口是一个jsonp调用的接口,当前使用get请求时,会返回一个js函数调用,由于我使用的是Node.js的http原生库进行封装的请求库,默认是不会自动处理jsonp的返回数据,所以得自行进行解析。
如,以下是某次请求返回的消息体
var body = `_jsInvoke({
"errcode": 1000,
"errmsg": "Invalid request trace id.",
"data": [],
"datetime": "2018-05-02 15:22:53"
})`
于是,出于一般的思维,我们就使用了new Function进行处理返回的消息体,代码如下:
var obj = (new Function(`function _jsInvoke(obj) {return obj} return ${body}`))();
Then? 代码正常执行,结果成功返回,没有任何问题,如些正常运行了半个月,直到这一天,我们突然发现在pm2 日志里面突然多次出现一些问题日志。
[2018-05-10 12:56:23] [Laravel Req Error] 非法请求,已拒绝连接!
[2018-05-12 08:41:50] [Laravel Req Error] 订单号出错!
[2018-05-16 17:32:56] [Laravel Req Error] IP黑名单已满!
问题来了时,当时最受关注的是,在一个pm2运行的Node.js项目里面,怎么会有Laravel的日志?因为在pm2中的log日志,是不会显示出错栈跟踪的,所以,我们只能做一些日志跟踪测试,(跟踪过程省略......)
终于,在5月22日早上,奇怪的pm2日志再次出现,而这次我们就迅速定位到上面的用到的new Function处理方法中,而这些错误的pm2日志,正是从里面打印出来的,
奇怪?
new Function里面没有console.log啊?
除非,console.log被调用了,不可能打印出log啊?
对,console.log被调用了,console.log被调用了,console.log被调用了...
好恐怖的现象......现在想起来还有点后怕,幸亏没造成什么损失!
以下,我们来分析一下,问题是怎样产生的,这个现象会造成什么严重效果。
我们都知道:
- jsonp请求返回的是一个javascript脚本字符串,并且响应头为Content-Type:text/javascript;
- new Function 方法的调用,可以把javascript脚本字符串翻译为javascript语法进行执行;
- node.js的变量使用或才函数调用,如果没有严格规定作用域,则会一层层往上追溯到最顶层的对象global中;
所以为什么之前刚接入接口是,能正常执行,是因为接口返回的数据正好是符合我们需要的规范,所以return之后能够把obj当成js对象返回。
但后来,返回的数据变成了如下:
var body = `console.log("这里是错误信息!")`
则new Function里面的字符串就变成了如下:
var obj = (new Function(`function _jsInvoke(obj) {return obj} return console.log("这里是错误信息!")`))();
于时,console.log就被调用了!
深入一下:
如果请求响应数据是这样子:
var body = `(function() {
while(true){}
})()`
那么当前进程就一直处于死循环中,直到资源耗尽,进程崩溃!如果对一个正在线上运行的项目,这是致命的!
再深入一下:
如果请求响应数据是这样子:
var body = `this.constructor.constructor("return require('child_process')")()`
呵呵,这样子处理的数据就可以逃逸出去,严重的话,服务器就GG了。
所以,在任何第三请求以及回调中,一定要经过严格的数据处理,不然最后自己的服务器被夺走了,也没发现问题!