Security 26丨你看到的,可能不是文件真正的路径
复旦白泽战队
复旦白泽战队
复旦白泽战队
2026年10月29日 17:10
上海
去阅读
引言
下载,双击,一个压缩包就解压到了电脑上,这背后经过了数据提取、路径检查与文件写入等多个步骤。但是就这样一个日常的功能,因为压缩包中“软链接”的存在,就可能导致压缩包中的文件解压在任何位置,覆盖关键配置,造成代码执行等严重后果。
我们对文件系统中软链接机制进行了深入研究,发现从编程语言到应用软件,开发者们对这个四十年多前引入的特性依然不够了解。
一个路径,看似在当前目录之下,但其实文件本体远在另一个磁盘上,这就是软链接最基础的使用之一。软链接作为一种系统底层特性,在操作系统上已经存在了超过四十年,广泛应用于版本切换、文件重定向等功能。但是其灵活的特性一直没有被深入研究,造成开发者的广泛误解,导致下游软件上的层层安全风险。
当程序需要判断一个文件是否存在、确认它的类型,或者找到它的实际位置时,都可能遇到软链接。不同语言为这些操作提供了现成的接口,但接口对软链接的处理方式并不一致:同一条路径,交给不同函数,可能得到不同的判断,甚至指向不同的位置。这对路径穿越防护带来极大挑战。
复旦大学系统软件与安全实验室最新研究成果Wormholes in the File System: Understanding the Misunderstanding of Symlinks系统性分析了操作系统和不同编程语言中软链接处理问题,该论文已发表于系统安全领域国际顶级学术会议USENIX Security 2026,并于今年8月在美国巴尔的摩会场线下报告。该研究比较了10种编程语言中的60余个文件路径相关接口,揭示了看似相近的函数为何会给出不同的结果,以及这些差异如何导致路径检查问题。
01
软链接:去往想要的任何地方
软链接(symlink,也称符号链接)是一种用于引用另一个文件或目录的特殊文件类型。它与硬链接不同:软链接的文件内容是一个表示路径名的字符串,而硬链接则是直接引用一个文件对象的inode。硬链接需要引用一个已经存在的目标,软链接记录的路径则可能指向一个不存在的位置。
这种设计赋予了软链接极大的灵活性。它可以指向绝对路径,也可以指向相对路径;目标可以是文件系统中几乎任何位置:一个文件、一个目录、另一个链接,甚至是一个当前并不存在的位置。程序就可以沿着软连接跨到其他分支,甚至绕回已经经过的位置。此时,文件系统的访问关系就不再只是一棵树,而是一张有向图。
在日常软件中,应用可以保留固定的路径入口,通过修改链接目标,实现切换版本或运行环境。这个过程通常由操作系统在后台完成,使用者不必手动追踪每一次转向。因此,当程序要判断文件类型、整理路径或限制访问范围时,就得弄清它究竟在检查链接本身,还是链接指向的目标。
02
这是链接,文件,还是文件夹?
假设一个软链接指向某个文件夹,问它“是不是文件夹”,答案可能是“是”;再问它“是不是软链接”,答案也可能是“是”。这两个答案检查的是不同对象:前者跟随链接查看目标,后者识别链接本身。软链接的易混淆性远不止于此,本研究讨论了文件类型识别中的四类易混淆场景。
第一类是藏在路径中间的链接。lstat()常被用来检查软链接本身,但“不跟随链接”只针对路径的最后一段。只看最终路径结果,就会漏掉中间经过软链接时的判断。
第二类是在链接后加上 /. 。当软链接link指向目录时,link/.最后检查的是目标目录中的 . ,一些接口因而返回目录类型,而不是将这条路径识别为软链接。看似只多了两个字符,不同接口给出的类型判断却可能不同。
第三类是在链接后加上 / 。链接指向目录时,许多接口会返回目标目录的类型;链接指向普通文件时,有的接口会报错,有的则仍返回普通文件类型。由此可见,不同语言对于路径的异常状态的判断也有区别。
第四类是悬空软链接:软链接指向不存在的位置。悬空软链接可以直接调用系统接口创建,或者是先建立软链接后删除指向的文件。此时,不同接口行为也存在细微差别。这种悬空软链接与软链接的条件竞争类漏洞密切相关。
03
同一条路径,不同语言,不同结果
▲该图表示不同编程语言对路径解析结果不同,但是同种语言中不同接口实现亦存在差异,可能出现同一语言不同接口结果不同的情况。图中语言标注仅供参考,不代表所有接口实现。
对于含有软链接的路径,不同语言的解析顺序也有所不同。这甚至可能导致同一个路径字符串,在不同编程语言的接口中对应完全不同的物理位置。论文考察了10种语言中的34个路径变换接口,其中8个仅进行相对路径与绝对路径之间的基本转换,其余接口的路径归一化策略可以归为四类,但难以从函数名称上看出区别。
第一类是直接调用系统接口,按照路径从左到右逐段解析。路径中间若出现不存在的位置,这类接口会直接报错。主要例子包括Java的Path.toRealPath()和Rust的std::fs::canonicalize()。
第二类是先化简路径字符串,再调用系统接口解析剩余部分。Node.js的普通 fs.realpathSync()就采用这种方式。它会先把路径中 link/.. 的 .. 处理抵消,会导致原来的链接还没被解析就已经消失。于是,同样的输入,第一类接口会到达上一级目录,这一类停留在了当前目录。
第三类是在用户态自行逐个解析软链接,通过系统调用readlink()读取链接目标,再递归向下解析,直到路径中没有软链接。操作系统对软链接指向软链接这种递归行为有深度限制,Linux中一般为40,以避免软链接成环的情况。而这种实现能够解析超过系统链接深度上限的路径。Python的os.path.realpath()为这类实现的代表。
第四类只处理路径字符串,不解析软链接。它们只会整理 . 、 .. 等路径片段,典型函数包括Java的Path.normalize()、Go的path.Clean()。
04
软链接问题在Agent中被放大
我们选择解压缩作为主要验证场景。压缩包既能携带普通文件,也能记录软链接,路径检查中的疏漏就可能在这个过程中被触发。论文共收集85个项目,最终发现16个任意文件写入漏洞,另有2个任意文件读取漏洞。
论文进一步发现在多个实际场景场景中都会受到软链接问题影响:操作系统自带工具、物联网设备工具包、数据集加载,以及Web应用的上传服务等。从软件供应链角度来看,这种攻击极其隐蔽,甚至只要双击就可自动触发。用户只调用一次功能接口,底层库就会自动完成解压和写入,恶意软链接随之被还原。上层应用不知晓依赖库处理细节,却会继承底层解压库对路径的判断行为,使一次普通的数据处理操作演变成越界写入,并可能引发代码自动执行。
到了Agent场景,软链接相关的威胁又被连续的文件操作放大。Agent通过工具读取资料、修改文件,模型通常接收的是工具返回的路径、文件内容和执行状态,未必能看到操作过程中经过了哪些软链接;不同工具又可能使用不同语言和依赖库,继承各自的路径解析方式。如果模型把一次存在偏差的返回结果当作后续决策的依据,就可能继续读取或修改错误的目标。现在已经有大量此类Agent漏洞的报告。
当我们凝视夜空之时,我们想象着宇宙之中的虫洞正连接着时间与空间。当我们编写一行行代码时,文件系统中的“虫洞”也让我们可以灵活穿梭于各个位置。希望我们都可以像《星际穿越》中“永恒号”飞船穿越虫洞一样,最终达到那颗合适的星球。
作者介绍
Authors
刘永横
复旦大学计算与智能创新学院24级直博生,入选复旦大学第四期卓博计划。主要研究方向为软件供应链安全、Agent系统安全、AI驱动漏洞治理等。在USENIX Security、ASE等网络安全和软件工程国际顶级会议上发表多篇论文。
张磊
复旦大学副研究员,主要研究方向为漏洞挖掘与治理,主持国家重点研发计划子课题、国家自然科学基金青年基金、上海市人民政府决策咨询项目等,在IEEE S&P、ACM CCS等网络安全顶会上发表论文十余篇,获上海市科技发明一等奖、上海CCF科学技术一等奖,并获得2022年USENIX Security杰出论文奖(国内首篇)、2024 ACM FSE杰出论文奖等。研究成果目前在中国电信、中远海运、支付宝、vivo、OPPO等多个行业头部厂商落地应用。
供稿:刘永横
责编:董佳仪
审核:洪赓、张琬琪、王婧
复旦白泽战队
一个有情怀的安全团队
还没有关注复旦白泽战队?
公众号、小红书搜索:复旦白泽战队也能找到我们哦~
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论