UFW, Fail2Ban, and a custom SSH Server

As you may have seen, this is not a standard SSH Server. It’s actually a Bubbletea application running with Wish, making it easy to create TUIs directly over an SSH connection.

But how to protect this app from bad actors trying to run some strange commands when using SSH, like [apt update && apt install sudo curl -y && sudo useradd -m -p $(openssl passwd -1 EDQbJ5nP) system && sudo usermod -aG sudo system] or using noninteractive TTYs to scan what’s happening here?

It’s simple!

We ban them.

Let’s talk about Fail2Ban

Small recap: Fail2Ban is an intrusion prevention software that scans logs and bans IP addresses matching specific rules such as failed login attempts. Each rule can be customized to match a failregexp representing the log used to detect a bad attempt.

In my case, I just need to close the connection to the user and log that this was a bad actor trying to run something not related to my app. With a little middleware, I can easily check if the TTY is not interactive or check if any command was sent to the server, and in both of those cases, create a specific log containing the IP address of the client :

return func(handler ssh.Handler) ssh.Handler {
  return func(session ssh.Session) {
    _, _, active := session.Pty()
    if len(session.Command()) != 0 || !active {
      log.Printf("[block] %s", session.RemoteAddr())
      session.Exit(0)
      return
    }
    handler(session)
  }
}

Seting up Fail2Ban

Now that we have our custom blocking log, we just need to create our custom filter and jail for Fail2ban.

For our filter, a simple definition should do the trick, only defining the failregex to match our [block] log :

[Definition]
failregex = \[block\] <HOST>

Then for our jail, we can define anything we want:

[customssh]
enabled = true
backend = systemd #if your service is running as a daemon
filter = customssh
port = 22
maxretry = 1
findtime = 1w
bantime = 1w
banaction = ufw

Then a simple fail2ban reload should do the trick.

foo@bar:~$ fai2ban-client reload

To check if our rules are properly working, check the status of fail2ban:

foo@bar:~$ sudo fail2ban-client status
Status
|- Number of jail:      2
`- Jail list:   customssh, sshd

UFW

Now that our Fail2Ban configuration is done, we need to enable UFW.

foo@bar:~$ ufw allow proto tcp from any to any port 22
foo@bar:~$ ufw reload
foo@bar:~$ ufw enable

Conclusion

With a little setup, we can easily integrate Fail2Ban with our custom server and use UFW to block incoming traffic from bad actors.