首页 > 解决方案 > Perl:同形异义词攻击。可以比较 ascii / non-ascii 字符串,视觉上相似吗?

问题描述

我遇到了这种所谓的“同形异义词攻击”,我想拒绝解码后的 punycode 在视觉上似乎只是字母数字的域。例如,www.xn--80ak6aa92e.com 将在浏览器 (Firefox) 中显示 www.apple.com。域在视觉上是相同的,但字符集是不同的。Chrome 已经对此进行了修补,并且浏览器会显示 punycode。

我有下面的例子。

#!/usr/bin/perl

use strict;
use warnings;

use Net::IDN::Encode ':all';
use utf8;                             


my $testdomain = "www.xn--80ak6aa92e.com";
my $IDN = domain_to_unicode($testdomain);
my $visual_result_ascii = "www.apple.com";

print "S1: $IDN\n";
print "S2: $visual_result_ascii";
print "MATCH" if ($IDN eq $visual_result_ascii);

视觉上是相同的,但它们不会匹配。可以将 unicode 字符串 ($IDN) 与字母数字字符串进行比较,视觉上是否相同?

标签: perlidnpunycode

解决方案


Punycode 转换器转换的示例会产生以下 UTF-8 字符串:

www.аррӏе.com
$ perl -e 'printf("%02x ", ord) for split("", "www.аррӏе.com"); print "\n"'
77 77 77 2e d0 b0 d1 80 d1 80 d3 8f d0 b5 2e 63 6f 6d

作为 Unicode:

$ perl -Mutf8 -e 'printf("%04x ", ord) for split("", "www.аррӏе.com"); print "\n"'
0077 0077 0077 002e 0430 0440 0440 04cf 0435 002e 0063 006f 006d

使用@ikegamis 输入:

$ perl -Mutf8 -MEncode -e 'print encode("UTF-8", $_) for ("www.аррӏе.com" =~ /\p{Cyrillic}/g); print "\n"'
аррӏе
$ perl -Mutf8 -MEncode -e 'print encode("UTF-8", $_) for ("www.аррӏе.com" =~ /\P{Cyrillic}/g); print "\n"'
www..com

创见

我不确定是否存在此代码,但我的第一个想法是创建一个地图\N{xxxx}->“视觉等效 ASCII/UTF-8 代码”。然后,您可以将映射应用于 Unicode 字符串以将其“转换”为 ASCII/UTF-8 代码,并将生成的字符串与域列表进行比较。

示例代码(我跳过了 IDN 解码内容并直接在测试数据中使用 UTF-8 结果)。这可能仍然可以改进,但至少它显示了这个想法。

#!/usr/bin/perl
use strict;
use warnings;

use utf8;
use Encode;

# Unicode (in HEX) -> visually equal ASCII/ISO-8859-1/... character
my %unicode_to_equivalent = (
   '0430' => 'a',
   '0435' => 'e',
   '04CF' => 'l',
   '0440' => 'p',
);

while (<DATA>) {
    chomp;

    # assuming that this returns a valid Perl UTF-8 string
    #my $IDN = domain_to_unicode($_);
    my($IDN, $compare) = split(' ', $_) ; # already decoded in test data

    my $visually_decoded =
        join('',              # merge result
             map {            # map, if mapping exists
                 $unicode_to_equivalent{sprintf("%04X", ord($_))} // $_
             }
             split ('', $IDN) # split to characters
        );

    print "Testing: ", encode('UTF-8', $IDN), " -> $compare ";
    print "Visual match!"
        if ($visually_decoded eq $compare);
    print "\n";
}

exit 0;

__DATA__
www.аррӏе.com www.apple.com

测试运行(取决于答案中的复制和粘贴是否保留了原始 UTF-8 字符串)

$ perl dummy.pl
Testing: www.аррӏе.com -> www.apple.com Visual match!

计算字符串中的脚本数

#!/usr/bin/perl
use strict;
use warnings;

use utf8;
use Encode;
use Unicode::UCD qw(charscript);

while (<DATA>) {
    chomp;

    # assuming that this returns a valid Perl UTF-8 string
    #my $IDN = domain_to_unicode($_);
    my($IDN) = $_;  # already decoded in test data

    # Unicod characters
    my @characters = split ('', $IDN);

    # See UTR #39: Unicode Security Mechanisms
    my %scripts =
        map { (charscript(ord), 1) } # Codepoint to script
        @characters;
    delete %scripts{Common};

    print 'Testing: ',
        encode('UTF-8', $IDN),
        ' (', join(' ', map { sprintf("%04X", ord) } @characters), ')',
        (keys %scripts == 1) ? ' not' : '', " suspicious\n";
}

exit 0;

__DATA__
www.аррӏе.com
www.apple.com
www.école.fr

测试运行(取决于答案中的复制和粘贴是否保留了原始 UTF-8 字符串)

$ perl dummy.pl
Testing: www.аррӏе.com (0077 0077 0077 002E 0430 0440 0440 04CF 0435 002E 0063 006F 006D) suspicious
Testing: www.apple.com (0077 0077 0077 002E 0061 0070 0070 006C 0065 002E 0063 006F 006D) not suspicious
Testing: www.école.fr (0077 0077 0077 002E 00E9 0063 006F 006C 0065 002E 0066 0072) not suspicious

推荐阅读