Security Advisories (4)
CVE-2026-57079 (2026-06-30)

Net::BitTorrent versions through 2.0.1 for Perl write files outside the download directory via path traversal in peer-supplied metadata. Net::BitTorrent validates file path components only on the .torrent-file ingest path. The peer and magnet metadata path (_on_metadata_received, reached from the BEP09 ut_metadata extension) passes attacker-supplied file names straight to Storage::add_file and Storage::_parse_file_tree, where Path::Tiny's child() does not collapse "..". A v2 file tree key, a v1 files[].path element, or a single-file name containing ".." segments therefore resolves outside the download directory. Because the peer also controls the piece hashes and the served bytes, content verification passes, so a malicious magnet or peer writes attacker-chosen content to an attacker-chosen path on the downloading host.

CVE-2026-57080 (2026-06-30)

Net::BitTorrent versions through 2.0.1 for Perl allow remote memory exhaustion via an uncapped peer-wire message-length prefix. The peer-wire framing in _process_messages trusts the 4-byte length prefix sent by a connected peer with no upper bound, while receive_data appends every inbound byte to the input buffer. A peer announces a length prefix of up to about 4 GiB and then streams bytes; the decoder waits until the buffer holds the full message before processing it, so the buffer grows without limit. Peer connections are unauthenticated, so any peer in the swarm exhausts the downloading process's memory. The largest legitimate message is a 16 KiB piece block, so any announced length far above that is anomalous.

CVE-2026-57082 (2026-06-30)

Net::BitTorrent versions through 2.0.1 for Perl generate the MSE Diffie-Hellman private key with a non-cryptographic PRNG. The MSE (Message Stream Encryption) handshake derives its 160-bit Diffie-Hellman private key from Perl's rand(), a non-cryptographic drand48-class generator seeded once per process, in KeyExchange.pm. The shared secret and the RC4 keys derived from it (the SHA-1 of "keyA" or "keyB", the shared secret, and the infohash) therefore depend entirely on a predictable PRNG. The same handshake sends, in cleartext, random padding drawn from the same rand() sequence in _random_pad, immediately after the public key and the private-key draw. A passive observer of the handshake recovers the PRNG state from the cleartext padding, reconstructs the private key, computes the shared secret from the peer's public key on the wire, derives the RC4 keys, and decrypts the connection, defeating the passive-observation obfuscation MSE provides.

CVE-2026-57081 (2026-06-30)

Net::BitTorrent versions through 2.0.1 for Perl allow remote memory exhaustion via deeply nested bencoded input. bdecode recurses once per nested list or dictionary level with no depth cap, and each recursive call receives the remaining buffer by value while the list and dictionary branches capture the whole remainder, so every live recursion frame keeps its own copy of the shrinking buffer (O(N^2) bytes for an N-deep input). The decoder runs on every untrusted bencode source: .torrent files, BEP09 metadata fetched from peers, DHT messages, and tracker responses. A bencoded input of roughly 150,000 nested lists (about 150 KB on the wire) drives multi-gigabyte peak memory, so one short message from any peer, or one crafted .torrent file or magnet link, terminates the client.

NAME

Net::BitTorrent::DHT - BitTorrent Mainline DHT implementation

SYNOPSIS

use Net::BitTorrent::DHT;
use Net::BitTorrent::DHT::Security;

# Initialize security and generate a BEP 42 compliant ID
my $security = Net::BitTorrent::DHT::Security->new();
my $node_id  = $security->generate_node_id('12.34.56.78'); # Use your external IP

# Create the DHT instance
my $dht = Net::BitTorrent::DHT->new(
    node_id_bin => $node_id,
    port        => 6881,
    v           => 'NB01' # Client version
);

# Join the network
$dht->bootstrap();

# Main Loop
while (1) {
    # Process packets with a 100ms timeout
    my ($new_nodes, $found_peers, $data) = $dht->tick(0.1);

    # Handle discovered peers
    for my $peer (@$found_peers) {
        printf "Discovered peer: %s\n", $peer->to_string;
    }
}

DESCRIPTION

