freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

利用视觉模糊测试技术探索Z͌̈́̾a͊̈́l͊̿g̏̉͆o̾̚̚S̝̬ͅc̬r̯̼͇ͅi̼͖̜̭͔p̲̘...
2018-03-16 10:50:16

请大家先观察下面这条JavaScript语句:

̀̀̀̀̀́́́́́̂̂̂̂̂̃̃̃̃̃̄̄̄̄̄̅̅̅̅̅̆̆̆̆̆̇̇̇̇̇̈̈̈̈̈̉̉̉̉̉̊̊̊̊̊ͅͅͅͅͅͅͅͅͅͅͅalert(̋̋̋̋̋̌̌̌̌̌̍̍̍̍̍̎̎̎̎̎̏̏̏̏̏ͅͅͅͅͅ1̐̐̐̐̐̑̑̑̑̑̒̒̒̒̒̓̓̓̓̓̔̔̔̔̔ͅͅͅͅͅ)̡̡̡̡̡̢̢̢̢̢̛̛̛̛̛̖̖̖̖̖̗̗̗̗̗̘̘̘̘̘̙̙̙̙̙̜̜̜̜̜̝̝̝̝̝̞̞̞̞̞̟̟̟̟̟̠̠̠̠̠̣̕̕̕̕̕̚̚̚̚̚ͅͅͅͅͅͅͅͅͅͅͅͅͅͅͅ

zalgo_text_by_kaidandelrose-d79lx75.jpg

写在前面的话

当Twitter宣布将推文字符限制从140提升到280之后,我就想弄清楚在这种新的字符限制下,如何使用Unicode字符来“搞事情”。我测试了某些特殊字符,并在Twitter上引起了一些显示错误,即所谓的ZalgoScript。此时我就想知道如何自动化地去识别这些字符,因为你无法通过查看页面DOM树结构来发现某些字符的特殊行为,我们需要截图才能知道浏览器所要呈现的视图。我一开始使用的是JavaScript和canvas技术来进行截图,但生成的图片并不匹配浏览器中真正呈现的画面。所以我需要其他的方法,Headless Chrome就是最佳的选择。我在这里使用了puppeteer,这是一个NodeJS模块,允许我们控制Headless Chrome并获取截图。

生成字符

为了生成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

比如说,下面的JavaScript代码可以利用上面的某些字符来生成非常难看的文本:

<script>document.write(String.fromCharCode(834).repeat(20))</script>

得到的结果为: ͂͂͂͂͂͂͂͂͂͂͂͂͂͂͂͂͂͂͂͂

有趣的是,如果合并使用多个字符的话,还可以产生不同的效果。比如说,用字符311和844合并之后,同样的技术会产生如下的效果:

<script>document.write(String.fromCharCode(311)+String.fromCharCode(844).repeat(20))</script>

得到的结果为:ķ͌͌͌͌͌͌͌͌͌͌͌͌͌͌͌͌͌͌͌͌

构建模糊测试器

模糊测试器的构建其实非常简单,首先我们需要一个用来呈现字符效果的Web页面,然后通过CSS来增加页面宽度,并让正常字符平移到页面右侧,这样我们就可以检测页面的左侧、顶部和底部了,我把需要测试的div元素放在了页面中心。

下图为字符“a”和“b”呈现在fuzzer中的效果,为了让大家更好地理解fuzzer的处理过程,我们把fuzzer检查的地方在图中进行了标注:

2.png

而上述代码所呈现的字符ķ和~的码点为311和834,fuzzer也记录下了这两个字符所生成的有趣的视图效果:

3.png

<style>
.parent{
  position: absolute;
  height: 50%;
  width: 50%;
  top: 50%;
  -webkit-transform: translateY(-50%);
  -moz-transform:    translateY(-50%);
  -ms-transform:     translateY(-50%);
  -o-transform:      translateY(-50%);
  transform:         translateY(-50%);
}
.fuzz{
  height: 300px;
  width:5000px;
  position: relative;
  left:50%;
  top: 50%;
  transform: translateY(-50%);
}
</style>
</head>
<body>
<divclass="parent">
  <div class="fuzz"id="test"></div>
</div>
<script>
varchars = 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>

JavaScript会从查询字符串中读取一到两个字符编码,然后使用innerHTML和String.fromCharCode来输出它们,这一切都是在客户端实现的。

然后在NodeJS中,我还需要使用PNG库和puppeteer库:

