Hardening sys-net

this guide works no matter whether or not you chose disposable sys-net or not. no nonsense guide, lets get in

SECTION 1

  1. open a dom0 terminal and install a fedora-39-minimal template (dont close the terminal yet we need it for later)

         sudo qubes-dom0-update --enablerepo=qubes-templates-community qubes-templates-fedora-39-minimal
    
  2. clone the template and name it ‘net-dvm’ (optionally clone it twice, and name the second one fw-dvm. youll use it later IF you do decide to do a bit more advanced hardening)

  3. in your dom0 terminal, open a root XTerm in your newly created net-dvm

         qvm-run -u root net-dvm xterm
    
  4. install packages

         dnf install qubes-core-agent-networking qubes-core-agent-network-manager NetworkManager-wifi network-manager-applet wireless-tools notification-daemon gnome-keyring polkit ufw clamav @hardware-support
    
  5. from the docs on minimal templates, ‘If your network devices need extra packages for the template to work as a network VM, use the lspci command to identify the devices, then run dnf search firmware (replace firmware with the appropriate device identifier) to find the needed packages and then install them.’

  6. enable ufw (or if you want to use nftables instead, dnf remove ufw and install nftables and set our rules.

         systemctl enable ufw
         ufw enable
    
  7. remove default rules in ufw and allow incoming from sys-firewall (ip will be different in your case, just find ip address of sys-firewall)

         ufw delete 1 (x4)
         ufw allow in from 10.x.x.x
    
  8. (optional) run comprehensive system security audits with lynis

         dnf install lynis
         lynis audit system
    
  9. update packages

         dnf update && dnf upgrade
    
  10. finishing up (run commands one after another)

         freshclam
         clamscan -r /
    
  11. exit the XTerm and shutdown net-dvm, make a clone of default-dvm named network-dvm and change the template of network-dvm to net-dvm.

  12. shutdown sys-net and change the template from default-dvm to network-dvm. majority should be done right here, unless you want to go and help out sys-firewall too. IF you didnt choose to make it disposable, open a dom0 terminal and edit /var/lib/qubes/qubes.xml

        sudo nano /var/lib/qubes/qubes.xml
    

scroll down until you find the actual properties of sys-net qube (might take a while, might not, depends on how good of an observer you are) and edit “class=“AppVM”” to say DispVM. copy the entire autostart property line, the entire line of the property for autostart being true, and make a new line right below that property, paste in your copied property, and change the name from autostart to dispid, and change True to a unique value (just hit 4 random keys on your keyboard). CTRL + O to write out, and CTRL + X to exit. restart QubesOS, and now you can go change the template of sys-net to network-dvm, and if it doesnt work and it says no default dispid for sys-net and that sys-net is still an AppVM, set a different dispid than before.

SECTION 2

this section is for sys-firewall, and is the section where your second template is needed, if you created it. if you didnt, and want to follow section 2, go create a new one.

  1. get root terminal access since passwordless root is disabled by default

         qvm-run -u root fw-dvm xterm
    
  2. install packages

         dnf install qubes-core-agent-networking iproute ufw clamav
    
  3. if you are using sys-firewall for dom0 updates instead of sys-whonix, install dom0 update support

         dnf install qubes-core-agent-dom0-updates
    
  4. remove default rules. ufw doesnt interfere with the firewall shit that qubes has on by default, atleast with our ruleset, so dont worry.

         ufw delete 1 (x4)
         ufw default deny outgoing
         ufw default allow incoming
         ufw allow out to <ip of sys-net>
         ufw deny in from <ip of sys-net>
         systemctl enable ufw
         ufw enable
    
  5. update packages and start clamav

         dnf update && dnf upgrade
         freshclam
         clamscan -r /
         clamscan
    
  6. (optional) install lynis and audit system

         dnf install lynis
         lynis audit system
    
  7. you know the drill, clone default-dvm again, name it firewall-dvm, change firewall-dvm template to fw-dvm, shutdown sys-firewall, change template of sys-firewall to firewall-dvm, and youre good to go.

EOF

Could you describe what this is providing compared to the default sys-net?

yes, since sys-net is your weakest link, as in, if it gets hacked, youre fucked. and its quite easy to hack aswell, this makes it more difficult to hack into sys-net, and even if an attacker did get in, this limits what they can do to cause significant damage.

yes, but what does your guide make that isn’t already done in sys-net?

1 Like

by default, sys-net is based off Fedora-38-Xfce, and not Fedora-38-Minimal, and obviously this is not as good, since minimal would have less attack vectors since its, well, minimal. by default, passwordless-root is also enabled in sys-net, and there isnt a firewall inside of sys-net to prevent attackers from breaking in, with the large attack surface of the default. the minimal templates have a much lesser attack surface, they do not have passwordless root, and if following my guide, will have a firewall, which wont do much since there arent really attack vectors to abuse with the minimal VMs, but its always good to go above and beyond in terms of securing the highest-risk qube. as for sys-firewall, what ive described is only really needed if youre being hunted by the CIA or KGB or something.

and i think that we can all agree that big attack surface with passwordless root and no firewall, is not good for security.

by default (on 4.2), the firewall is blocking all incoming connections in sys-net (and all qubes)

as long as the VM doesn’t run network services, you could install whatever you want this wouldn’t change the attack surface for a network attacker

to your first statement, if youre referring to sys-firewall, it does not apply to sys-net. if you arent, im unaware of what youre referring to and would appreciate some elaboration. as for your second statement, im gonna have to also ask you to elaborate, since sys-net is indeed the one running network services, which is kind of why i made the guide.

the default policy is to drop input packets

user@sys-net ~> sudo -i
bash-5.2# nft list ruleset
table ip qubes {
	set downstream {
		type ipv4_addr
		elements = { 10.138.16.222 }
	}

	set allowed {
		type ifname . ipv4_addr
		elements = { "vif5.0" . 10.138.16.222 }
	}

	chain prerouting {
		type filter hook prerouting priority raw; policy accept;
		iifgroup 2 goto antispoof
		ip saddr @downstream counter packets 0 bytes 0 drop
	}

	chain antispoof {
		iifname . ip saddr @allowed accept
		counter packets 0 bytes 0 drop
	}

	chain postrouting {
		type nat hook postrouting priority srcnat; policy accept;
		oifgroup 2 accept
		oif "lo" accept
		masquerade
	}

	chain input {
		type filter hook input priority filter; policy drop;
		jump custom-input
		ct state invalid counter packets 2 bytes 80 drop
		iifgroup 2 udp dport 68 counter packets 0 bytes 0 drop
		ct state established,related accept
		iifgroup 2 meta l4proto icmp accept
		iif "lo" accept
		iifgroup 2 counter packets 0 bytes 0 reject with icmp host-prohibited
		counter packets 8816 bytes 1184246
	}

	chain forward {
		type filter hook forward priority filter; policy accept;
		jump custom-forward
		ct state invalid counter packets 40 bytes 1600 drop
		ct state established,related accept
		oifgroup 2 counter packets 0 bytes 0 drop
	}

	chain custom-input {
	}

	chain custom-forward {
	}

	chain dnat-dns {
		type nat hook prerouting priority dstnat; policy accept;
		ip daddr 10.139.1.1 udp dport 53 dnat to 10.42.42.42
		ip daddr 10.139.1.1 tcp dport 53 dnat to 10.42.42.42
		ip daddr 10.139.1.2 udp dport 53 dnat to 9.9.9.9
		ip daddr 10.139.1.2 tcp dport 53 dnat to 9.9.9.9
	}
}
table ip6 qubes {
	set downstream {
		type ipv6_addr
	}

	set allowed {
		type ifname . ipv6_addr
	}

	chain antispoof {
		iifname . ip6 saddr @allowed accept
		counter packets 34 bytes 2000 drop
	}

	chain prerouting {
		type filter hook prerouting priority raw; policy accept;
		iifgroup 2 goto antispoof
		ip6 saddr @downstream counter packets 0 bytes 0 drop
	}

	chain postrouting {
		type nat hook postrouting priority srcnat; policy accept;
		oifgroup 2 accept
		oif "lo" accept
		masquerade
	}

	chain _icmpv6 {
		meta l4proto != ipv6-icmp counter packets 0 bytes 0 reject with icmpv6 admin-prohibited
		icmpv6 type { nd-router-advert, nd-redirect } counter packets 0 bytes 0 drop
		accept
	}

	chain input {
		type filter hook input priority filter; policy drop;
		jump custom-input
		ct state invalid counter packets 0 bytes 0 drop
		ct state established,related accept
		iifgroup 2 goto _icmpv6
		iif "lo" accept
		ip6 saddr fe80::/64 ip6 daddr fe80::/64 udp dport 546 accept
		meta l4proto ipv6-icmp accept
		counter packets 0 bytes 0
	}

	chain forward {
		type filter hook forward priority filter; policy accept;
		jump custom-forward
		ct state invalid counter packets 0 bytes 0 drop
		ct state established,related accept
		oifgroup 2 counter packets 0 bytes 0 drop
	}

	chain custom-input {
	}

	chain custom-forward {
	}
}
table ip qubes-firewall {
	chain forward {
		type filter hook forward priority filter; policy drop;
		ct state established,related accept
		iifname != "vif*" accept
		ip saddr 10.138.16.222 jump qbs-10-138-16-222
	}

	chain prerouting {
		type filter hook prerouting priority raw; policy accept;
		iifname != "vif*" ip saddr 10.138.16.222 drop
	}

	chain postrouting {
		type filter hook postrouting priority raw; policy accept;
		oifname != "vif*" ip daddr 10.138.16.222 drop
	}

	chain qbs-10-138-16-222 {
		accept
		reject with icmp admin-prohibited
	}
}
table ip6 qubes-firewall {
	chain forward {
		type filter hook forward priority filter; policy drop;
		ct state established,related accept
		iifname != "vif*" accept
	}

	chain prerouting {
		type filter hook prerouting priority raw; policy accept;
	}

	chain postrouting {
		type filter hook postrouting priority raw; policy accept;
	}
}
table inet qubes-nat-accel {
	flowtable qubes-accel {
		hook ingress priority filter
		devices = { ens7, lo, vif5.0, wls6 }
	}

	chain qubes-accel {
		type filter hook forward priority filter + 5; policy accept;
		meta l4proto { tcp, udp } iifgroup 2 oifgroup 1 flow add @qubes-accel
		counter packets 46349130 bytes 72436166186
	}
}

Which network services do the stock sys-net run that your sys-net isn’t running?

Are these the default rules or did you change them somehow?

I see, i was unaware of that and will update the guide to remove the firewall part. as for your second statement, according to Qubes docs minimal templates, none. the point im making is that since sys-net is the only qube actually communicating with the ethernet controller (or router if youre on wifi) then its the only qube that an attacker could directly attack, minimizing the attack surface prevents the ways in which an attacker could go about penetrating into sys-net, and we’ve seen examples a few years ago with a user who was being targeted by israeli hackers, having his sys-net being infiltrated as we see here Forensics on sys-net (take his statements with a grain of salt though because some of what he says doesnt make sense)

it’s the default rules

1 Like

actually might not remove it, since ufw offers easy to use customizability that would be more beneficial to most users than nftables. especially if you need to set specific rules and configs and arent willing to spend a few hours tweaking. also considering the fact that youd have to config it to be like the previous defaults, which im assuming most people here dont know how to do.

I’m not sure ufw is more customizable than nftables. But yeah, that could be useful for people allergic to nftables which is way more complicated than ufw.

for sure nftables is more customizable, but i dont like scrolling through a ton of code to change 1 setting, and i dont think the increase in customizability is enough for an advanced user ti want to choose it, over ufw. as when youre trying to setup a good firewall config, you dont want to spend 6 days, but instead just 6 minutes.