Securing my IRC (irssi, screen) session with dtach and systemd
A recent vulnerability in GNU screen caused some people to reconsider their commitment to the venerable terminal multiplexing program. GNU screen is probably used by thousands of old sysadmins around the world to run long-standing processes and, particularly, IRC sessions, which are especially vulnerable to arbitrary garbage coming on ... screen, so to speak.
So this vulnerability matters, and you should definitely pay attention to it. If you haven't switched to tmux yet, now might be a good time to get your fingers trained. But don't switch to it just yet for your IRC session, and read on for a better, more secure solution.
After all, it's not because we found this flaw in screen
that it
doesn't exist in tmux
(or your favorite terminal emulator, for that
matter, a much scarier thought).
Hardening my bouncer
Back in March 2019, I had already switched away from screen for
IRC, but not to tmux
like many did, but to dtach. I figured
that I didn't actually need multiplexing to run my long-running IRC
session: I just needed to be able to reattach to the terminal. That's
what dtach
does. No windows, no panes, and, especially, no way to
start a new shell, which is exactly the kind of hardening I need.
So I came up with this, to start irssi
:
dtach -N /run/$USER/dtach-irssi.socket irssi
To attach:
dtach -a /run/$USER/dtach-irssi.socket
Fairly simple no? Already one attack vector gone: evil attacker can't get a new shell through my terminal multiplexer, yay.
Splitting into another user
But why stop there! Why am I running irssi
as my main user anyways!
Let's take the lessons from good UNIX security, and run this as a
separate user altogether. This requires me to create another user, say
foo-irc
:
adduser foo-irc
... and run it as a systemd
service, because how else are you going
to start this thing anyways, cron
? I came up with something
like this unit file:
[Unit]
Description=IRC screen session
After=network.target
[Service]
Type=simple
Environment="TERM=screen.xterm-256color"
User=%i
RuntimeDirectory=%i
ExecStart=-/usr/bin/dtach -N /run/%i/dtach-irssi.socket irssi
ExecStop=-/bin/sh -c 'echo /quit stopping service... | exec /usr/bin/dtach -p /run/%i/dtach-irssi.socket'
ExecReload=-/bin/sh -c 'echo /restart | exec /usr/bin/dtach -p /run/%i/dtach-irssi.socket'
Notice this is a service template, because of the %i
stuff. I don't
actually remember how to enable this thing, but let's say you drop
this in /etc/systemd/system/irssi@.service
, then you run:
systemctl daemon-reload
And then, not sure about this bit, instantiate that template:
systemctl enable irssi@foo-irc.service
And then this should start the irssi
session:
systemctl start irssi@foo-irc.service
To access the session:
sudo -u foo-irc dtach -a /run/foo-irc/dtach-irssi.socket
Obviously, you will probably need to migrate your irssi
configuration over, otherwise you'll end up with a blank, old-school
irssi
. Take a moment to savor the view though. Nostalgia. Ah.
Hardening irssi
But this is still not enough. That pesky foo-irc
user can still
launch arbitrary commands, thanks to irssi
/exec
(and a generous
Perl scripting environment). Let's throw the entire kitchen sink at it
and see what sticks. At this point, the unit file becomes too long to
just maintain in a blog post (which would be silly, but not unheard
of), so just look at this git repository instead.
Password-less remote irssi
The neat thing with this hardening is that I now feel comfortable
enough with the setup to just add a password-less SSH key to that
(basically throwaway) account: worst that can happen if someone gets a
hold of that SSH key is they land in a heavily sandboxed irssi
session. So yay, no password to jump on chat. Like a real client or
something.
Just make sure to secure the SSH key you'll deploy in
authorized_keys
with:
restrict,pty,command="dtach -a /run/foo-irc/dtach-irssi.socket" [...]
Obviously, make sure the keys are not writable by the user, by placing
it somewhere outside their home, which might require hacking at your
server's SSH configuration. Because otherwise a compromised user will
be able to change his own authorized_keys
, which could be bad.
Configuration management
And at this point, you may have noticed that you shouldn't actually
followed my instructions to the letter. Instead, just use this neat
little Puppet module which does all of the above, but also include
some little wrapper so that mosh
still works.
It also includes instructions on how to setup your SSH keys.
Enjoy, and let me know if (or rather, how) I messed up.
Updates
it seems that dtach is not very active upstream: the last release (0.9) is from 2016, and the last commit (at the time of writing) is from 2017
dtach is not necessarily safer than screen or tmux from arbitrary input from the outside, in fact there was a vulnerability on dtach CVE-2012-3368 that led to an attacker accessing stack memory (but maybe not code execution)
after writing the Puppet module and publishing this article, I started to get weird behavior from dtach: i would leave the office at night and then return the next morning to find that I was timed out on servers. from my perspective,
irssi
noticed only when I re-attached the session:09:39:51 -!- Irssi: warning Broken pipe 09:39:51 -!- Irssi: warning SSL write error: Broken pipe 09:39:51 -!- Irssi: warning SSL write error: Broken pipe 09:39:51 -!- Irssi: warning SSL write error: Broken pipe 09:39:51 -!- Irssi: warning SSL write error: Broken pipe 09:39:51 [bitlbee] -!- Irssi: Connection lost to localhost 09:39:51 -!- Irssi: warning SSL write error: Broken pipe 09:39:51 [gitter] -!- Irssi: Connection lost to irc.gitter.im 09:39:51 [OFTC] -!- Irssi: Connection lost to irc.oftc.net 09:42:34 [IMC] -!- Irssi: Connection lost to irc.indymedia.org 09:42:34 -!- Irssi: Connection lost to irc.hackint.org 09:42:34 -!- Irssi: Connection lost to chat.freenode.net 09:42:34 -!- dtach_away: Set away
from the outside I actually timed out a few minutes after I detached, which also makes for a weird asymmetry:
22:31:00 -!- anarcat [~anarcat@ocean] has quit [Ping timeout: 250 seconds]
that is eleven hours before the error I get.
the mosh wrapper script seems to not work as well as it did before. somehow just running
mosh $server
hangs with a blank screen instead of instantly rejoining the session. I'm not sure it is related to the timeout problem but I did rewrite the wrapper before publication. this is the old version:#!/bin/sh # inspired by https://serverfault.com/questions/749474/ssh-authorized-keys-command-option-multiple-commands command="dtach -a /run/anarcat-irc/dtach-irssi.socket" case "$SSH_ORIGINAL_COMMAND" in mosh-server*) exec mosh-server -- $command ;; *) exec $command ;; esac
I'm thinking of trying that one out for a while to see if it's related. The weirdest thing is that mosh "un-hangs" if i reattach with plain
ssh
, so there's definitely something fishy going on here.I've found other alternatives to dtach/screen/tmux: diss (rust, simple dtach alternative), shpool (rust, similar to dtach and diss but with a single client and some more "smart" logic about rendering and shell prompts)
Thanks for that blog, sounds really interesting - I guess lots of people are running something like tmux/*irc.
Searching around for dtach I realized there is a fork of the original repo that includes several fixes and has a "release" 0.10 just 2 month ago. It also deals with ANSI sequences etc, so you might have better success with that one: https://github.com/xPMo/dtach (not related in anyway to me, just found it)