Net::BitTorrent::DHT is a feature-complete implementation of the BitTorrent Mainline DHT protocol. It supports IPv4, IPv6 (BEP 32), Security Extensions (BEP 42), Scrapes (BEP 33), Arbitrary Data Storage (BEP 44), and Infohash Indexing (BEP 51).

METHODS

new( %params )

Creates a new DHT node instance.

my $dht = Net::BitTorrent::DHT->new(
    port    => 6881,
    want_v6 => 1,
    bep44   => 1
);

This method initializes a new DHT node with the specified configuration.

Expected parameters:

node_id_bin - optional

The 20-byte binary string representing the local node ID. Defaults to a randomly generated ID.

port - optional

The UDP port to listen on. Defaults to 6881.

address - optional

The local IP address to bind to.

want_v4 - optional

Whether to enable IPv4 support. Defaults to true.

want_v6 - optional

Whether to enable IPv6 support. Defaults to true.

bep32 - optional

Enable BEP 32 (IPv6). Defaults to true.

bep42 - optional

Enable BEP 42 (Security). Defaults to true.

bep33 - optional

Enable BEP 33 (Scrapes). Defaults to true.

bep44 - optional

Enable BEP 44 (Data Storage). Defaults to true.

bep51 - optional

Enable BEP 51 (Indexing). Defaults to true.

boot_nodes - optional

An array reference of bootstrap nodes.

on( $event, $callback )

Registers an event handler.

$dht->on( external_ip_detected => sub ( $ip ) { ... });

This method allows reacting to DHT events like external IP detection.

Expected parameters:

$event

The name of the event to listen for.

$callback

The code reference to execute.

bootstrap( )

Joins the DHT network.

$dht->bootstrap( );

This method queries the configured bootstrap nodes to join the DHT network.

Expected parameters:

None.

tick( [$timeout] )

Processes DHT events.

my ( $nodes, $peers, $data ) = $dht->tick( 0.1 );

This method handles incoming packets, timeouts, and periodic tasks. It returns an array containing discovered nodes, peers, and any received data.

Expected parameters:

$timeout - optional

The maximum time to wait for activity in seconds. Defaults to 0.

ping( $addr, [$port] )

Sends a ping query.

$dht->ping( 'router.bittorrent.com', 6881 );

This method sends a ping query to the specified node to check for liveness.

Expected parameters:

$addr

The IP address or hostname of the target node.

$port - optional

The port of the target node.

find_node_remote( $target_id, $addr, [$port] )

Sends a find_node query.

$dht->find_node_remote( $target_id, '1.2.3.4', 6881 );

This method queries a specific remote node for nodes close to the target ID.

Expected parameters:

$target_id

The 20-byte binary target node ID.

$addr

The IP address of the remote node.

$port - optional

The port of the remote node.

get_peers( $info_hash, $addr, [$port] )

Sends a get_peers query.

$dht->get_peers( $info_hash, '1.2.3.4', 6881 );

This method queries a specific remote node for peers associated with the infohash.

Expected parameters:

$info_hash

The 20-byte binary infohash.

$addr

The IP address of the remote node.

$port - optional

The port of the remote node.

announce_infohash( $info_hash, $port )

Announces the local node as a peer.

$dht->announce_infohash( $info_hash, 6881 );

This high-level method finds nodes closest to the given infohash and announces the local client's port.

Expected parameters:

$info_hash

The 20-byte binary infohash.

$port

The TCP port where the client is listening.

find_peers( $info_hash )

Queries local nodes for peers.

$dht->find_peers( $info_hash );

This method queries nodes in the local routing table for peers associated with the infohash.

Expected parameters:

$info_hash

The 20-byte binary infohash.

scrape( $info_hash )

Queries local nodes for swarm stats (BEP 33).

$dht->scrape( $info_hash );

This method queries the local routing table for seeders/leechers counts.

Expected parameters:

$info_hash

The 20-byte binary infohash.

sample( $target_id )

Queries local nodes for infohashes (BEP 51).

$dht->sample( $target_id );

This method queries the local routing table for infohash samples.

Expected parameters:

$target_id

