接手一个内网安全巡检任务时,我在某组备份服务器上发现了一个看起来很普通的端口:873。这个端口在大多数运维同事眼里只是“rsync的默认端口”,但当我在没有账号的情况下,用一条命令把服务器上整个备份目录的文件列表拉到本地时,旁边的同事表情说明了一切。这就是典型的rsync未授权访问——一个不算新、但一直没被重视的配置型风险。
这篇文章面向安全测试人员、运维工程师和做基线检查的朋友,把我在实际授权测试中验证该类问题时的完整思路写出来:从协议原理、模块枚举、读写验证到目录穿越边界,再到最终如何加固、如何确认修复生效。内容聚焦可落地的操作,不堆概念。
1. rsync 为什么会在内网里“裸奔”:协议设计、配置习惯与隐患根源
很多问题之所以反复出现,是因为大家只知道“怎么用”,不深究“为什么会有这个口子”。rsync未授权访问的核心其实是rsync的daemon模式(守护进程模式)下的模块配置问题,而不是rsync客户端本身的问题。
1.1 rsync 的工作方式与模块(module)设计
rsync有两类典型用法:一种是通过SSH通道传输,比如rsync -avz -e ssh src user@host:/dest;另一种是在服务器上跑一个rsync daemon,监听873端口,客户端通过rsync://ip/module的方式直接访问。绝大多数未授权访问漏洞,都出在第二种模式。
rsync daemon通过配置文件/etc/rsyncd.conf来定义共享模块。每个模块类似一个“共享目录”,可以单独配置访问权限、认证方式、是否允许列出、是否只读等。配置文件结构大致如下:
[backup] path = /data/backup comment = backup data list = yes read only = yes uid = root gid = root这里[backup]就是模块名,path指定了对应到服务器上的物理目录。客户端不需要知道服务器目录结构,只需要知道模块名,就能访问模块对应目录下的文件。
问题就在这里:模块的认证、授权完全依赖于配置项。如果某个模块没有配置auth users和secrets file,那么任何人都能直接连接;如果read only = no,那就不仅能读,还能写——这对业务服务器来说,等价于给攻击者开了一个“匿名FTP + 上传目录”的复合口子。
1.2 为什么大家总在配置上栽跟头
我的经验是:rsync未授权访问很少是有意为之,绝大多数是“顺手配的”结果。
比如有些运维同事图省事,把rsync当作纯内网备份的快捷通道,想着“内网防火墙拦着,外面进不来”,就省掉了账号密码;有些自动化部署脚本为了批量分发文件时不用交互输密码,直接把认证配置注释掉了;还有些是兼容历史遗留配置,从老机器迁移过来时把旧配置一并复制,但新环境里根本没有配套的安全策略。
这些配置从功能上看都没问题——内网里确实能正常同步,备份也确实跑得挺好。但隐患在于:服务器一旦被其他途径攻破,或者内网出现横向移动路径,这台rsync服务器就会成为“文件中转站”。更现实的情况是,公网扫描器一直在扫873端口,只要有暴露,自动化扫描工具几秒钟就能识别出模块列表,后续问题只是时间问题。
所以我的结论很明确:rsync未授权访问不是“工具漏洞”,是“配置漏洞”。它不依赖任何高危CVE,任何版本的rsync只要配置不当,都可能存在同类问题。这也是这类问题在渗透测试里常被归为“中危/高危配置问题”的原因。
2. 授权环境下的风险确认:从端口探测到模块枚举的完整步骤
先说一句必要的话:下文所有操作,都必须在获得充分授权、且处于合法的渗透测试或安全巡检场景下进行。未授权探测他人系统,本身就是违规甚至违法的事。下面这些步骤,是我在内部授权项目里验证该问题时的标准做法。
2.1 第一步:确认873端口与rsync协议横幅
端口探测最直接的方式是nmap,但我会先做一个更轻量、更“不显眼”的动作:直接用nc(或ncat)连接目标主机的873端口,看返回内容。
nc -vn 192.168.2.10 873如果目标端口开着且是rsync服务,通常会直接返回类似这样的横幅:
@RSYNCD: 31.0这个字符串就是rsync协议握手信息。看到这个,基本可以确认目标就是rsync daemon,而不是其他应用。这一步看似简单,实际很关键,因为在真实环境中873端口可能被SSL工具、自定义脚本或其他服务占用,仅凭端口号判断是会出现偏差的。
nmap则可以做更自动化的确认:
nmap -sV -p 873 192.168.2.10典型识别结果会出现rsync或rsyncd的版本信息。如果对方运行的是多实例或者自定义编译的rsync,手动nc确认依然是最稳妥的办法。
2.2 第二步:枚举模块列表,判断信息暴露面
确认是rsync服务后,下一步就是看对方到底暴露了什么。这里用rsync客户端自带的模块枚举能力:
rsync --list-only rsync://192.168.2.10/注意最后的斜杠不能少。这是“列出根模块”的意思。正常情况下,服务器会返回所有list = yes的模块名,以及模块注释(comment字段)。
典型的输出长这样:
backup Backup directory (backup data) uploads Uploads from internal network public Public share for office files有的版本还会用类似-rw-r--r--的方式显示模块属性。这里需要留意:模块列表本身就是在泄露信息。模块名结合comment,往往能直接判断出这台服务器的用途、重要程度、哪些业务系统依赖它。
如果模块配置了list = no,那就不会出现在这里。但注意,list = no并不等于模块不可访问——只代表“不广播名称”。如果你能猜到模块名(比如backup、data、share这种常见命名),仍然可以直接连接。这一点我放在第4节专门展开,因为在实战里踩坑概率很高。
2.3 第三步:分别判断模块的“可读性”与“可写性”
枚举到模块后,为每个模块单独执行:
- 看目录内容:
rsync --list-only rsync://192.168.2.10/backup/ - 看是否允许下载:
rsync -r rsync://192.168.2.10/backup/ /tmp/rsync_download/ - 看是否允许上传:
rsync /tmp/test.txt rsync://192.168.2.10/uploads/test.txt
每一步都可能出现不同结果。最常见的有三种:
| 探测动作 | 可能收到的报错 | 含义 |
|---|---|---|
| 模块列表 | @ERROR: access denied to backup | 存在主机白名单或账号控制,需要授权 |
| 列目录 | @ERROR: chdir failed | 模块path存在但权限不足,或配置的path不存在 |
| 下载文件 | rsync: opendir ... Permission denied | 系统文件权限限制了rsync进程账号的读取范围 |
| 上传文件 | 正常返回文件列表 | 可写,需重点标记为高风险 |
判断“是否存在未授权访问”,在看列表和下载时能否拿到真实文件内容;判断“风险等级”,则重点看是否能上传。能上传比仅能下载严重得多——后者是数据泄露,前者几乎等于远程写入。
3. 实操验证:数据拉取、文件回传与目录穿越边界的真实测试结果
这一节是完整的验证过程复现,我用自己的测试环境来说明。模拟环境里,目标主机是一台未加固的备份服务器,rsyncd.conf中存在多个模块,全部未启用认证。
3.1 只读场景验证:从模块清单到敏感文件落地
目标主机IP假设为192.168.2.10,先枚举:
rsync --list-only rsync://192.168.2.10/返回结果里有两个值得注意的模块:www_backup和db_backup。名字已经把用途暴露了——前者是网站文件备份,后者是数据库备份。
接着列目录:
rsync --list-only rsync://192.168.2.10/www_backup/输出显示出生产环境的Web源码备份文件列表。我直接尝试拉取:
mkdir -p /tmp/rsync_test rsync -av rsync://192.168.2.10/www_backup/ /tmp/rsync_test/这里-a保留属性,-v输出过程。如果文件不多,几秒内就能全部落地。下载完成后,我在备份文件里找到了数据库连接配置、部分业务源码等敏感内容。从验证角度来说,这一步已经能确认“未授权读取”成立。
3.2 可写场景验证:上传验证与覆盖风险
db_backup模块的配置是read only = no。我先在本地创建一个特征测试文件:
echo "rsync-test-write-$(date +%s)" > /tmp/w_probe.txt rsync -av /tmp/w_probe.txt rsync://192.168.2.10/db_backup/w_probe.txt传输过程没报错,说明上传成功。为了确认文件真的留在了目标服务器上,我在另一台机器上通过rsync再次拉取这个文件做一致性校验。返回内容中出现了相同的特征字符串。
可写模块意味着什么?最直接的风险是覆盖。如果模块映射的是备份目录,且uid/gid配置为root,那么攻击者可以覆盖备份文件,把恶意脚本伪装成正常备份文件,等待管理员在恢复演练或紧急恢复时执行。代码层面的持久化、勒索部署、供应链投毒,都可能在“备份服务器被未授权写入”这个前提下展开。这也是为什么我一旦确认可写,就会直接把这个模块标记为最高优先级。
3.3 目录穿越边界:chroot=no时的路径逃逸验证
目录穿越是rsync未授权访问里容易被忽视的点。原理在于:rsync模块有自己的path,但守护进程对path外目录的访问能力取决于两个条件——配置项use chroot是否为yes,以及rsync进程的运行权限。
在use chroot = no的场景下,模块可能允许客户端以../的方式跳出path目录。经典的验证命令如下:
rsync --list-only rsync://192.168.2.10/db_backup/../../etc/如果返回了/etc目录下的文件列表(比如passwd、shadow的影子、各种配置文件),那就说明目录穿越成功。我在测试环境里见过最严重的案例,是模块path设置为/data/db_backup,但因为没有chroot,客户端可以直接列出/root/.ssh/,拿到了授权的authorized_keys文件。
真实环境中,rsync的高版本对..的拦截能力有所增强。隔离测试时,多个版本的表现不完全一致。但无论如何,只要配置里没开use chroot = yes,就必须做穿越验证。这个验证成本很低,不验证则可能漏掉一个隐蔽的高危路径。
3.4 上传路径中的斜杠与通配符细节
做上传验证时,目标路径的写法经常让新手抓狂。rsync的模块路径规则和普通FTP有差异:模块后的斜杠数量、路径末尾是否有目录、源路径是否带斜杠,都会影响结果。简单说,客户端路径写法会让rsync把上传目标放到模块根目录,还是模块下的子目录,保证先列一下模块目录,确认结构,再执行上传是更稳妥的方式。
另外需要注意:某些模块可能配置了exclude规则或filter规则,即使上传成功也会被后台过滤掉。所以“上传成功”和“文件最终落地”需要二次验证,上一小节的拉取校验动作不能省。
4. 实战中容易误判的几个边界:模块隐藏、权限视角与日志盲区
这一节想分享的,不是标准流程里的步骤,而是我在多个真实项目中发现的最容易出错的判断点。
4.1 list=no 的隐藏不等于访问不了
list = no这个配置的用意是“不广播模块名”,但从风险角度来说,它带来的安全感非常有限。
有一次我对某台目标做模块枚举,返回的列表里只有一个模块,看起来一切正常。之后我在内网情报里看到一段历史配置片段,里面有一个名为syncdata的模块。我没有放过这个信息,直接尝试连接:
rsync --list-only rsync://192.168.2.10/syncdata/结果没有报“unknown module”,而是正常返回了目录列表。也就是说,这个模块确实存在,只是不对外广播。攻击者虽然不会在列表里看到它,但模块名如果来自历史泄漏、配置文件备份、命令行参数、运维文档,或者只是简单猜测,依然能够访问。
判断时要注意区分报错:如果某个模块不存在,rsync会返回@ERROR: unknown module 'xxx';如果模块存在但白名单拒绝,会返回@ERROR: access denied to xxx。这两种报错的含义完全不同。前者说明模块名无效,后者说明模块存在且已经触发了访问控制——建议继续尝试弱口令或检查白名单配置缺陷,而不是直接放弃。
4.2 权限校验要看服务进程的uid/gid,而不是“感觉上有没有权限”
rsync daemon的读写能力,由模块配置中的uid和gid决定。如果配置为uid = root,服务进程就以root身份读写模块目录,此时即使系统目录权限为755,rsync也能读取大部分系统文件;如果配置为普通用户如uid = nobody,那么即使模块可写,因为目录属主不是nobody,实际写入也会触发chown failed或mkstemp failed。
所以我做验证时,都会刻意测试写模块内、模块外、以及子目录三种路径。千万不要看到一个read only = no就直接下结论说“可写”——得实际丢一个文件进去,再拉出来看,才能确认。类似的,也别看到read only = yes就认为绝对安全——只读模块如果包含密钥、备份明文,依然是严重的数据泄露面。
4.3 日志与防火墙的“失效”盲区
默认情况下,rsync daemon的日志由syslog或rsyncd.conf中的log file决定。很多系统配置后,日志被写到/var/log/rsyncd.log,但这个文件默认可能不存在,或者日志级别不够细。如果没有主动打开transfer logging = yes或设置log format,管理员很难从日志看出“谁拉过哪个文件”。
防火墙方面更常见的问题是:安全组规则允许内网特定网段访问873,但实际服务器上还运行了容器化实例,宿主机通过端口映射或反代暴露了rsync服务,绕过原有ACL。我在一次巡检中就遇到过“规则显示只允许10.0.0.0/8访问,但云上负载均衡后端却把rsync转发到了公网”的情况。所以排查这一步时,除了看防火墙,还要看有没有反代、容器端口映射、负载均衡转发规则,这种“规则在,但没生效”的案例非常多。
5. 修复与加固:一套可以直接落到配置文件的方案
检测归检测,最终要把风险消掉。下面这套方案不是理论,是我在多次加固项目里实际用过的配置模板,按优先级排列。
5.1 先收紧rsyncd.conf:一份加固后的配置模板
加固的第一步永远是改配置。建议在模块中用read only = yes作为默认值;如果业务确实需要上传,单独开一个专用模块,并加上认证、白名单和目录隔离。
一份可直接参考的加固配置如下:
# /etc/rsyncd.conf uid = rsyncsrv gid = rsyncsrv use chroot = yes max connections = 10 timeout = 300 motd file = /etc/rsyncd.motd [backup] path = /data/backup comment = authorized backup only list = no read only = yes auth users = backup_user, audit_user secrets file = /etc/rsyncd.secrets hosts allow = 192.168.1.0/24, 192.168.2.10 hosts deny = 0.0.0.0/0关键配置项说明:
uid = rsyncsrv:创建一个专用系统账户,只用它来做文件读写,不建议用root跑rsync。use chroot = yes:把客户端限制在模块path以内,阻断目录穿越。list = no:隐藏模块名,降低模块枚举的信息泄露。read only = yes:默认只读,除非明确业务需要,否则不开写权限。hosts allow:限制来源IP,内网也分网段,而不是图省事直接允许整个内网。secrets file:启用账号认证,替代匿名访问。
注意:uid和gid配置后,模块path的属主和权限必须对应该账户有访问权。否则会出现“配置已经加固,却同步失败”的怪问题,那不是安全策略的锅,而是账号权限没配套。
5.2 密钥文件、权限位与主机白名单的协同设置
账号认证开启后,需要同步创建secrets file。该文件的格式很简单:每行一个用户名:密码。密码建议用强随机串生成,并限制该账号的系统登录权限。
创建后必须把密钥文件权限改成仅root可读:
chown root:root /etc/rsyncd.secrets chmod 600 /etc/rsyncd.secrets如果权限过宽(比如644),rsync daemon会自动拒绝启动,原因是secrets file不安全,这是一个很实用的主动性校验。
hosts allow和hosts deny的作用范围需要理解:它是rsync应用层的IP过滤,而不是系统网络层的防火墙。它是额外的一层,但一定不能替代防火墙。如果机器上跑着其他服务,或者存在端口转发,系统防火墙仍然需要单独配置。
5.3 加固效果的验证:如何确认漏洞已修复
改完配置后,重启rsync服务:
systemctl restart rsyncd # 或针对源码安装的方式 kill -HUP $(pidof rsync)然后重新走一遍检测流程,按顺序执行:
# 1 尝试匿名枚举模块,预期出现 access denied 或 unknown module rsync --list-only rsync://192.168.2.10/ # 2 对指定模块尝试匿名列目录,预期被拒绝 rsync --list-only rsync://192.168.2.10/backup/ # 3 尝试匿名上传,预期被拒绝 echo probe > /tmp/probe.txt rsync -av /tmp/probe.txt rsync://192.168.2.10/backup/probe.txt # 4 尝试目录穿越,预期被限制在模块内 rsync --list-only rsync://192.168.2.10/backup/../../etc/四种预期结果全部达标,说明加固生效。如果前两步仍然能匿名访问,优先检查secrets file是否被注释、auth users是否绑定到模块内、list是否有语法错误。
5.4 额外建议:关注rsync版本与自动化扫描
有些老旧系统上的rsync存在历史缓冲区漏洞,虽然现在不太容易被打穿,但建议还是固定版本升级。另外,如果有条件就把873端口彻底在内网隔离:rsync根本不需要对所有内网机器开放。按“最小暴露”原则,只让需要同步的那几台机器白名单访问即可。
自动化方面,如果公司有漏扫或基线检查平台,建议把“rsync匿名访问”加入自定义检测项,定期巡检。实测中这类问题最怕的不是被扫到,而是配置完成后无人复核,过几个月又被人“顺手”改回去。
5.5 修复后持续观测的思路
加固完成后,建议开启详细日志,辅助发现后续异常:
log file = /var/log/rsyncd.log transfer logging = yes log format = %h %o %f %l %b %a %m %u重点关注异常IP的大批量文件读取、反复连接、高频目录枚举等行为。配合NIDS或云日志审计,把rsync的日志纳入统一采集,能明显提高“配置被改坏后迅速发现”的概率。
结尾:说点个人体会
rsync未授权访问这类问题,技术上并不复杂,但它体现了一个很现实的安全命题:安全的本质不是工具的强弱,而是配置习惯和管理流程是否闭环。我在实际项目里做过上百次巡检,发现类似问题反复出现,原因从来都不是“不知道风险”,而是“没时间改”“改完怕影响业务”“觉得内网没关系”。但备份服务器恰恰是最容易成为靶标的资产——它存放着最有价值的数据,却又常常是防护最薄弱的环节。
如果这篇文章能带来一点行动上的改变,我希望是把加固模板直接拿过去落地,而不是停留在“了解了”。改配置花不了半小时,但它砍掉了内网横向移动里一个非常实用的跳板。接下来如果大家感兴趣,我可以继续写写rsync的日志审计规则,以及如何把这类配置检查接入现有巡检体系——这些我都在实际环境里验证过,值得单独展开一篇。