const PNGReader = require('png.js');
const puppeteer = require('puppeteer');

接下来,我可以使用下面这两个函数来判断某个像素是否为白色,并且是否位于目标区域中(顶部、左侧、右侧或底部):

function isWhite(pixel) {
  if(pixel[0] === 255 && pixel[1] ===255 && pixel[2] === 255) {
    return true;
  } else {
    return false;
  }
}
 
function isInRange(x,y) {
  if(y <= 120) {
   return true;
  }
  if(y >= 220) {
   return true;
  }
  if(x <= 180) {
   return true;
  }
  return false;
}

fuzzBrowser()是一个异步函数,可以进行屏幕截图,也可以利用png库来读取png文件。这个函数可以将我们的特殊字符输出到控制台以及chars.txt文件之中。

async function fuzzBrowser(writeStream, page, chr1, chr2) {
  if(typeof chr2 !== 'undefined') {
    awaitpage.goto('http://localhost/visualfuzzer/index.php?'+chr1+','+chr2);
  } else {
    awaitpage.goto('http://localhost/visualfuzzer/index.php?'+chr1);
  }
  await page.screenshot({clip:{x:0,y:0,width:400,height: 300}}).then((buf)=>{
    var reader = new PNGReader(buf);
    reader.parse(function(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)) {
            if(typeof chr2 !== 'undefined') {
             writeStream.write(chr1+','+chr2+'n');
              console.log('Interesting chars:'+chr1+','+chr2);
            } else {
              writeStream.write(chr1+'n');
              console.log('Interesting char:'+chr1);
            }
            break outerLoop;
          }
        }
      }
    });
  });
}

接下来,我需要构造一个匿名异步函数,用来循环处理目标字符并调用fuzzBrowser()函数。我对多种字符组合场景进行了测试,并排除掉了一些可能会出现错误的字符:

(async() => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
 
 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 fs = require('fs');
  let writeStream =fs.createWriteStream('logs.txt', {flags: 'a'});
  for(let i=768;i<=879;i++) {
    for(let j=768;j<=879;j++) {
        if(singleChars[i] || singleChars[j]) {
          continue;
        }
        process.stdout.write("Fuzzingchars "+i+","+j+" r");
        await fuzzBrowser(writeStream, page, i,j).catch(err=>{
          console.log("Failed fuzzingbrowser:"+err);
        });
    }
  }
  await browser.close();
  await writeStream.end();
})();

ZalgoScript

就在不久之前,我在Edge浏览器中发现了一个非常有趣的漏洞,简单来说,Edge会错误地将某些特殊字符当成空白字符,而某些Unicode字符组合在一起的时候就会触发这种漏洞。因此,我们只需要将Zalgo应用到这里,就能够利用该漏洞,并得到我们想要的ZalgoScript了。首先,我们需要生成一份字符列表,而Edge会将该列表中所有的字符都当作空白字符。这里我选择的是768-879之间的字符,根据测试结果显示,字符837跟768-879之间的字符如果组合在一起的话,将产生非常好的视觉效果。因此,我循环遍历这个列表来进行字符组合,最终生成的代码既是ZalgoScript,又是JavaScript。

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(''));

这也就是我们文章开头̀̀̀̀̀́́́́́̂̂̂̂̂̃̃̃̃̃̄̄̄̄̄̅̅̅̅̅̆̆̆̆̆̇̇̇̇̇̈̈̈̈̈̉̉̉̉̉̊̊̊̊̊ͅͅͅͅͅͅͅͅͅͅͅalert(̋̋̋̋̋̌̌̌̌̌̍̍̍̍̍̎̎̎̎̎̏̏̏̏̏ͅͅͅͅͅ1̐̐̐̐̐̑̑̑̑̑̒̒̒̒̒̓̓̓̓̓̔̔̔̔̔ͅͅͅͅͅ)̡̡̡̡̡̢̢̢̢̢̛̛̛̛̛̖̖̖̖̖̗̗̗̗̗̘̘̘̘̘̙̙̙̙̙̜̜̜̜̜̝̝̝̝̝̞̞̞̞̞̟̟̟̟̟̠̠̠̠̠̣̕̕̕̕̕̚̚̚̚̚ͅͅͅͅͅͅͅͅͅͅͅͅͅͅͅ的由来。

资源获取

可视化Fuzzer:【源代码

Zalgo文本生成器:【传送门

* 参考来源:portswigger,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM

# ZalgoScript
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者