Sysmask security challenge

This page lets you run arbitrary code on this server, and you are welcome to crack it. But I bet you'll never succeed! Details

codes executed since 14 April 2005, /etc/unreadable.52971 still intact.

The file /etc/unreadable.52971

This is a file in the chroot environment that your submitted codes run. Your goal is to make its contents shown to you, by using your submitted code ( c,  perl or sh). History.

To make the game seemingly more attractive, this file is even world readable by its attributes. But it is protected by sysmask, so unless you break this protection, you cannot read it.

To start with, if you manage to get the size of the file or even make some utility confirm its presence, it is already a great success.

But before anything else, there are three files declared readable in /etc for the sh method, one of them being physically missing. Can you find out their names (without looking at the sysmask configuration)?

Please note that it is sysmask that protects the (un)readability of the file, not SELinux! SELinux does not offer protection against kernel vulnerabilities, so that with arbitrary c programming, it would be possible to exploit a privilege elevation and get rid of the protection. While with a strictly configured sysmask here, nothing less than a privilege elevation behind read() or write() will be of any help.

Rules of the game

This is not a contest for being first to break in. It is rather there to show the unbreakability of the system! You beat ME if you break it. Try some exercises first.

Your codes (c, sh or perl) will run in a chroot environment of the server, by a non-root user. Output and error messages are captured and shown back to you in the web page.

You won't have to break the chroot environment, nor even to gain root privileges. Within the chroot environment, there is a small file named /etc/unreadable.52971. If you manage to print its contents by your code, you win!

You have to use your code submissions. You are free to break the compiler or the interpreters if you can, or even to exploit a kernel vulnerability if you can reach it, but breaking things by other means than the submitted code, although not necessarily easier, is not valid.

As a reward to the successful breakers, they may have their identities published here, under the condition that a working proof (your code) is together with the content of the file.

The server is running under Linux-2.4.31, sysmask-patched. The chroot environment contains a mini-system derived from Knoppix-3.9 (Debian). I guarantee that none of the files in this mini-system is patched, and that all file attributes are set up in the standard ways. The only security measure that protects /etc/unreadable.52971 is the Linux kernel sysmask patch. It seems pretty clear to me that this protection is unbreakable. I may be wrong; but at least I can say that if this file is broken, then the great majority of Linux boxes in the world are in big trouble.

I can even give you technical details helping you crack it: c  sh  perl . And the whole chroot environment can be downloaded from here, together with the config file for the Linux-2.4.31 kernel used by the server.


Now if you are a sysadmin, think about what if all your network daemons are running under a similar environment (by installing sysmask; chroot is in fact useless now). Execution of arbitrary codes is the worst thing that can happen to "vulnerabilities" of a daemon!

Tired of viruses? Put your browsers and mail agents under a similar environment, and forget about viruses and anti-viruses!

Interpreting a sh script

ATTENTION. The server will run your arbitrary code, but will not let it run arbitrarily. This is the main point of the security protection. It is fundamentally wrong to think that a working system HAS TO let an attacking code run arbitrarily. So here is what your code is allowed to do.

Your submitted sh script is interpreted by /bin/ash.static after the following three-line header.

cd $TMPDIR || exit
echo >/dev/null || exit
cd / 2>/dev/null && exit
These lines are a test used by wims to check whether sysmask is present and whether it is correctly configured. In this case, the first call to cd acts as a trigger that tells sysmask to deny further access to the service chdir.

Of course, chdir is not the only service that is masked after the header. In particular, external programs available to the script are strictly limited to the following list.

awk basename bc cat cmp comm cut diff diff3 dir dirname echo egrep expand expr factor false fgrep gawk grep head join ln ls nl od paste pwd sed seq sort sum tac tail test tr true unexpand uniq wc

Not every one among them works. For example, ln is only there to help you verify that link() and symlink() are masked. (Try it.)

No file can be opened for writing except /dev/null. Readable files include everything in your current directory, /usr, /bin, as well as a few files in /etc (can you find out which?).

Once a program is executed by ash, its access rights are reduced to the strict minimum, in particular it has no right to fork, clone or execve. So breaking into one of the above programs is not very interesting for attacking purposes.

On the other hand, it should be more interesting to break into ash.static itself, because then you have some more available system calls, basically fork, execve, getuid, nanosleep. Well, except getuid family of calls you don't need to break ash.static to reach them.

In any case, there is no access to sockets, ipc, devices, /proc, and creation and removal of files, directories, symbolic and hard links. And of course, no obsolete, new or rarely used Linux system calls are available. Any process created by ash will be killed when the latter exits or when time is up (thanks to a sysmask feature).

