Guides

MUD Security with Firewalls and iptables

Legacy MUD codebases built on C derivatives of DikuMUD and CircleMUD contain inherent security risks including unsafe string operations, plaintext credential storage, and lack of input sanitization. This guide provides concrete implementation steps to harden these systems against buffer overflows, credential theft, command injection, and DDoS attacks while maintaining compatibility with legacy client protocols and player file formats.

60-90 minutes8 steps
MUD Security with Firewalls and iptables illustration
Placeholder illustration shown while custom artwork is being produced.
1

Replace unsafe string operations in the command interpreter

Audit the command interpreter (typically interpreter.c or comm.c) for all instances of strcpy, strcat, and sprintf handling player input. Replace with strncpy, strncat, and snprintf using sizeof(destination)-1 as the length limit to prevent buffer overflows in command parsing and network input buffers.

interpreter.c.patch
// BEFORE: Unsafe
typedef char buf_t[MAX_INPUT_LENGTH];
buf_t buf;
strcpy(buf, argument);

// AFTER: Safe bounds checking
if (strlen(argument) >= sizeof(buf)) {
    log_security("Input overflow from %s", GET_NAME(ch));
    return;
}
strncpy(buf, argument, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';

⚠ Common Pitfalls

  • Off-by-one errors leaving buffers unterminated
  • Breaking color code parsing that relies on raw string manipulation
  • Performance degradation from repeated strlen calls on large inputs
2

Implement bcrypt password hashing for player files

Replace MD5, crypt(3) DES, or plaintext password storage with bcrypt via crypt_rn() or libsodium. Modify db.c load/write functions to store hashed passwords with unique salts. Use crypt_ra() for thread safety if running multiple driver threads.

password_security.c
// In db.c during player load
if (!player->passwd[0] || strlen(player->passwd) < 60) {
    // Rehash legacy passwords on next login
    SET_BIT(PLR_FLAGS(ch), PLR_REHASH);
}

// In act.other.c do_password
char salt[16];
arc4random_buf(salt, sizeof(salt));
char *hash = crypt_rn(newpass, gensalt(salt), arena, sizeof(arena));
if (!hash || !constant_time_compare(hash, stored_hash)) {
    send_to_char(ch, "Authentication failed.");
}

⚠ Common Pitfalls

  • Breaking existing player files requiring password reset emails
  • Insufficient entropy on salt generation
  • Failing to use constant-time comparison enabling timing attacks
3

Configure iptables rate limiting against connection floods

Implement connection tracking rules for the MUD port (typically 4000-5000) to prevent SYN flood DDoS attacks. Limit new connections to 10 per minute per IP with burst allowance, and restrict total concurrent connections per IP to prevent resource exhaustion.

iptables-mud-rules.sh
# Drop new connections if more than 10 attempts per minute
iptables -A INPUT -p tcp --dport 4000 -m conntrack --ctstate NEW \
  -m recent --set --name MUD_CONN
iptables -A INPUT -p tcp --dport 4000 -m conntrack --ctstate NEW \
  -m recent --update --seconds 60 --hitcount 10 --name MUD_CONN -j DROP

# Limit concurrent connections per IP to 3
iptables -A INPUT -p tcp --syn --dport 4000 -m connlimit \
  --connlimit-above 3 --connlimit-mask 32 -j REJECT

⚠ Common Pitfalls

  • Blocking legitimate players behind NAT routers (corporate/university networks)
  • Conntrack table exhaustion on high-traffic servers requiring nf_conntrack_max tuning
  • Breaking web clients that open multiple connections for AJAX polling
4

Sanitize command parser against injection and control characters

Implement a whitelist filter in process_input() or similar entry point to reject control characters (0x00-0x08, 0x0B-0x1F) and shell metacharacters (`$|;\`) before command tokenization. This prevents escape sequence injection and command chaining attacks through crafted telnet negotiations.

input_sanitization.c
// In comm.c process_input()
for (char *p = input; *p; p++) {
    if ((*p > 0 && *p < 9) || (*p > 13 && *p < 32) || 
        *p == '`' || *p == '$' || *p == '|' || *p == ';') {
        log_security("Invalid char 0x%02X from %s", *p, GET_NAME(ch));
        *p = '\0'; // Truncate at first invalid char
        break;
    }
    // Normalize high-bit characters if not supporting UTF-8
    if (*p & 0x80) *p &= 0x7F;
}

