I'm drunk on a Friday night again. This time I decide to investigate how to use OpenBSD's pledge(2) and unveil(2) to enhance the security of the Drogon web application framework. Drogon in written in the C++ programming language, not known to be safe. But in truth, as a maintainer of Drogon, non of our CVEs are related to memory at all. Most either they are are directory traversal and/or abusing the HTTP spec. Anyway, in case we messed up, I still want to make sure that Drogon is as secure as possible. And OpenBSD's pledge is a really good way to do that.
About my drink, I added too much gin into my poor-man's Long Island Iced Tea. It's bitter and strong. Had to add more coke to make it drinkable. Should just got some flavored vodka instead.
=> OpenBSD manual page - pledge(2) | OpenBSD manual page - unveil(2)
pledge, is a C fuction on OpenBSD (and SerenityOS) that allows a program to restrict its access to the system. For example, a program can pledge to only use stdio and network. If the program tries to set the system time, it's considered a violation and killed immidiately. Think it like SELinux, but many default policies are already written for you. For example, the following C.
int main() { pledge("stdio inet", NULL); // From this point on, the program can only use stdio and network. // It cannot, for example, fork or exec. char buf[1024]; scanf("%s", buf); // oops vulnerable to buffer overflow, doesn't matter // the attacker can't fork or exec to run commands anyway // This will cause OpenBSD to abort the program. // fork(); return 0;
Unveil, on the otherhand, limits a program's access to the filesystem. It's A LOT harder to create a reverse shell if you can't execute stuff in /bin
and /usr/bin
.
int main() { char* cwd = getcwd(NULL, 0); unveil(cwd, "r"); // Only allow read access to the current directory unveil(NULL, NULL); // This is required to lock down the filesystem // otherwise the program can't do anything system("ls"); // This will fail because the program can't access /bin/ls }
Pledging and unveiling Drogon can be a bit complicated. The framework does quite a few things that is hidden from the user, that could be supprising.
stat
on documents to check when they were last modified
The simplest way to pledge and unveil Drogon is to do it before the framework is initialized. The following code is a good start. Add more path/permissions as needed.
using namespace drogon; unveil(app().getDocumentRoot().c_str(), "r"); unveil(app().getUploadPath().c_str(), "rwc"); // Remember to add `unveil` to the end of the list // if you add more unveil calls later. pledge("stdio rpath wpath cpath inet", NULL);
But, we can do better. Drogon only needs to create the cache directory at startup. And the queueInLoop
function on the main thread is only executed when the server is done initializing. If you know you no endpoints supports uploadingand have set client_max_body_size == client_max_memory_body_size. The following is stricter and should provide better security. In fact, the following is what I'd run for this blog if it were running on OpenBSD. (It's not, but I'm emulating unveil with landlock on Linux, can't emulate pledge yet, you need a few more paths on Linux too).
using namespace drogon; // running after finished initializing app().getLoop()->queueInLoop([]() { unveil(app().getDocumentRoot().c_str(), "r"); unveil(app().getUploadPath().c_str(), "rw"); // "c" is not needed unveil(nullptr, nullptr); pledge("stdio rpath wpath inet", NULL); // "cpath" is not needed // The "create" class is dropped since we don't need to create files. });
It's possible to further improve the security of Drogon by unveiling the configuration file (if needed), ruling out any chance of vulnerabilities in the JSON/YAML parser. But I think that's a bit overkill.
/tmp
Pledging and unveiling is not a silver bullet. They are very easy to deploy security measure that can be used to limit the damage of a vulnerability, stop some out right some times and makes the life of attackers painful. pledge
does not and cannot distinguish between LAN and WAN addresses. If an attacker can figure out how to defeat ASLR, they can still ROP their way to opening a socket and accessing the DB that way. However, I had to assume it's virtually impossible to write a Postgres client by ROPing.
text/gemini
This content has been proxied by September (ba2dc).