For an introduction to my hacking game, checkout: Creating a Hacking Game - Part 1: Introduction
Creating this system was an interesting challenge - the main threat vector are root exploits. I'm not a sysadmin and my Linux knowledge is not very in-depth. But I'm still pretty confident in my design. So now I want to go over every design decision.
> The whole setup is currently running on a very cheap vServer running a 64bit Debian
I got a cheap vServer because I didn't want to pay a lot of money for something nobody will use. And I chose Debian because that's the OS I'm most familiar with on a Server. But the distro shouldn't really matter as you will see soon.
> Chroot Jail for the game:
I wanted to separate the game from the real system and
chroot seemed like a very good choice to handcraft the system. This can be easily done with
Match user level* PasswordAuthentication yes chrootdirectory /var/sshjail/
This means that all players will
/var/sshjail and they should only be able to access the files inside that folder. So the whole system may look like this:
/proc /home /home/user /var /var/sshjail /var/sshjail/home /var/sshjail/home/level0
But when the
level0 player is logged in
ls / will only list:
This allows me to handcraft the filesystem used by the players and limit the attack surface.
> No access to potential dangerous stuff like
Being able to setup the filesystem how I want, I can choose to not mount
/dev. There is no reason why a user should have access to
/proc/kallsyms and know where kernel symbols are. I pulled up a random root exploit on the ExploitDatabase and it relies on access to
It's a lot of work to copy all the files necessary for a Linux system into the chroot jail. I need to copy every binary, including the shell itself and
cd, ... . But not only that, all the libraries like libc have to be copied as well. But this allows me to carefully control to what binaries users have access too and exclude any
setuid root binaries.
setuid binaries are another way how a root exploit could be achieved - so better remove those.
> Use Linux file attributes prevent players modifying or deleting files, even though they are the owner of them:
The game relies on
setuid binaries for levels. So for example you exploit the
/matrix/level1/level1 binary that belongs to user
level2, so when you exploit it, that you have the rights of
level2. But when you login as
level2 you should not be able to delete or modify that binary - that would destroy the game. You should also not be able to create files anywhere, even in your home folder. That's why I use Linux file attributes to control this.
Here as an example
level1. The owner of the
level1 binary is user
level2 but the group is still
level1, together with the
s the user
level1 can execute the binary but it will run as
level2. Additionally the
immutable file attribute
i is set so that even the owner
level2 cannot modify it.
ls -l /matrix/level1 total 12 -r-sr-x--- 1 level2 level1 level1 $ lsattr ./matrix/level1 ----i--------e-- ./matrix/level1/level1
Same goes for the files in the home folder of the user. They all belong to
level1 but they are
immutable. You may notice that the
iwashere file has the write permission for the
level1 owner and that the file attribute is
a. This allows the user to add a line to the file with for example
echo "samuirai was here" >> /home/level1/iwashere but the user cannot delete or overwrite it.
$ ls -l /home/level1/* -rw-r----- 1 level1 level1 /home/level1/iwashere -r--r----- 1 level1 level1 /home/level1/recap -r--r----- 1 level1 level1 /home/level1/story -r--r----- 1 level1 level1 /home/level1/welcome $ lsattr /home/level1/* -----a-------e-- /home/level1/iwashere ----i--------e-- /home/level1/recap ----i--------e-- /home/level1/story ----i--------e-- /home/level1/welcome
> iptable firewall rules to stop users from abusing the server:
fail2ban against ssh password bruteforcing. And I block all outgoing connections from the players, so that the server cannot be abused for DoS attacks.
Chain OUTPUT (policy ACCEPT) target prot opt source destination REJECT all -- anywhere anywhere owner UID match level0 reject-with icmp-port-unreachable REJECT all -- anywhere anywhere owner UID match level1 reject-with icmp-port-unreachable
> Set user limits:
I mainly just copied the values from io.smashthestack.org, because I have no idea what good values are.
For example the limit of 40
-u processes prevents fork bombs.
[email protected]:~$ ulimit -a -t: cpu time (seconds) unlimited -f: file size (blocks) unlimited -d: data seg size (kbytes) unlimited -s: stack size (kbytes) 8192 -c: core file size (blocks) 0 -m: resident set size (kbytes) 100000 -u: processes 40 -n: file descriptors 1024 -l: locked-in-memory size (kbytes) 64 -v: address space (kbytes) 2000000 -x: file locks unlimited -i: pending signals 7976 -q: bytes in POSIX msg queues 819200
> Remaining threats:
One issue will always be root exploits like the recent CVE-2015-3290. But I hope the restricted filesystem together with the virtualized vServer will protect me from the majority.
The other big issue are race conditions in setting up new levels or making changes to current levels. When I make changes to levels I cannot make these atomic. I have to remove the
immutable attribute, modify a file and readd the attribute. There is a window of opportunity where an attacker could make a mess. But this can be avoided by blocking ssh access, killing all processes from players, do the changes and allow them back in.