CVE-2026-46739
Description
Net::Statsd for Perl versions prior to 0.13 are vulnerable to metric injection due to insufficient validation of metric names and values.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Net::Statsd for Perl versions prior to 0.13 are vulnerable to metric injection due to insufficient validation of metric names and values.
Vulnerability
Versions of Net::Statsd for Perl prior to 0.13 are vulnerable to metric injection. The library does not adequately validate metric names for newlines, colons, or pipes, nor does it ensure that metric values are strictly numeric. This allows for the injection of additional, attacker-controlled metrics into the data stream [1].
Exploitation
An attacker can exploit this vulnerability by sending specially crafted metric names or values that contain newline, colon, or pipe characters. These characters are not properly escaped or validated, allowing them to inject arbitrary metric data. This can occur through methods like update_stats or gauge if the input is not sanitized before being processed [1].
Impact
Successful exploitation allows an attacker to inject arbitrary metrics into the StatsD server. This can lead to data poisoning, where the attacker can manipulate or add metrics to the system, potentially impacting monitoring, alerting, and analytics based on the collected statistics.
Mitigation
Net::Statsd version 0.13 addresses this vulnerability by introducing validation for metric names and values, preventing the injection of malicious characters. Users are advised to upgrade to version 0.13 or later. No workarounds are specified in the available references.
AI Insight generated on Jun 4, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2(expand)+ 1 more
- (no CPE)
- (no CPE)range: <0.13
Patches
1f2cda961459cMerge pull request #10 from cosimo/sec/CVE-2026-46739
4 files changed · +176 −3
Changes+12 −0 modified@@ -1,5 +1,17 @@ Change history for Net::Statsd +0.13 - Tue Jun 1 22:16:11 CET 2026 + + [Security] CVE-2026-46739 + + Metric names and values are now validated to ensure they do not contain + characters below ASCII 32 (including newlines), colon (":") or pipe ("|") + characters that might allow metric injection. Offending calls now croak. + + More than 10 years since the latest release :-) + + Thanks to rrwo@cpansec.org for the report and suggestions. + 0.12 - Fri Jan 15 09:39:00 CET 2016 Fixed RT#111204. There was a t/send-one.t script included in the
dist.ini+4 −3 modified@@ -1,10 +1,10 @@ name = Net-Statsd -author = Cosimo Streppone <cosimo@cpan.org> +author = Cosimo Streppone <cosimo@streppone.it> license = Perl_5 copyright_holder = Cosimo Streppone -copyright_year = 2016 +copyright_year = 2026 -version = 0.12 +version = 0.13 [@Basic] [PkgVersion] @@ -28,6 +28,7 @@ repository.type = git [Prereqs] IO::Socket = 0 Time::HiRes = 0 +Carp = 0 [Prereqs / TestRequires] Test::More = 0
lib/Net/Statsd.pm+59 −0 modified@@ -87,6 +87,20 @@ sample_rate value. (e.g. a sample rate of 0.5 would indicate that approximately only half of the metrics given to this module would actually be sent to statsd). +=head1 SECURITY + +To prevent B<metric injection> (CVE-2026-46739), metric names and values are +validated before being sent. The statsd wire format is C<name:value|type>, and +several metrics can be packed into one UDP datagram separated by newlines, so a +name or value containing a newline (or any control character below ASCII 32), a +colon (C<:>) or a pipe (C<|>) could be used to forge extra metric lines. + +Any such name or value causes the offending call (C<timing>, C<increment>, +C<decrement>, C<update_stats>, C<gauge> or C<send>) to throw via +C<Carp::croak>. Note that values passed to C<send()> already contain a +formatted C<|type> suffix, so C<send()> only re-validates the metric I<names>; +raw values are validated by the higher level functions before formatting. + =head1 FUNCTIONS =cut @@ -107,6 +121,8 @@ sub timing { $sample_rate = 1; } + _validate_metric_value($time); + my $stats = { $name => sprintf "%d|ms", $time }; @@ -218,6 +234,8 @@ sub update_stats { Carp::croak("Usage: update_stats(\$str, ...) or update_stats(\\\@list, ...)"); } + _validate_metric_value($delta); + my %data = map { $_ => sprintf "%s|c", $delta } @{ $stats }; return Net::Statsd::send(\%data, $sample_rate) @@ -268,6 +286,7 @@ sub gauge { while (my($name, $value) = splice(@_, 0, 2)) { $value = 0 unless defined $value; + _validate_metric_value($value); # Didn't use '%d' because values might be floats push @{ $stats->{$name} }, sprintf("%s|g", $value); } @@ -286,6 +305,13 @@ Squirt the metrics over UDP. sub send { my ($data, $sample_rate) = @_; + # Validate metric names here: every metric, regardless of which + # public function was used to record it, funnels through send(), + # so this single check also guards direct Net::Statsd::send() calls. + if ($data && ref $data eq 'HASH') { + _validate_metric_name($_) for keys %{ $data }; + } + my $sampled_data = _sample_data($data, $sample_rate); # No sampled_data can happen when: @@ -410,4 +436,37 @@ sub _sample_data { return $sampled_data; } +=head2 C<_validate_metric_name($name)> + +=head2 C<_validate_metric_value($value)> + +B<These methods are used internally, they're not part of the public interface.> + +Guard against B<metric injection> (CVE-2026-46739). The statsd wire format is +C<name:value|type>, and multiple metrics are packed in a single UDP datagram +separated by newlines. A metric name or value that contains a newline (or any +other control character below ASCII 32), a colon (C<:>) or a pipe (C<|>) could +therefore forge additional, attacker-controlled metric lines. + +Both functions C<Carp::croak> when the input contains any of those characters. +Names are validated centrally in C<send()>; raw values are validated at each +recording entry point (C<timing>, C<update_stats>, C<gauge>) before the +C<|type> suffix is appended. + +=cut + +sub _validate_metric_name { + my ($name) = @_; + Carp::croak("Net::Statsd: malformed metric name") + if !defined $name || $name =~ /[\x00-\x1f:|]/; + return $name; +} + +sub _validate_metric_value { + my ($value) = @_; + Carp::croak("Net::Statsd: malformed metric value") + if defined $value && $value =~ /[\x00-\x1f:|]/; + return $value; +} + 1;
t/sec-CVE-2026-46739.t+101 −0 added@@ -0,0 +1,101 @@ +#!/usr/bin/perl + +=head1 NAME + +t/sec-CVE-2026-46739.t - Metric injection guard tests + +=head1 DESCRIPTION + +CVE-2026-46739: metric names and values must not contain control characters +(below ASCII 32), colons (":") or pipes ("|"), as those could be used to forge +extra metric lines in the UDP packet sent to statsd. These tests verify that +such input is rejected with a croak, while legitimate input is accepted. + +=cut + +use strict; +use warnings; +use Test::More tests => 16; +use Net::Statsd; + +# Make sure nothing actually leaves the box: point at a discard port. +$Net::Statsd::HOST = '127.0.0.1'; +$Net::Statsd::PORT = 1; + +# Run $code, return the exception (or '' if it lived). +sub dies { + my ($code) = @_; + my $err = ''; + eval { $code->(); 1 } or $err = $@; + return $err; +} + +# --- Bad metric NAMES (validated centrally in send()) ------------------------ + +for my $bad ( + [ "test\ncounter" => 'newline' ], + [ "test:counter" => 'colon' ], + [ "test|counter" => 'pipe' ], + [ "test\0counter" => 'null byte' ], + [ "test\tcounter" => 'tab' ], +) { + my ($name, $desc) = @{ $bad }; + like( + dies(sub { Net::Statsd::increment($name) }), + qr/malformed metric name/, + "increment() rejects metric name with $desc", + ); +} + +# A direct send() call (the low-level public API) must be guarded too. +like( + dies(sub { Net::Statsd::send({ "evil\nname" => "1|c" }) }), + qr/malformed metric name/, + "direct send() rejects a malformed metric name", +); + +# --- Bad metric VALUES (validated at the recording entry points) ------------- + +like( + dies(sub { Net::Statsd::gauge('core.temp', "5|c\nevil:9") }), + qr/malformed metric value/, + "gauge() rejects a value containing a newline", +); + +like( + dies(sub { Net::Statsd::gauge('core.temp', "5|g") }), + qr/malformed metric value/, + "gauge() rejects a value containing a pipe", +); + +like( + dies(sub { Net::Statsd::gauge('core.temp', "5:6") }), + qr/malformed metric value/, + "gauge() rejects a value containing a colon", +); + +like( + dies(sub { Net::Statsd::update_stats('some.int', "1|c\nevil:9") }), + qr/malformed metric value/, + "update_stats() rejects a malformed delta", +); + +# --- Legitimate input must still be accepted (no croak) ---------------------- + +is(dies(sub { Net::Statsd::increment('good.name') }), '', + 'increment() accepts a normal metric name'); + +is(dies(sub { Net::Statsd::gauge('core.temp', 55) }), '', + 'gauge() accepts a normal numeric value'); + +is(dies(sub { Net::Statsd::gauge('core.temp', -18.5) }), '', + 'gauge() accepts a negative float value'); + +is(dies(sub { Net::Statsd::timing('some.timer', 345) }), '', + 'timing() accepts a normal timing value'); + +is(dies(sub { Net::Statsd::update_stats('some.int', 10) }), '', + 'update_stats() accepts a normal delta'); + +is(dies(sub { Net::Statsd::increment(['a.counter', 'b.counter']) }), '', + 'increment() accepts an arrayref of normal names');
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
3News mentions
0No linked articles in our index yet.