Putting dc in (chroot) jail
A little over 4 years ago, I set up a VM and configured it to offer dc over a network connection using xinetd. I set it up at http://dc.pr0.uk and made it available via a socket connection on port 1312.
Yesterday morning I woke to read a nice email from Sylvan Butler pointing out that users could run shell commands from dc…
I had set up the dc command to run as a user “dc”, but still, if someone could run a shell command they could, for example, put a key in the dc user’s .ssh config, run sendmail (if it was set up), try for privelidge escalations to get root etc.
I’m not sure what the 2017 version of me was thinking (or wasn’t), but the 2022 version of me is not happy to leave it like this. So here’s how I put dc in jail.
Firstly, how do you run shell commands from dc? It’s very easy. Just prefix with a bang:
So, really easy. Even if it was hard, it would still be bad.
This needed to be fixed. Firstly I thought about what else was on the VM - nothing that matters. This is a good thing because the helpful Sylvan might not have been the first person to spot the issue (although network dc is pretty niche). I still don’t want this vulnerability though as someone else getting access to this box could still use it to send spam, host malware or anything else they wanted to do to a cheap tiny vm.
I looked at restricting the dc user further (it had no login shell, and no home directory already), but it felt like I would always be missing something, so I turned to chroot jails.
A chroot jail lets you run a command, specifying a directory which is used as /
for that command. The command (In theory) can’t escape that directory, so can’t see or touch anything outside it. Chroot is a kernel feature, and forms a basic security feature of linux, so should be good enough to protect network dc if set up correctly, even if it’s not perfect.
Firstly, let’s set up the directory for the jail. We need the programs to run inside the jail, and their dependent libraries. The script to run a networked dc instance looks like this:
Firstly, I’ve used bash here, but this script is trivial, so it can use sh instead. We also need to keep the sed (I’m sure there are plenty of ways to do the replace not using sed, but it’s working fine as it is). For each of the 3 programs we need to run the script, I ran ldd
to get their dependencies:
So we copy those files to the exact directory structure inside the jail directory. Afterwards it looks like this:
and here is the modified dctelnet command:
I’ve switched to using sh instead of bash, and all of the commands are now relative paths, as they are just in the root directory.
First attempt
Now I have a directory that I can use for a chrooted dc network dc. I need to set up the xinetdconfig to use chroot and the jail I have set up:
I needed to set the HOME
and PATH
environment variables otherwise (not sure whether it was sh,sed or dc causing it) I got a segfault, and to run chroot, you need to be root, so I could no longer run the service as the user dc
. This shouldn’t be a problem because the resulting process is constrained.
A bit more security
Chroot jails have a reputation for being easy to get wrong, and they are not something I have done a lot of work with, so I want to take a bit of time to think about whether I’ve left any glaring holes, and also try to improve on the simple option above a bit if I can.
Firstly, can dc still execute commands with the !
operation?
Nope. Ok, that’s good. The chroot jail has sh though, and has it in the PATH, so can it still get a shell and call dc, sh and sed?
pwd
is a builtin, so it looks like the answer is no, but why?
Running strings on my version of dc, there is no mention of sh
or exec
, but there is a mention of system
. From the man page of system:
So dc calls system() when you use !
, which makes sense. system()
calls /bin/sh
, which does not exist in the jail, breaking the !
call.
For a system that I don’t care about, that is of little value to anyone else, that sees very little traffic, that’s probably good enough, but I want to make it a bit better - if there was a problem with the dc program, or you could get it to pass something to sed, and trigger an issue with that, you could mess with the jail file system, overwrite the dc application, and likely break out of jail as the whole thing is running as root.
So I want to do two things. Firstly, I don’t want dc running as root in the jail. Secondly, I want to throw away the environment after each use, so if you figure out how to mess with it you don’t affect anyone else’s fun.
Here’s a bash script which I think does both of these things:
- Line 2 -
set -e
causes the script to exit on the first error - Lines 3 & 4 - make a temporary directory to run in, then set a trap to clean it up when the script exits.
- I then copy the required files for the jail to the new temp directory, set $HOME and SPATH and run the jail as an unprivileged user (uid 1001).
Now to make some changes to the xinetd file:
The new version just runs the script from above. It still needs to run as root to be able to chroot.
I’ve also added some logging as this has piqued my interest and I want to see how many people (other than me) ever connect, and for how long.
As always, I’m interested in feedback or questions. I’m no expert in this setup so may not be able to answer questions, but if you see something that looks wrong (or that you know is wrong), please let me know. I’m also interested to hear other ways of process isolation - I know I could have used containers, and think I could have used systemd or SELinux features (or both) to further lock down the dc user and achive a similar result.
Thanks for reading.