Addition since April 15, 2005: the number of forks is limited to 100 per script. You can test this limitation using an infinite while loop.


As a starting test, you may try to show the environment variables with the command set. Don't forget to make a

cat /etc/unreadable*.

Or what is in every textbook:
rm -Rf / Yes, what if you try to do some nasty resource exhaustions by, say, sleeping 100000000 seconds, or creating a lot of huge files, or opening many dangling symbolic links?

Don't worry: everything is cleaned up when you see the answer page.

General limitations

For all the three methods, the input code size is limited to 16K. This is probably a temporary limitation, due to the fact that I don't want to do the extra work to extend it for the time being.

The code is supposed to be plain ASCII. There is not (yet) a strict checking for this, but char values below 32 (except new_line and tab) are folded to space, and most characters above 128 are folded to their unaccented counterparts under ISO-8859-1. This should be more than enough to protect against any attempt to embed binary codes in the submitted source or script, for example in an attack by buffer overflow against the compiler.

The programs can only write to two standard file handles: the standard output channel (stdout for c and 1 for sh), and the standard error channel (stderr for c and 2 for sh). Both have size limits of 64K. Moreoever, WIMS will generate an error message (intermediate result too long) if one of the files grows over 16K.

The sh script can also open /dev/null for writing. No other device driver is available.

The standard input is empty. If you have data, they should be embedded in the code.

Of course I haven't tested all the potential ways of attack. And I don't know whether one of the concerned programs is breakable: ash, perl, gcc, awk ... I just make the hypothesis that ANY of them is breakable, then try to make sure that even if it is broken, the server's security is still preserved. It is up to the attacker to find out an effective exploit. If so, he is welcome to break it in any way he likes, including and not limited to DoS attacks, as long as everything is done under the chroot environment.

For more details about what is masked at which stage, you need to study the protocol of sysmask, then read the corresponding token definitions that can be downloaded here.

Interpreting a perl script

ATTENTION. The server will run your arbitrary code, but will not let it run arbitrarily. This is the main point of the security protection. It is fundamentally wrong to think that a working system HAS TO let an attacking code run arbitrarily. So here is what your code is allowed to do.

Your submitted perl script is interpreted by perl-5.8.0 after the following two-line header.

chdir($ENV{TMPDIR}) || exit;
chdir("/") && exit;
These two lines are a test used by wims to check whether sysmask is present and whether it is correctly configured. In this case, the first call to chdir() acts as a trigger that tells sysmask to deny further access to the same service.

Of course, chdir() is not the only service that is masked after the header. In fact, only the following system calls are available when your submitted codes are interpreted.

exit, read, write, open, close, brk.

Moreover, open is limited to reading perl library files in /usr/lib/per5/ as well as files in TMPDIR. Therefore require will work, but no file can be opened for writing, except the already opened standard output and error channels.

General limitations

For all the three methods, the input code size is limited to 16K. This is probably a temporary limitation, due to the fact that I don't want to do the extra work to extend it for the time being.

The code is supposed to be plain ASCII. There is not (yet) a strict checking for this, but char values below 32 (except new_line and tab) are folded to space, and most characters above 128 are folded to their unaccented counterparts under ISO-8859-1. This should be more than enough to protect against any attempt to embed binary codes in the submitted source or script, for example in an attack by buffer overflow against the compiler.

The programs can only write to two standard file handles: the standard output channel (stdout for c and 1 for sh), and the standard error channel (stderr for c and 2 for sh). Both have size limits of 64K. Moreoever, WIMS will generate an error message (intermediate result too long) if one of the files grows over 16K.

The sh script can also open /dev/null for writing. No other device driver is available.

The standard input is empty. If you have data, they should be embedded in the code.

Of course I haven't tested all the potential ways of attack. And I don't know whether one of the concerned programs is breakable: ash, perl, gcc, awk ... I just make the hypothesis that ANY of them is breakable, then try to make sure that even if it is broken, the server's security is still preserved. It is up to the attacker to find out an effective exploit. If so, he is welcome to break it in any way he likes, including and not limited to DoS attacks, as long as everything is done under the chroot environment.

For more details about what is masked at which stage, you need to study the protocol of sysmask, then read the corresponding token definitions that can be downloaded here.

Running c source

ATTENTION. The server will run your arbitrary code, but will not let it run arbitrarily. This is the main point of the security protection. It is fundamentally wrong to think that a working system HAS TO let an attacking code run arbitrarily. So here is what your code is allowed to do.

The starting point of your c codes is a function

int test(void)

In fact, your submitted code is stored in a file test.c that is included by the following wrapper main.c.

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <time.h>

int test(void);

int main(int argc, char *argv[])
{
	time(0);
	return test();
}

