利用视觉模糊测试技术探索Z͌̈́̾a͊̈́l͊̿g̏̉͆o̾̚̚S̝̬ͅc̬r̯̼͇ͅi̼͖̜̭͔p̲̘̘̹͖t̠͖̟̹͓͇ͅ
字数 853 2025-08-18 11:37:07
ZalgoScript与视觉模糊测试技术详解
1. ZalgoScript概述
ZalgoScript是一种利用Unicode组合字符创造视觉混乱效果的JavaScript代码技术。这些特殊字符会导致文本在浏览器中呈现异常,产生"溢出"或"破坏"正常布局的效果。
1.1 技术背景
- 起源于Twitter将字符限制从140提升到280后对Unicode字符的探索
- 某些Unicode组合字符会导致浏览器渲染异常
- 无法通过DOM树结构直接识别,必须通过视觉呈现检测
2. Unicode组合字符基础
2.1 关键Unicode码点
以下码点通过自我重复可产生Zalgo效果(多为Unicode合并字符):
834, 1425, 1427, 1430, 1434, 1435, 1442, 1443, 1444,
1445, 1446, 1447, 1450, 1453, 1557, 1623, 1626, 3633,
3636, 3637, 3638, 3639, 3640, 3641, 3642, 3655, 3656,
3657, 3658, 3659, 3660, 3661, 3662
2.2 字符生成方法
// 单个字符重复
document.write(String.fromCharCode(834).repeat(20))
// 多个字符组合
document.write(String.fromCharCode(311)+String.fromCharCode(844).repeat(20))
3. 视觉模糊测试器构建
3.1 测试页面设计
<style>
.parent {
position: absolute;
height: 50%;
width: 50%;
top: 50%;
transform: translateY(-50%);
}
.fuzz {
height: 300px;
width:5000px;
position: relative;
left:50%;
top: 50%;
transform: translateY(-50%);
}
</style>
<body>
<div class="parent">
<div class="fuzz" id="test"></div>
</div>
<script>
var chars = location.search.slice(1).split(',');
if(chars.length > 1) {
document.getElementById('test').innerHTML =
String.fromCharCode(chars[0]) +
String.fromCharCode(chars[1]).repeat(100);
} else {
document.getElementById('test').innerHTML =
String.fromCharCode(chars[0]).repeat(100);
}
</script>
3.2 测试流程
- 从查询字符串读取1-2个字符编码
- 使用innerHTML和String.fromCharCode输出字符
- 通过CSS将内容平移以便检测边缘溢出
4. 自动化测试实现
4.1 依赖工具
- Headless Chrome
- Puppeteer (NodeJS模块)
- PNG库
4.2 核心代码实现
const PNGReader = require('png.js');
const puppeteer = require('puppeteer');
// 判断像素是否为白色
function isWhite(pixel) {
return pixel[0] === 255 && pixel[1] === 255 && pixel[2] === 255;
}
// 判断像素是否在目标区域(顶部、左侧、右侧或底部)
function isInRange(x,y) {
return y <= 120 || y >= 220 || x <= 180;
}
// 模糊测试主函数
async function fuzzBrowser(writeStream, page, chr1, chr2) {
const url = typeof chr2 !== 'undefined'
? `http://localhost/visualfuzzer/index.php?${chr1},${chr2}`
: `http://localhost/visualfuzzer/index.php?${chr1}`;
await page.goto(url);
const buf = await page.screenshot({
clip: {x:0, y:0, width:400, height: 300}
});
const reader = new PNGReader(buf);
reader.parse((err, png) => {
if(err) throw err;
outerLoop:
for(let x=0; x<400; x++) {
for(let y=0; y<300; y++) {
if(!isWhite(png.getPixel(x,y)) && isInRange(x,y)) {
const output = typeof chr2 !== 'undefined'
? `${chr1},${chr2}\n`
: `${chr1}\n`;
writeStream.write(output);
console.log(`Interesting char(s): ${output.trim()}`);
break outerLoop;
}
}
}
});
}
4.3 测试执行
(async() => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const fs = require('fs');
// 已知有效的单个字符
const singleChars = {
834:1,1425:1,1427:1,1430:1,1434:1,1435:1,1442:1,
1443:1,1444:1,1445:1,1446:1,1447:1,1450:1,1453:1,
1557:1,1623:1,1626:1,3633:1,3636:1,3637:1,3638:1,
3639:1,3640:1,3641:1,3642:1,3655:1,3656:1,3657:1,
3658:1,3659:1,3660:1,3661:1,3662:1
};
const writeStream = fs.createWriteStream('logs.txt', {flags: 'a'});
// 测试768-879范围内的字符组合
for(let i=768; i<=879; i++) {
for(let j=768; j<=879; j++) {
if(singleChars[i] || singleChars[j]) continue;
process.stdout.write(`Fuzzing chars ${i},${j}\r`);
await fuzzBrowser(writeStream, page, i, j)
.catch(err => {
console.log("Failed fuzzing browser:"+err);
browser.close();
writeStream.end();
});
}
}
await browser.close();
await writeStream.end();
})();
5. ZalgoScript实际应用
5.1 Edge浏览器漏洞利用
在Edge浏览器中,某些特殊字符组合会被错误地识别为空白字符,从而可以构造有效的ZalgoScript。
5.2 示例代码
a=[];
for(i=768;i<=858;i++){
a.push(String.fromCharCode(837)+String.fromCharCode(i).repeat(5));
}
a[10]+='alert('
a[15]+='1';
a[20]+=')';
input.value=a.join('')
eval(a.join(''));
5.3 典型ZalgoScript
̀ͅͅalert(1ͅͅ
6. 防御措施
- 输入过滤:严格限制允许的Unicode字符范围
- 输出编码:对动态内容进行适当的HTML编码
- 内容安全策略(CSP):限制内联脚本执行
- 浏览器更新:保持浏览器最新版本以修复已知渲染问题
7. 资源
通过以上技术,安全研究人员可以系统地发现和测试浏览器对Unicode组合字符的渲染异常,从而识别潜在的ZalgoScript攻击向量。