[Sicherheitshinweis] CVE-2026-31431 „Copy Fail“ — Auswirkungen für QNAP NAS-Nutzer erklärt

QNAP untersucht derzeit CVE-2026-31431, auch bekannt als Copy Fail, und möchte einige Klarstellungen für Nutzer bereitstellen, die sich über die Auswirkungen auf QNAP NAS-Geräte Sorgen machen könnten.

Kurz gesagt: Die meisten QNAP NAS-Modelle sind von dieser Schwachstelle nicht betroffen.

Das Problem betrifft nur bestimmte ARM-basierte QNAP NAS-Modelle, die spezielle Linux-Kernel-Versionen verwenden. Nach unserer aktuellen Einschätzung gilt:

  • Alle x86-basierten QNAP NAS-Modelle sind nicht betroffen.
  • ARM-basierte NAS-Modelle mit QTS 4.x sind nicht betroffen.
  • Die Schwachstelle betrifft nur bestimmte ARM-basierte NAS-Modelle mit betroffenen Kernel-Versionen.

Bitte beachten Sie die offizielle QNAP Security Advisory für aktuelle Informationen:

https://www.qnap.com/go/security-advisory/qsa-26-16

Über diese Schwachstelle

Bei dieser Schwachstelle handelt es sich um ein Problem zur lokalen Privilegienerweiterung.

Das bedeutet, dass ein Angreifer zunächst in der Lage sein müsste, als normaler (nicht administrativer) Benutzer Code auf dem NAS auszuführen, bevor er versuchen kann, die Schwachstelle auszunutzen. Es handelt sich nicht um eine Schwachstelle, die direkt über das Internet ohne vorherigen lokalen Zugriff ausgenutzt werden kann.

Bei QNAP NAS-Geräten sind SSH- und Telnet-Zugriffe standardmäßig auf Benutzer der Administratorgruppe beschränkt. Dennoch sollten Nutzer ihre System- und Anwendungsexponierung überprüfen, insbesondere wenn sie Dienste oder Container betreiben, auf die andere Benutzer oder externe Netzwerke zugreifen können.

Empfohlene Maßnahmen

Zur allgemeinen Risikominderung empfehlen wir Folgendes:

  • Gewähren Sie nicht-administrativen Benutzern nur dann Shell-Zugriff, wenn dies unbedingt notwendig ist.
  • Verwenden Sie ausschließlich Container-Images aus vertrauenswürdigen Quellen.
  • Überprüfen Sie die Einstellungen in Container Station und vermeiden Sie unnötigen Benutzerzugriff auf Container.
  • Halten Sie Anwendungen, Container und Dienste stets aktuell.
  • Deaktivieren Sie nicht genutzte Dienste und Anwendungen.
  • Wenn der integrierte Webserver nicht aktiv verwendet wird, sollten Sie ihn unter Systemsteuerung > Webserver deaktivieren.
  • Belassen Sie das NAS hinter einer Firewall und vermeiden Sie einen direkten Zugang aus dem Internet.
  • Folgen Sie der offiziellen Sicherheitsmeldung und installieren Sie Sicherheitsupdates, sobald sie verfügbar sind.

QNAP arbeitet bereits an einem Sicherheitsupdate und wird die Sicherheitsmeldung aktualisieren, sobald weitere Informationen oder Patches verfügbar sind.

Wenn Sie eine spezielle Systemkonfiguration haben und Bedenken bestehen, wenden Sie sich bitte an den QNAP Support für weitere Unterstützung.

Zu Ihrer eigenen Sicherheit posten Sie bitte keine sensiblen Informationen öffentlich im Forum, darunter öffentliche IP-Adressen, Benutzernamen, Geräteseriennummern, vollständige Protokolle oder detaillierte Systemkonfigurationen.

— QNAP Community Team
Basierend auf Informationen vom QNAP Product Security Incident Response Team

Frage zu Containern: Viele Container laufen unter Linux-Versionen wie Alpine oder BusyBox oder ähnlichen. Da die Container ein „anderes“ Linux verwenden als das QNAP, besteht dadurch eine Schwachstelle in diesen Containern? Oder ist das nicht der Fall?

Container, die auf einem QNAP NAS laufen, teilen sich denselben Kernel mit anderen Prozessen. Wenn dein NAS also betroffen ist, solltest du besonders aufmerksam sein.

OK. Also, wenn mein NAS nicht betroffen ist, sind meine Container auch in Ordnung – ja?

Dank KI kann ich also schnell ein Testscript erstellen.