#define main test
#include "test.c"
Here time(0) is a last-step mask trigger. The source is compiled by gcc-3.3 (with as), and linked against libc-2.3.2 and libm-2.3.2. Common include files other than those listed above may be added, but no other library is provided for linking.

Everything in the compiling package is non-patched, and any correct c code will be happily compiled and executed. However, after time(0) is executed, only the following system calls will be available.

read(), write(), close(), exit(), fstat().

Any other system call and any library function depending on them will simply return an error with errno set to EPERM (Operation not permitted). All other library functions should work normally.

For read() and write(), you have the 3 usual file handles stdin (0), stdout (1) and stderr (2). Here stdin is empty so there is nothing to read.

If you think you can still break the server under such a condition, I hope you good luck. On the other hand, you might have more chances if you try some "non-conventional" ways:

Maybe there is a method allowing you to make your code executed before the time(0) call. In this case you will get a few more system calls: mmap() and munmap(). No big deal except perhaps for an attempt to attack the server resources (of course your process is carefully rlimited).

A more interesting method would be to try to break gcc or one of its child processes (cc1, as, collect2, ld). There are probably quite some hidden vulnerabilities in these programs, as people usually do not raise such a question of security for gcc. If you succeed, you get access to more system services and resources - those that are needed by gcc to do its job. But far from everything; the most "promising" system calls being fork, execve, and the signal handling calls. Of course, there is no socket access, and file accesses are strictely limited to those legitimately needed by gcc. You'd better find a privilege elevation behind one of the above system calls, if you want to do anything "serious".

You may also try to confuse gcc into outputing a badly formatted ELF binary, in order to confuse the kernel's elf loader who does not have a good reputation of solidity. It is for this reason that the compiled code is not directely loaded by execve(). Instead, I use /lib/ld-2.3.2.so to load it. ld-2.3.2.so is a trustworthy binary, and when it starts to read your compiled binary, there is already not many system calls left unmasked, except open(), mmap(), stat() and some similar ones.

Or, maybe simply by accessing some memory area that are not supposed to be accessible to your process, but that the kernel has forgotten to close to you? Sysmask doesn't protect against this, but if such a beast still exists today, Linux would be worthless.

The same is true for provoked exceptions, by the asm mnemonics bound, div or similar. Or an io port mistakenly left accessible.

General limitations

For all the three methods, the input code size is limited to 16K. This is probably a temporary limitation, due to the fact that I don't want to do the extra work to extend it for the time being.

The code is supposed to be plain ASCII. There is not (yet) a strict checking for this, but char values below 32 (except new_line and tab) are folded to space, and most characters above 128 are folded to their unaccented counterparts under ISO-8859-1. This should be more than enough to protect against any attempt to embed binary codes in the submitted source or script, for example in an attack by buffer overflow against the compiler.

The programs can only write to two standard file handles: the standard output channel (stdout for c and 1 for sh), and the standard error channel (stderr for c and 2 for sh). Both have size limits of 64K. Moreoever, WIMS will generate an error message (intermediate result too long) if one of the files grows over 16K.

The sh script can also open /dev/null for writing. No other device driver is available.

The standard input is empty. If you have data, they should be embedded in the code.

Of course I haven't tested all the potential ways of attack. And I don't know whether one of the concerned programs is breakable: ash, perl, gcc, awk ... I just make the hypothesis that ANY of them is breakable, then try to make sure that even if it is broken, the server's security is still preserved. It is up to the attacker to find out an effective exploit. If so, he is welcome to break it in any way he likes, including and not limited to DoS attacks, as long as everything is done under the chroot environment.

For more details about what is masked at which stage, you need to study the protocol of sysmask, then read the corresponding token definitions that can be downloaded here.

Exercises

Before anything really serious, try to do the following exercises that are known to be possible. These are for your own tests, no success record is kept.

The configuration files of sysmask contain the hint or solution of most of the exercises.

  1. Examine your current directory and its contents. (sh mode, entry level.)

  2. Find out the process id of your process. (sh mode, entry level.)

  3. Find out the uid and gid of your process. (sh mode, level 2.)

  4. Find out the system time of the server. (sh mode, level 2.)

  5. Find out the two existing files in /etc that are readable, and examine them: attributes, ownership, timestamps, content. (sh mode, level 2.)

  6. Find out how non-printable characters embedded in your codes are translated. (any mode, level 2.)

  7. Find out the number and depth of forks allowed, as well as the limit of number of active processes. (sh mode, level 3.)

  8. Find out the address space and time limitations of your process. (any mode, level 3.)

  9. Check whether your code is running under native Linux, vm86 or UML. (c mode, advanced level.)