diff --git a/README.md b/README.md index c3ee485..e8e6173 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,26 @@ # Qubes-VM-hardening Enhancing Qubes VM security and privacy -## rc.local: Protect sh, bash and GUI init files - ### Pre-requisites: Enabling authentication for sudo (see link below for Qubes doc). + +--- + + +## vm-sudo-protect.service + * Protect /home script files + * Remove /rw scripts at VM start + +## Testing systemd version... +Install the two files `vm-sudo-protect.sh` and `vm-sudo-protect.service` into template then use `systemctl` to enable the service. + +Activate by specifying as a Qubes service for each VM; There are two levels... + 1. `vm-sudo-protect` - similar to the rc.local script. Protects scripts within /home and may be used with wide array of VMs including standalone, netVMs and Whonix. + 2. `vm-sudo-protect-root` - new feature which **erases** /rw/config, /rw/usrlocal and /rw/bind-dirs. Use with caution! This feature can also replace files on a global or per-VM basis... see script for details. Not recommended for standalone or VMs that rely on /rw root dirs such as netVMs or Whonix. + +--- + +## rc.local (old version) ### Description: Placed in /etc/rc.local (or equivalent) of a template VM, this makes the shell init files immutable so PATH and alias cannot be used to hijack commands like su and sudo, nor can impostor apps autostart whenever a VM starts. I combed the dash and bash docs -- as well as Gnome, KDE, Xfce and X11 docs -- to address all the user-writable startup files that apply. Feel free to comment or create an issue if you see an omission or other problem. diff --git a/lib/systemd/system/vm-sudo-protect.service b/lib/systemd/system/vm-sudo-protect.service new file mode 100644 index 0000000..f632011 --- /dev/null +++ b/lib/systemd/system/vm-sudo-protect.service @@ -0,0 +1,19 @@ +[Unit] +Description=Script protections to enhance VM security +After=qubes-sysinit.service +Before=qubes-mount-dirs.service +ConditionPathExists=|/var/run/qubes-service/vm-sudo-protect +ConditionPathExists=|/var/run/qubes-service/vm-sudo-protect-root +ConditionPathExists=|/var/run/qubes-service/vm-sudo-protect-cli +DefaultDependencies=false +OnFailure=shutdown.target +OnFailureJobMode=replace-irreversibly + +[Service] +Type=oneshot +RemainAfterExit=no +#Environment="privdirs=/rw/config /rw/usrlocal /rw/bind-dirs" +ExecStart=/usr/lib/qubes/init/vm-sudo-protect.sh + +[Install] +WantedBy=sysinit.target diff --git a/rc.local b/rc.local deleted file mode 100644 index beac2ae..0000000 --- a/rc.local +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -e -# Debian: /etc/rc.local - - ######################################################### -## Protect sh, bash, X and desktop init scripts ## -## to prevent privilege escalation attacks ## -## and malware persistence - for Qubes Linux templates ## -chfiles=".bashrc .bash_profile .bash_login .bash_logout .profile \ -.xprofile .xinitrc .xserverrc .xsession" -chdirs="bin .config/autostart .config/plasma-workspace/env \ -.config/plasma-workspace/shutdown .config/autostart-scripts" - -cd /home/user -mkdir -p $chdirs ||true -touch $chfiles || true -chattr -R -f +i $chfiles $chdirs || true -#touch /home/user/FIXED || true - -# end of script -exit 0 diff --git a/usr/lib/qubes/init/vm-sudo-protect.sh b/usr/lib/qubes/init/vm-sudo-protect.sh new file mode 100644 index 0000000..d895786 --- /dev/null +++ b/usr/lib/qubes/init/vm-sudo-protect.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +## Protect startup of Qubes VMs from /rw scripts ## +## https://github.com/tasket/Qubes-VM-hardening ## + + +# Source Qubes library. +. /usr/lib/qubes/init/functions + +# Define sh, bash, X and desktop init scripts in /home/user +# to be protected +chfiles=".bashrc .bash_profile .bash_login .bash_logout .profile \ +.xprofile .xinitrc .xserverrc .xsession" +chdirs="bin .config/autostart .config/plasma-workspace/env \ +.config/plasma-workspace/shutdown .config/autostart-scripts" +vmname=`qubesdb-read /name` +rw=/mnt/rwtmp + +# Function: Make user scripts immutable. +make_immutable() { + #initialize_home $rw/home ifneeded + cd $rw/home/user + mkdir -p $chdirs + touch $chfiles + chattr -R -f +i $chfiles $chdirs + cd /root + touch $rw/home/user/FIXED #debug +} + +# Mount private volume in temp location +mkdir -p $rw +if [ -e /dev/xvdb ] && mount /dev/xvdb $rw ; then + echo Good rw mount. +else + echo Mount failed! + xterm -hold -display :0 -title "VM PROTECTION: MOUNT FAILED!" \ +-e "bash -i" + exit 1 +fi +if qsvc vm-sudo-protect-cli; then + xterm -hold -display :0 -title "VM PROTECTION: SERVICE PROMPT" \ +-e "echo Private volume is mounted at $rw; bash -i" +fi + + +# Protection measures for /rw dirs: +# Activated by presence of vm-sudo-protect-root Qubes service. +# * Hashes in vms/vms.all.SHA and vms/$vmname.SHA files will be checked. +# * Remove /rw root startup files (config, usrlocal, bind-dirs). +# * Contents of vms/vms.all and vms/$vmname folders will be copied. +defdir="/etc/default/vms" +privdirs=${privdirs:-"$rw/config $rw/usrlocal $rw/bind-dirs"} + +if qsvc vm-sudo-protect-root && is_rwonly_persistent; then + + # Check hashes + checkcode=0 + echo "File hash checks:" >/tmp/vm-protect-sum-error + for vmset in vms.all $vmname; do + if [ -f $defdir/$vmset.SHA ]; then + sha256sum --strict -c $defdir/$vmset.SHA >>/tmp/vm-protect-sum-error 2>&1 + checkcode=$((checkcode+$?)) + fi + done + cat /tmp/vm-protect-sum-error # For logging + + # Stop system startup on checksum mismatch: + if [ $checkcode != 0 ]; then + xterm -hold -display :0 -title "VM PROTECTION: CHECKSUM MISMATCH!" \ +-e "cat /tmp/vm-protect-sum-error; echo Private volume is mounted at $rw; bash -i" + exit 1 + fi + + # Files mutable for del/copy operations + cd $rw/home/user + chattr -R -f -i $chfiles $chdirs $privdirs + cd /root + + # Deactivate private.img config dirs + for dir in $privdirs; do + bakdir=`dirname $dir`/BAK-`basename $dir` + bak2dir=`dirname $dir`/BAK2-`basename $dir` + if [ -d $bakdir ] && [ ! -d $bak2dir ]; then + mv $bakdir $bak2dir + fi + rm -rf $bakdir + mv $dir $bakdir + done + mkdir -p $privdirs + + for vmset in vms.all $vmname; do + + # Process whitelists... + cat $defdir/$vmset.whitelist \ + | while read wlfile; do + # Must begin with '/rw/' + if echo $wlfile |grep -q "^\/rw\/"; then #Was [ $wlfile =~ ^\/rw\/ ]; + srcfile="`echo $wlfile |sed -r \"s|^/rw/(.+)$|$rw/BAK-\1|\"`" + dstfile="`echo $wlfile |sed -r \"s|^/rw/(.+)$|$rw/\1|\"`" + dstdir="`dirname \"$dstfile\"`" + if [ ! -e "$srcfile" ]; then + echo "Whitelist entry not present in filesystem." + continue + # For very large dirs: mv whole dir when entry ends with '/' + elif echo $wlfile |grep -q "\/$"; then + echo "Whitelist mv $srcfile" + mkdir -p "$dstdir" + mv "$srcfile" "$dstdir" + else + echo "Whitelist cp $srcfile" + cp -a --link "$srcfile" "$dstdir" + fi + elif [ -n "$wlfile" ]; then + echo "Whitelist path must begin with /rw/." + fi + done + + # Copy default files... + if [ -d $defdir/$vmset/rw ]; then + cp -af "$defdir/$vmset/rw/*" $rw + fi + + done + +fi + +make_immutable +umount $rw +exit 0