FBIがTorを破ったネットワーク捜査手法について

tor
FBIは過去にTorを利用して児童ポルノサイトにアクセスするユーザを1000人以上摘発したことが知られています。
その手法はMetasploit FrameworkのDecloaking Engineを利用したものだと言われています。
今回はDecloaking Engineとはなんなのかを調査してみたいと思います。

Decloaking Engineの概要

Decloaking Engineはクライアント側の技術とカスタムサービスの組み合わせを使用してプロキシ設定に関係なくWebユーザの実際のIPアドレスを示すためのシステムです。
このツールは脆弱性を用いた攻撃は行いません。

Decloking Engineの動作原理

  1. Webクライアントがホスト名を解決しようとすると、その設定されたDNSサーバに検索要求を送信します。クライアントのDNSサーバは特定のドメインのネームサーバにクエリを送信します。ホスト名は一位の識別子が含まれている場合、DNSサーバはクライアントのIPアドレスを関連づけることが可能です。しかし被害者がsocks4a, socks5を利用しており、socks proxy越しにdnsリクエストを送信する場合、この手法は通用しません。socks4を利用している場合は通用します。

  2. Javaアプレットは、ソケットAPIを使用してホスト名を解決しようとすると、ホスト名がアプレットを提供するWebサイトと同じでない場合、セキュリティ例外が発生します。しかし、セキュリティ例外がトリガされていても、DNS要求自体は依然として、クライアントのDNSサーバーに送信されます。これは、DNS対応プロキシサーバが使用中の場合、特定のクライアントがあっても例に、ウェブにアクセスして、そこからISPや企業をリークすることができます。

  3. JavaアプレットはUDPパケットを送信すると、パケットは通常、プロキシサービスを経由せずに送信されます。これは、Webクライアントの実際の外部IPアドレスをリークします。このメソッドは、Javaの新しいバージョンでは動作しない可能性があり、パケットの宛先は、アプレットを提供するホストのIPアドレスに制限されています。

  4. Javaが有効になっている場合、Webクライアントのホスト名とIPアドレスは、ソケットAPIにアクセスすることができます。この方法は、ユーザのホスト名とIPアドレスが漏洩します。言い換えれば、これは、システムがNATゲートウェイまたはプロキシサーバの背後にある場合でも、システムの内部IPアドレスが漏洩します。

  5. Flashプラグインがインストールされると、それは、バック元のホストへの直接TCP接続を可能にします。これらの接続は、ユーザーのホストの実際の外部アドレスが漏れ、プロキシサーバをバイパスすることがあります。

  6. Microsoft Officeがインストールされ、自動的に開いているドキュメントに設定されている場合、ファイルは自動的にインターネットから画像をダウンロードする返すことができます。これは、プロキシ設定を省略して、ユーザーの実際のDNSサーバを公開することができます。

カスタムDNSサーバ

動作原理で説明した手法を実行するためにはカスタムされたDNSサーバを用意しなければなりません。カスタムDNSサーバはmetasploit framework teamによりソースコードが公開されています。

#!/usr/bin/perl
###############

use Net::DNS::Nameserver;
use DBD::Pg;

use strict;
use warnings;

# Configure the user ID to run as (must start as root)
my $user = 1015;

# Configure the interfaces and ports
# You need :53 on the wildcard domain and :5353 on the IP running the web site
my $bind = [ ['0.0.0.0', 53], ['0.0.0.0', 5353] ];

# You need :53530 TCP on the IP running the web site
my $tcps = [ ['0.0.0.0', 53530] ];

# Wildcard subdomain we handle DNS for
my $dom  = "red.metasploit.com";

# Configure postgres credentials
my $db_name = "dbname";
my $db_user = "dbuser";
my $db_pass = "dbpass";
my $dbh;

my $opts = {
        AutoCommit => 1,
        RaiseError => 0,
};

# Escape the $dom var to be a valid regex
$dom =~ s/\./\\\./g;

foreach my $c ( @{$bind} ) {
        if (! fork()) {
                Launch($c->[0], $c->[1]);
                exit(0);
        }
}

foreach my $c ( @{$tcps} ) {
        if (! fork()) {
                LaunchTCP($c->[0], $c->[1]);
                exit(0);
        }
}

exit(0);

# This table must already exist
##
# Table "public.requests"
#  Column |         Type          | Modifiers
# --------+-----------------------+-----------
# cid    | character(32)         |
# type   | character varying(16) |
# eip    | character varying(16) |
# iip    | character varying(16) |
# dip    | character varying(16) |
# stamp  | timestamp             |
##

sub reply_handler {
        my ($qname, $qclass, $qtype, $peerhost) = @_;
        my ($rcode, @ans, @auth, @add);

        if ($qname =~ m/^([a-z0-9]{32})\.(\w+)\.(\d+\.\d+\.\d+\.\d+)\.(\d+\.\d+\.\d+\.\d+)\.$dom/) {
                # print "$peerhost > $qname (MATCH)\n";
                my ($cid, $type, $eip, $iip, $dip) = ($1, $2, $3, $4, $peerhost);
                my $sth = $dbh->prepare("INSERT INTO requests values (?, ?, ?, ?, ?, now())");
                $sth->execute($cid, $type, $eip, $iip, $dip);
                $sth->finish();
        }else{
                # print "$peerhost > $qname (NO MATCH)\n";
        }

         if ($qtype eq "A")
         {
             my ($ttl, $rdata) = (1, $peerhost);
             push @ans, Net::DNS::RR->new("$qname $ttl $qclass A $rdata");
             $rcode = "NOERROR";
         }
         elsif ($qtype eq "PTR") {
             my ($ttl, $rdata) = (1, $peerhost);
             push @ans, Net::DNS::RR->new("$qname $ttl $qclass A $rdata");
             $rcode = "NOERROR";
         }
         else {
             my ($ttl, $rdata) = (1, $peerhost);
             push @ans, Net::DNS::RR->new("$qname $ttl $qclass A $rdata");
             $rcode = "NOERROR";
         }

         # mark the answer as authoritive (by setting the 'aa' flag
         return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
}