⚠ Common Pitfalls

  • Over-filtering legitimate player names with extended ASCII
  • Breaking MCCP (MUD Client Compression Protocol) negotiation which uses IAC sequences
  • Telnet option handling conflicts requiring IAC (0xFF) passthrough exceptions
5

Harden SSH administrative access

Configure sshd to disable password authentication entirely, enforce key-based authentication only, and restrict login to specific administrative accounts. If the MUD runs on a non-standard SSH port, ensure fail2ban monitors both ports. Disable root login and restrict TCP forwarding to prevent tunneling exploits.

sshd_config
# /etc/ssh/sshd_config
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers mudadmin backupagent
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowTcpForwarding no
X11Forwarding no

⚠ Common Pitfalls

  • Locking out administrators if keys are lost without console access
  • Breaking automated backup scripts using password auth
  • Port obfuscation (2222) providing minimal security while complicating logging
6

Deploy fail2ban for brute force protection

Configure fail2ban to monitor MUD login logs (typically in libexec/logs/ or syslog) for failed password attempts. Create a custom filter regex matching your MUD's "Wrong password" log format. Ban IPs for 1 hour after 3 failed attempts to prevent credential stuffing against legacy accounts.

fail2ban-mud.conf
# /etc/fail2ban/jail.local
[mud-telnet]
enabled = true
port = 4000
filter = mud-auth
logpath = /mud/lib/log/syslog
maxretry = 3
bantime = 3600
findtime = 600
backend = polling

# /etc/fail2ban/filter.d/mud-auth.conf
[Definition]
failregex = %HOSTNAME%.*Wrong password for .*\n.*IP: <HOST>
ignoreregex = %HOSTNAME%.*Successful login.*<HOST>

⚠ Common Pitfalls

  • False positives from shared university networks hitting multiple accounts
  • Persistent attackers using rotating botnets requiring permanent blacklists
  • Log format changes breaking regex patterns during MUD version updates
7

Implement immutable audit logging for sensitive operations

Extend wiz commands and character modification functions to write security-relevant events to an append-only log file or remote syslog server. Log password changes, privilege grants (wiz commands), player file modifications by immortals, and character deletion requests with timestamps, IP addresses, and admin credentials.

security_logging.c
// In security_log.c
void log_security_event(struct char_data *ch, const char *event, 
                        const char *target, const char *details) {
    FILE *fp = fopen("/var/log/mud/audit.log", "a");
    if (!fp) {
        mudlog(BRF, LVL_GOD, TRUE, "SECURITY: Failed to open audit log");
        return;
    }
    // Set append-only attribute (chattr +a) on file
    fprintf(fp, "[%ld] %s %s by %s (%s) Target: %s Details: %s\n",
            time(NULL), event, target, GET_NAME(ch), 
            ch->desc ? ch->desc->host : "internal", 
            details ? details : "none");
    fclose(fp);
}

⚠ Common Pitfalls

  • Log file permissions allowing tampering by compromised MUD process (use chattr +a)
  • GDPR compliance issues with storing IP addresses indefinitely
  • Disk space exhaustion from verbose logging requiring logrotate configuration
8

Containerize or chroot the MUD process

Run the MUD driver inside a chroot jail or systemd-nspawn container with read-only access to binaries and libraries, writable only to specific directories for player files, logs, and temporary buffers. Mount /proc and /sys as read-only or hide entirely to prevent information leakage from kernel vulnerabilities.

mud-systemd.service
# /etc/systemd/system/mud.service
[Service]
Type=simple
User=mud
Group=mud
ExecStart=/usr/local/bin/circle %i
WorkingDirectory=/var/lib/mud
ReadWritePaths=/var/lib/mud/lib/plrfiles /var/lib/mud/log
ReadOnlyPaths=/usr/local/bin /usr/lib /lib
InaccessiblePaths=/home /root /boot
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true

⚠ Common Pitfalls

  • Breaking external shell commands (mail notification scripts, external compilers)
  • Shared library loading issues requiring ld.so.cache inside chroot
  • Inability to access system time or DNS resolution without /etc/resolv.conf bind mount

What you built

Security hardening for legacy MUDs requires prioritizing input validation and password hashing to mitigate the most common automated attack vectors. Implement steps 1, 2, and 4 first to eliminate buffer overflows and credential theft, then layer network defenses with iptables and fail2ban. Maintain offline backups before migrating player files to new hashing schemes, and test all changes in a staging environment before production deployment to prevent locking out legitimate players.