Last week I've published an article about the static site hosting hurdles in which I also pitch my own implementation, Kawipiko. Then I've submitted that article to Lobsters and HackerNews, and someone else also submitted it to Reddit.
During the discussion cycle, many (if not most?) have failed to see the great potential such a simpler server (like my own) has with regard to security (and other directions). Most participants were happy with their generic HTTP server that also serves static files.
However, how secure are these HTTP servers? (Please bear with me for a second; I'm not suggesting my own implementation is safer, on the contrary...)
Quickly searching the CVE database, we identify around 100+ reports for NGinx and around 500+ for Apache HTTP. (Granted this is only a keyword search, and at least in the case of Apache not all are related to the HTTP server; but the fact that there are tens of reports is enough.) As such, it's not unreasonable to say that neither of them are without security flaws.
But, I quickly hear some say:
"yeah, these are complex pieces of software,
and I only use the file serving functionality,
plus I chroot
my server..."
To which I can only point to
CVE-2021-33909,
an interesting vulnerability that allows any user (or service)
to achieve root
privileges by just playing with the file-system.
Would that work in a chroot
?
It depends upon one actually employing this measure,
because looking at the ArchLinux NGinx inside chroot documentation
I doubt many have done so...
(And the same applies to most complex web servers.)
Now getting back to my own static server, Kawipiko, because it uses an embedded CDB database for all the request serving, I was able to employ Linux's seccomp facilities (via libseccomp) to reduce the allowed syscalls to the minimum amount required to have it working.
Does it need chroot
? Perhaps, but given it requires root
to bootstrap it,
I think it's impractical (and a source of security issues itself).
However, it's not needed because once started,
the server can't even look at the file-system
without getting the process kill-9
-ed by the Linux kernel.
Furthermore, with the flexibility afforded by seccomp
I was able to gradually strengthen the filtering in multiple phases
(one can only remove capabilities):
- in the initial phase I limit the permitted syscalls to all those strictly needed for the entire runtime (memory, threads, file-system, networking);
- after opening the CDB database, and before even reading from it, I drop all file-system syscalls; (any vulnerability exploited through malicious contents shouldn't have far-reaching impact;)
- after creating the listening sockets, but before any
accept
call (thus before any inbound network connection is handled), I drop everything except memory, threads, inbound sockets accept/read/write, and reading from the CDB database; - (at the moment I only filter syscalls without also constraining their arguments; it is possible, though not without great effort, to also filter the allowed options and patterns of these syscalls;)
Thus, I was able to reduce the attack surface to a handful of system calls, which in the case a security vulnerability (which certainly exists in my code or one of its dependencies) is exploited, the worst an attacker can do is mess with other connections or create threads and allocate memory until it hits (configurable) resource limits. In summary, at worst one can DoS the service.
Could this have been done with NGinx or Apache? Certainly! Although perhaps not in a day of tinkering and with the same level of strictness...
Want to take this even further?
I've already experimented with deploying Kawipiko inside Firecracker (a KVM-based virtual machine developed by AWS for their own Lambda and Fargate services).
How would an attack look in this case:
- (1) find a vulnerability in Kawipiko or one of its dependencies; (I bet a highly skilled security researcher would find one in less than a few hours;)
- (2) by using only the few syscalls available from within the process, try to attack the trimmed-down Linux kernel (running inside Firecracker), which itself has so few features compiled-in that would perhaps be quite a challenge;
- (3) once the Linux kernel (inside Firecracker) was compromised, try to compromise Firecracker itself to escape the VM;
- (4) once the Firecracker process was compromised,
and by using only a small set of syscalls available to this process
(because Firecracker also uses
seccomp
and other security features), try to escape and compromise the host system;
Quite a challenge, right? :) Only time (and effort) can tell...