sub Launch {

my $host = shift();
my $port = shift();

$0 .= " ($host:$port)";

$dbh = DBI->connect("DBI:Pg:dbname=$db_name", $db_user, $db_pass, $opts) || die "Couldn't connect to database: " . DBI->errstr;

my $ns = Net::DNS::Nameserver->new(
    LocalPort    => $port,
    LocalAddr    => $host,
    ReplyHandler => \&reply_handler,
    Verbose      => 0,
);


$ = $user;


if ($ns) {
        $ns->main_loop;
} else {
   die "Couldn't create nameserver object\n";
}

}

sub LaunchTCP {

my $host = shift();
my $port = shift();

$0 .= " TCP ($host:$port)";

my $srv =  IO::Socket::INET->new(
        'Proto'     => 'tcp',
        'LocalPort' => $port,
        'LocalAddr' => $host,
        'Listen'    => 5,
        'Reuse'     => 1
);

die unless $srv;

while (my $cli = $srv->accept()) {

        if(! fork()) {
                my $sel = IO::Select->new($cli);
                $cli->autoflush(1);
                if ($sel->can_read(5)) {
                        my $buf = ;
                        if ($buf && $buf =~ m/^([a-z0-9]{32}):(.*)/i) {
                                my $cid = $1;
                                my $eip = $2;
                                chomp($eip);

                                $dbh = DBI->connect("DBI:Pg:dbname=$db_name", $db_user, $db_pass, $opts) || die "Couldn't connect to database: " . DBI->errstr;
                                my $sth = $dbh->prepare("INSERT INTO requests values (?, ?, ?, ?, ?, now())");
                                $sth->execute($cid, 'socket', $eip, '0.0.0.0', $cli->peerhost);
                                $sth->finish();
                                print $cli ($cli->peerhost . "\x00");
                        }
                }
                $cli->close();
                exit(0);
        }
}

}

カスタムDNSサーバはPerlのはNet::DNS::Nameserverのスクリプトを使用して要求を処理し、特定のドメインに対するすべての要求を処理します。このDNSサーバーの巧妙な機能はspy.metasploit.comドメイン内の任意のホストを検索すると、独自に設定されたDNSサーバの外部IPアドレスを返すことです。また、このサーバーはフラッシュからJavaやTCP要求からのUDP要求を処理します。

decloaking Engineの現状と未来

Decloaking Engineは2008年に発表されましたが、現時点では削除されているようです。確かに陳腐化する手法ではあると思いますが、FBIが利用していると言うならば、基本となる考え方は実績があり、例えば日々発見されるFlashなどの脆弱性を組み込めば使えるようになるはずです。
しかし未来を見据えるなら、Flashは今後どのブラウザでも廃止されHTML5になるでしょう。JavaAppletやJavaはTorブラウザからは使用すべきではないということはTorを利用している人なら多くの人が理解していますし、デフォルト設定ではそうなっています。

まとめ

FBIの現時点での手法は苦肉の策であり、脆弱性を組み込まない限り成功率も低く、将来的にも使えるものではないようです。DeepWebは必要に駆られたジャーナリストなどに使用されるならば、新たな可能性を秘めていると考えますが、児童ポルノに関わる犯罪をなくすためにも新たな手法が望まれています。

  • t4ilint

    FBIがやったのはVidaliaの開発者を抱き込んでそこから流しました
    いわゆる、ネットワークマップが表示されるものです
    残念ながら、あなたのやり方ではTorBrowserはブロックするでしょう
    Gigazineなどで書かれている攻撃方法はあくまでも推測の一つで、これだけでは攻撃者が政府回線でC&Cサーバを用意して攻撃した意味が無いです

    二期の一斉検挙の際に使われたと思われる手法の勝手な推測ですが
    ブラウザをフックした上でVidaliaをoverflowさせ、
    そこからトロイの木馬を実行する
    が現時的にありそうだと思います

    幾つかの海外メディアサイト曰く、児童ポルノのファイルハッシュが見つからなければ自己消滅するらしいので、事件が起きた時期も繋げてました

    今思うと、Vidaliaはbugfixにはだいぶ消極的でしたね

  • GMa6f

    t4lintさんがおっしゃっている通り記事で説明した手法はTorBrowserをデフォルト設定で動かしていた場合、通用しません。
    しかしFBIはDecloaking Engineを使用した調査を行い犯人を逮捕したことは事実であると思います。
    2015年1月に児童ポルノ所持で捕まった被告の裁判で、Declocking
    Engineを用いて得た証拠に科学的な根拠があるのかを調査を裁判所がセキュリティ研究者に要求しています。
    以下のpdfが実際に提出された報告書です。
    https://assets.documentcloud.org/documents/2124281/fbi-tor-busting-227-1.pdf
    どれもTor Browserをデフォルト設定で使っていた場合は通用しなさそうな内容ですが、児童ポルノを快適に閲覧するためのセキュリティレベルを落としていたのかもしれませんね。

    また記事内でも言及していますが情報をリークするための具体的な手法はは陳腐化していますが、根本的な考え方は通用すると思います。
    Declocking Engineに新たな脆弱性を組み込まれれば、Torの匿名性を破ることはできるはずです。