So I was learning about the options for Systemd services for restricting or in another word “hardening” services.
This made me question does Kicksecure deploy certain hardening for it’s services or certain Kicksecure specific services?
These are some interesting options I will note here:
PrivateNetwork=yes/no
Gives the service its own network namespace. It can’t see or use the host’s network interfaces unless you explicitly bind sockets. Seems useful to prevent leaks or prevent services from being ran without networking?
PrivatePIDs=yes/no
Gives a service an isolated view of the process table, which is a useful “defense in depth” measure, especially for daemons that don’t need to inspect or manage other system processes.
PrivateTmp=yes/no
Provides a private /tmp and /var/tmp for the service, isolating it from the host’s temporary directories. One service can’t read temporary files left by another service, which mitigates attacks that rely on guessing or stealing temp‑file names.
MemoryDenyWriteExecute=yes/no
Remounts parts of the filesystem read‑only (or makes the whole tree read‑only) for the service. (helps mitigate code‑injection attacks).
ProtectSystem=full/strict/yes
Remounts parts of the filesystem read‑only (or makes the whole tree read‑only) for the service.
I don’t think we harden specific “third-party” services yet, but we definitely use these kinds of things for applicable services in Kicksecure and Whonix. See sdwdate/usr/lib/systemd/system/sdwdate.service at master · Kicksecure/sdwdate · GitHub for instance, there’s a whole battery of hardening options enabled here, including a syscall filter.
@arraybolt3
On the USBGuard thread you mentioned CUPS, does systemd support a drop in file? it looks like documentation states it does?
Along with a unit file foo.service, a “drop-in” directory foo.service.d/ may exist. All files with the suffix “.conf” from this directory will be merged in the alphanumeric order and parsed after the main unit file itself has been parsed. This is useful to alter or add configuration settings for a unit, without having to modify unit files. Each drop-in file must contain appropriate section headers. For instantiated units, this logic will first look for the instance “.d/” subdirectory (e.g. “foo@bar.service.d/”) and read its “.conf” files, followed by the template “.d/” subdirectory (e.g. “foo@.service.d/”) and the “.conf” files there. Moreover, for unit names containing dashes (“-”), the set of directories generated by repeatedly truncating the unit name after all dashes is searched too. Specifically, for a unit name foo-bar-baz.service not only the regular drop-in directory foo-bar-baz.service.d/ is searched but also both foo-bar-.service.d/ and foo-.service.d/. This is useful for defining common drop-ins for a set of related units, whose names begin with a common prefix. This scheme is particularly useful for mount, automount and slice units, whose systematic naming structure is built around dashes as component separators. Note that equally named drop-in files further down the prefix hierarchy override those further up, i.e. foo-bar-.service.d/10-override.conf overrides foo-.service.d/10-override.conf.
Couldn’t we create a CUPS drop in /etc/systemd/system/cups.service.d/hardening.conf that would apply some hardened options that would essentially mitigate:
WAN / public internet: a remote attacker sends an UDP packet to port 631. No authentication whatsoever.
LAN: a local attacker can spoof zeroconf / mDNS / DNS-SD advertisements (we will talk more about this in the next writeup ) and achieve the same code path leading to RCE.
I don’t think the package manager would ever touch it either with upgrades to CUPS?
However they might be accepting to NoNewPrivileges=yes and others.
People do indeed use network printing or LAN printing (I don’t do either personally) but maybe a new thread regarding CUPS should be made like “should CUPS even come disabled by default” or the ideas mentioned here.
FWIW, CUPS doesn’t even appear to be installed on Kicksecure 17 by default, unless I accidentally uninstalled it in my development VM during other work:
[sysmaint ~]% apt list --installed | grep cups
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
libcups2/oldstable,oldstable-security,now 2.4.2-3+deb12u8 amd64 [installed,automatic]
[Service]
# Minimal capabilities – only needed to bind to privileged ports (631)
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# Give CUPS its own network namespace – no external network access
PrivateNetwork=yes
# Prevent the service from gaining new privileges (setuid, etc.)
NoNewPrivileges=yes
# Restrict network families – only IPv4/IPv6 TCP, no UDP
RestrictAddressFamilies=AF_INET AF_INET6
# Bind only to the loopback interface (change to a specific IP if you need LAN access)
# The ExecStart line in the original unit already runs /usr/sbin/cupsd
# If you want to force a bind address at the service level uncomment
# ExecStart=
# ExecStart=/usr/sbin/cupsd -o Listen=127.0.0.1:631
# Filesystem protection – keep CUPS from touching the rest of the system
ProtectSystem=full
PrivateTmp=yes
ReadOnlyPaths=/etc/cups
ReadWritePaths=/var/spool/cups /var/log/cups
# Drop any unnecessary privileges
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes