DNSCrypt is a DNS Resolver that encrypts the DNS requests between you and the first level DNS resolver. I have a guide for setting it up here. This guide will be about restricting the process and user account, making DNSCrypt more resilient to attack – I will continue to update this guide, I have a few more ideas.
One of the nice features of DNSCrypt is that it actually takes security into account. I wish this weren’t something to be shocked by, but, *gasp* it actually uses compiler security flags. Specifically, it uses the following flags:
-fPIC -fPIE -fstack-protector-all -fno-strict-overflow -fwrapv **
-fPIC and -fPIE tell the compiler to create a relocatable binary, completing the implementation of ASLR. It’s a mitigation technique we rarely seen used, despite it being somewhat critical, and it having been around for years. So right off the bat they’re doing more than most.
-fstack-protector-all (unlike the oft used -fstack-protector, which only protects functions using char arrays/strings) tells the compiler to protect every function with a stack canary. If an overflow occurs the canary may be overwritten, and the function will fail.
-fno-strict-overflow and -fwrapv are essentially the same (in other words, I don’t know the difference) and they tell GCC to not make assumptions about overflows, basically to not assume that overflows won’t occur. Compilers make the assumption that overflows won’t happen when they generate the optimized assembly, so they can build optimizations with that assumption – this prevents that, which is safer.
So these are nice, and we like them. But DNSCrypt also does a bit more.
You can create a new DNSCrypt user with no write rights, and it will chroot itself into that user, and drop rights. This is great, since a chroot’d process with no ability to write is difficult to break out of. And running as a separate user means no X11 access, it gets its own home folder, and it’s generally more isolated from the system – all good things!
But it means some other stuff too. Because it does all of the above we as users can take that protection further – beyond where typical programs allow us to. I think this demonstrates what a strong security model really can do when built from the ground up.
So, on to what we can do.
First thing’s first, we’re going to want some information on our DNSCrypt user.
run ‘id dnscrypt’
You should get something similar to:
uid=109(dnscrypt) gid=123(dnscrypt) groups=123(dnscrypt)
We’re going to need this.
IPTables On User
Note that if you’re using UFW this may cause issues, using UFW/GUFW with iptables isn’t recommended, and your mileage may vary – to remove your UFW rules run ‘iptables -F’.
Normally I’m not fond of outbound filtering, but because DNSCrypt separates itself into another user, it’s actually not such a bad idea. It means that DNSCrypt can’t just switch its outbound connection to another program under the same user account, and it means that the ports we limit will be limited to that user account specifically. This assumes you are using DNSCrypt under a user called ‘dnscrypt’.
So it’s a lot more worthwhile to set up outbound filtering here.
DNSCrypt should only need outbound access to port 443, with UDP. So we can restrict it to just port 443 and UDP with the following IPTables rules:
iptables -A OUTPUT -m owner --uid-owner dnscrypt -p udp --dport 443 -j ACCEPT
iptables -A OUTPUT -m owner --uid-owner dnscrypt -j DROP
Basically, the first rule allows outbound access to the DNSCrypt user over port 443 and UDP, and the second rule denies everything. If the first rule is hit, and it passes, the second rule doesn’t have to come into play.
DNSCrypt is now restricted to UDP over port 443, and all processes running under the dnscrypt user are as well. If you followed the tip then no new connections can be made to your system except over port 53 (you can have dnscrypt use another port, in which case you’ll switch that port to whatever that one is. I have yet to figure the details of this out, I’ll edit it in when I do.)
Trusted Path Execution
If you care about security you’re already running Grsecurity, but if not, see my guide here.
Grsecurity has an option called Trusted Path Execution that allows us to limit a group, preventing it from executing files owned and only writable by root – since our program doesn’t run as root, and can’t write anywhere, it means it won’t be able to execute anything at all.
So check the TPE box and add the GID for untrusted users, in this case 123.
Now this protection is superfluous, DNSCrypt shouldn’t be able to write to the filesystem, so it shouldn’t be able to execute any payloads off of the file system, but it’s still good to have as the protection is now implemented by the user account itself, and doesn’t rely on the program to drop rights properly, or a perfect implementation of chroot.
While you’re compiling your Grsecurity kernel, you can also go ahead and turn on every single chroot restriction without worry – DNSCrypt works fine with them all. DNSCrypt already can’t write to its chroot, so as far as I know there’s no known bypass as is, but you can safely enable all of these restrictions. Although some of the protections are a bit redundant due to the aforementioned write restrictions, there are a few that are quite nice, such as:
More restrictions on our exposed service.
Apparmor is an LSM (Linux Security Module) program that restricts a process. If Apparmor is the LSM used on your distribution (Ubuntu derivatives) you can find my profile here. Apparmor will restrict file access, what programs can be executed, what libraries can be loaded, etc. An attacker who winds up in a program that is confined with apparmor must either find a flaw in apparmor, or the profile, or they have to use a local escalation attack. If you’re using everything listed above this is going to be a lot of work for them.
Users of other LSM such as SELinux will need to build their own profiles. This shouldn’t be hard, DNSCrypt needs little file access to work.
Given the situation where an attacker finds himself compromosing the DNSCrypt-Proxy on a system that has done all of the above, they’re going to be pretty pissed off. There is still room for improvement, (seccomp filters) but right now an attacker is going to have to do a lot to get an exploit to be reliable.
For a program like DNSCrypt this level of security is great. It already chroots itself to a directory that it can’t write to, and they use compiler security, so you know they’re taking this stuff seriously. That’s what allows us to spend our time securing it further. If DNSCrypt did not so gracefully run as another user, and if it weren’t built to drop its rights to the extent that it does, then our apparmor profile would be more convoluted, TPE may not be possible, and an outbound Firewall would have been a useless attempt at security through obscurity. But because it’s built from the ground up to be this way we can reinforce it well.
Much of this can be done to any process/ service with a bit of a change, but it’s nice to be able to do this to a process like DNSCrypt.
Keep in mind that you can add your own files to the makefile, such as “-march=native”, optimizing for your CPU. I can’t guarantee that this will play nice, or that it won’t add in unsafe compiler optimization! But you may end up using something like AES instructions since this deal swith crypto, and math, and this could speed things up.
Tip: The following commands will set your firewall so that:
1) If a connection is new, is over the loopback interface, is udp, and uses port 53, we accept it (allows dns resolution)
2) If a connection is already established from an outbound connection then we allow an inbound connection.
3)All other connections that do not meet the above criteria are blocked.
iptables -A INPUT -i lo -m state --state NEW -p udp --dport 53 -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -m state --state NEW,INVALID -j REJECT