Creating a Hacking Game - Part 2: The System

Posted on Sun 09 August 2015 in posts

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 sshd:

Match user level*
    PasswordAuthentication yes
    chrootdirectory /var/sshjail/

This means that all players will chroot to /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:

/home
/home/level0

This allows me to handcraft the filesystem used by the players and limit the attack surface.

> No access to potential dangerous stuff like /proc and setuid binaries:

Being able to setup the filesystem how I want, I can choose to not mount /proc or /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 /proc.

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 ls, 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 setuid bit 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 append only 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:

I use 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.