Ich habe zufällig ein h874 mit QTS 5.2.8 (2025/12/25) ausgewählt und folgendes Script ausgeführt, das von Claude generiert und von Codex noch einmal geprüft wurde, um zu überprüfen, ob der Entrypoint unter QTS existiert:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""CVE-2026-31431 surface check. Python 2.7 / 3.x kompatibel. Nur Bind, sicher."""
from __future__ import print_function
import os, socket, sys, errno

AF_ALG, SOCK_SEQPACKET = 38, 5

def in_container():
    if os.path.exists("/.dockerenv"):
        return True
    try:
        with open("/proc/1/cgroup") as f:
            d = f.read()
        return any(k in d for k in ("docker", "lxc", "kubepods", "containerd"))
    except (IOError, OSError):
        return False

def kernel_info():
    u = os.uname()
    # u: (sysname, nodename, release, version, machine)
    print("[*] Kernel:    {0} ({1})".format(u[2], u[4]))
    print("[*] Container: {0}".format(in_container()))

def kconfig_probe():
    paths = ["/proc/config.gz", "/boot/config-" + os.uname()[2]]
    keys = ("CONFIG_CRYPTO_USER_API_AEAD", "CONFIG_CRYPTO_USER_API",
            "CONFIG_CRYPTO_AUTHENC")
    for p in paths:
        if not os.path.exists(p):
            continue
        try:
            if p.endswith(".gz"):
                import gzip
                f = gzip.open(p, "rt")
            else:
                f = open(p, "r")
            data = f.read()
            f.close()
        except Exception as e:
            print("[!] kconfig read fail {0}: {1}".format(p, e))
            continue
        print("[*] kconfig source: {0}".format(p))
        for line in data.splitlines():
            for k in keys:
                if line.startswith(k + "=") or line.startswith("# " + k + " "):
                    print("    {0}".format(line))
        return
    print("[*] kconfig: not exposed (no /proc/config.gz, no /boot/config-*)")

def module_state():
    loaded = False
    try:
        with open("/proc/modules") as f:
            for line in f:
                if line.startswith("algif_aead "):
                    loaded = True
                    break
    except (IOError, OSError) as e:
        print("[!] /proc/modules: {0}".format(e))
    sysmod = os.path.isdir("/sys/module/algif_aead")
    print("[*] algif_aead loaded:  {0}".format(loaded))
    print("[*] /sys/module/algif_aead present: {0}".format(sysmod))
    bl = []
    for d in ("/etc/modprobe.d", "/run/modprobe.d", "/lib/modprobe.d"):
        if not os.path.isdir(d):
            continue
        try:
            entries = os.listdir(d)
        except OSError:
            continue
        for fn in entries:
            full = os.path.join(d, fn)
            try:
                with open(full) as f:
                    if "algif_aead" in f.read():
                        bl.append(full)
            except (IOError, OSError):
                pass
    print("[*] blacklist refs:     {0}".format(bl if bl else "none"))
    return loaded, sysmod

def afalg_bind_probe():
    try:
        s = socket.socket(AF_ALG, SOCK_SEQPACKET, 0)
    except socket.error as e:
        en = e.args[0] if e.args else 0
        print("[+] AF_ALG socket() blocked: errno={0} ({1}) {2}".format(
            en, errno.errorcode.get(en, "?"), os.strerror(en) if en else ""))
        return False
    try:
        s.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
        print("[!] authencesn bind SUCCEEDED -- vulnerable surface EXPOSED")
        s.close()
        return True
    except socket.error as e:
        en = e.args[0] if e.args else 0
        print("[+] authencesn bind failed: errno={0} ({1}) {2}".format(
            en, errno.errorcode.get(en, "?"), os.strerror(en) if en else ""))
        s.close()
        return False

def userns_state():
    for p in ("/proc/sys/kernel/unprivileged_userns_clone",
              "/proc/sys/user/max_user_namespaces",
              "/proc/sys/kernel/apparmor_restrict_unprivileged_userns"):
        if os.path.exists(p):
            try:
                with open(p) as f:
                    print("[*] {0} = {1}".format(p, f.read().strip()))
            except (IOError, OSError):
                pass

def main():
    kernel_info()
    kconfig_probe()
    loaded, sysmod = module_state()
    exposed = afalg_bind_probe()
    userns_state()
    print("")
    if exposed:
        print("VERDICT: VULNERABLE attack surface present.")
        if in_container():
            print("         In container -> Part 2 (host page-cache write) reachable.")
        sys.exit(2)
    elif not (loaded or sysmod):
        print("VERDICT: Module absent and bind blocked.")
        sys.exit(0)
    else:
        print("VERDICT: Module present but bind blocked -- review.")
        sys.exit(1)

if __name__ == "__main__":
    main()

Und das Ergebnis:

Vom Host:

[qadmin@874QTS ~]$ uname -a
Linux 874QTS 5.10.60-qnap #1 SMP Thu Dec 25 01:08:48 CST 2025 x86_64 GNU/Linux
[qadmin@874QTS ~]$ python cpftest.py 
[*] Kernel:    5.10.60-qnap (x86_64)
[*] Container: False
[*] kconfig: not exposed (no /proc/config.gz, no /boot/config-*)
[*] algif_aead loaded:  False
[*] /sys/module/algif_aead present: False
[*] blacklist refs:     none
[+] AF_ALG socket() blocked: errno=97 (EAFNOSUPPORT) Address family not supported by protocol
[*] /proc/sys/user/max_user_namespaces = 127243

VERDICT: Module absent and bind blocked.

Aus einem Python-Container auf demselben NAS:

root@1ebc107603de:/home# uname -a
Linux 1ebc107603de 5.10.60-qnap #1 SMP Thu Dec 25 01:08:48 CST 2025 x86_64 GNU/Linux
root@1ebc107603de:/home# python cpftest.py 
[*] Kernel:    5.10.60-qnap (x86_64)
[*] Container: True
[*] kconfig: not exposed (no /proc/config.gz, no /boot/config-*)
[*] algif_aead loaded:  False
[*] /sys/module/algif_aead present: False
[*] blacklist refs:     none
[+] AF_ALG socket() blocked: errno=97 (EAFNOSUPPORT) Address family not supported by protocol
[*] /proc/sys/user/max_user_namespaces = 127243

VERDICT: Module absent and bind blocked.

Sieht also auch mit einer älteren QTS-Version okay aus.

Hinweis: Ich bin kein Profi im Security Team. Teile hier nur meine Gedanken.

Witzig eigentlich. Ich habe schon oft Leute gesehen, die sich darüber beschweren, dass QNAP immer alte Versionen dieser oder jener Bibliothek nutzt. Und jetzt ist es ausgerechnet die neuere Version des Linux-Kernels, die diese Sicherheitslücke hat!