The 20-byte binary target ID to sample around.

announce_peer( $info_hash, $token, $announce_port, $addr, [$port, $seed] )

Sends an announce_peer query.

$dht->announce_peer( $hash, $token, 6881, '1.2.3.4', 6881 );

This method announces the local node's presence to a specific remote node.

Expected parameters:

$info_hash

The 20-byte binary infohash.

$token

The token received from a previous get_peers response.

$announce_port

The TCP port to announce.

$addr

The IP address of the remote node.

$port - optional

The port of the remote node.

$seed - optional

Whether the local node is a seed.

get_remote( $target, $addr, [$port] )

Retrieves data from a specific node (BEP 44).

$dht->get_remote( $target_hash, '1.2.3.4', 6881 );

Expected parameters:

$target

The 20-byte SHA-1 hash of the data key.

$addr

The IP address of the remote node.

$port - optional

The port of the remote node.

put_remote( $args, $addr, [$port] )

Stores data (BEP 44) on a specific node.

$dht->put_remote( { v => 'data', token => $t }, '1.2.3.4', 6881 );

Expected parameters:

$args

A hash reference containing the data and token.

$addr

The IP address of the remote node.

$port - optional

The port of the remote node.

scrape_peers_remote( $info_hash, $addr, [$port] )

Directly queries a node for swarm statistics.

$dht->scrape_peers_remote( $hash, '1.2.3.4', 6881 );

Expected parameters:

$info_hash

The 20-byte binary infohash.

$addr

The IP address of the remote node.

$port - optional

The port of the remote node.

sample_infohashes_remote( $target, $addr, [$port] )

Directly queries a node for infohash samples.

$dht->sample_infohashes_remote( $target, '1.2.3.4', 6881 );

Expected parameters:

$target

The 20-byte binary target ID.

$addr

The IP address of the remote node.

$port - optional

The port of the remote node.

routing_table_stats( )

Returns routing table statistics.

my $stats = $dht->routing_table_stats( );

Expected parameters:

None.

export_state( )

Returns the current DHT state.

my $state = $dht->export_state( );

Expected parameters:

None.

import_state( $state )

Restores the DHT state.

$dht->import_state( $state );

Expected parameters:

$state

The state hash reference.

set_node_id( $new_id )

Updates the local node ID.

$dht->set_node_id( $new_id );

Expected parameters:

$new_id

The new 20-byte binary node ID.

external_ip( )

Returns the current external IP address.

my $ip = $dht->external_ip();

Expected parameters:

None.

run( )

Starts a blocking event loop.

$dht->run( );

Expected parameters:

None.

handle_incoming( [$data, $sender] )

Processes a packet.

my ( $nodes, $peers, $data ) = $dht->handle_incoming( $raw_data, $sender_sockaddr );

Expected parameters:

$data - optional

The raw packet data.

$sender - optional

The sender's socket address.

node_id_bin( )

Returns the local node ID.

my $id = $dht->node_id_bin();

Expected parameters:

None.

port( )

Returns the UDP port.

my $port = $dht->port();

Expected parameters:

None.

boot_nodes( )

Returns or sets the bootstrap nodes.

my $nodes = $dht->boot_nodes();
$dht->boot_nodes( $new_list );

Expected parameters:

$new_list - optional

An array reference of bootstrap nodes.

CLASSES

Net::BitTorrent::DHT::Peer

Represents a peer discovered via DHT.

METHODS

to_string( )

Returns a string representation ('ip:port').

say $peer->to_string();

ip( )

Returns the IP address.

port( )

Returns the port number.

family( )

Returns the address family (4 or 6).

Supported BEPs

  • BEP 05: Mainline DHT Protocol

  • BEP 32: IPv6 Extensions

  • BEP 33: DHT Scrapes

  • BEP 42: DHT Security Extensions

  • BEP 43: Read-only DHT Nodes

  • BEP 44: Storing Arbitrary Data

  • BEP 51: Infohash Indexing

AUTHOR

Sanko Robinson <sanko@cpan.org>

COPYRIGHT

Copyright (C) 2008-2026 by Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.