前言
目录穿越blog编写,记录一些逻辑和危险方法和现有历史漏洞,记忆总结的东西比较多,不难,但是值得一写,毕竟好多白盒漏洞都是这方面的问题
文件路径解析实现绕过
文件操作功能
新建文件 /上传文件功能
例2 前端接受了文件名且未考虑文件名中的../直接路径与文件名则存在目录穿越漏洞。 可构造文件名进行利用../filename.txt覆盖任意文件
读取文件 - 下载文件功能。
例3 前端接受了文件名且未考虑文件名中的../直接路径与文件名则存在目录穿越漏洞。 可构造文件名进行利用../filename.txt读取任意文件
修改文件 - 编辑文件功能。
- 三个功能的共同点:文件名 / 路径由前端传入,后端直接拼到目录后面;只要过滤是「字符串式」的就可能被绕
“过滤式”路径处理
「过滤式」指只在字符串层做检查:replace 删字符、正则匹配、解码后比对,不做路径规范化。文件 API 和容器事后还会再解析一次,两者的差就是绕过空间
绕过方法按「差异点」分六类:
绝对路径(不用 ..) ✓ /etc/passwd、\Windows\win.ini、//etc/passwd、\host\share\x(UNC)——子路径是绝对路径时,父目录base 直接被丢弃。
盘符相对路径(Windows 特有) C:..\..\x —— c:foo 是”盘符相对”,不是绝对也不是相对,很多过滤只拦 ^[A-Za-z]: 后面的第一层。
拼接函数把 base 丢掉(语言语义差异) 实测:
代码块
Python os.path.join(‘/app’, ‘/etc/passwd’) -> /etc/passwd ← 重置,危险
Python Path(‘/app’) / ‘/etc/passwd’ -> /etc/passwd ← 重置,危险
Node path.join(‘/app’, ‘/etc/passwd’) -> /app/etc/passwd ← 不重置
Node path.resolve(‘/app’, ‘/etc/passwd’) -> D:\etc\passwd ← 重置,危险
同一段 BASE + name 的代码,语言/函数选错就是一个洞。
规范化函数自身的语义坑 Clean/normpath/normalize/realpath 各不一样:normpath 不解析符号链接、realpath 解析、Go 的 Clean 在 Windows 上把 .\c: 变成 c:(CVE-2022-29804)。
前缀比较(startsWith) /data/storage 与 /data/storage2 前缀相同;大小写、缺末尾分隔符都能绕。
符号链接(不写 .. 也能出界) 预置软链(部署/容器常见)、解压时创建软链(zip/tar entry type=symlink)、软链 + 后续写入的两步穿越。
TOCTOU(检查完再打开) realpath 检查通过后把文件换成软链,再 open —— 可预测文件名(/tmp/xxx)场景经典。
服务器把 URL 当路径(映射式) nginx alias、Java ServletContext.getRealPath()、Python SimpleHTTPRequestHandler.translate_path()、PHP $_SERVER[‘PATH_TRANSLATED’]、IIS 的 URL→路径映射。
容器的 URL 语义被带进路径 Tomcat 剥 ; 路径参数后的 ..;/、%2F 处理策略(Apache AllowEncodedSlashes、Tomcat encodedSolidusHandling)—— 这类只在”URL path 来源 + 容器参与”时成立。
同一请求的多形态解析 nginx $uri(已解码+规范化)vs $request_uri(原样)、Java getRequestURI() vs getServletPath()/getPathInfo()、WAF 解一次框架再解一次。
归档 entry(Zip Slip 之外) Tar Slip(tar entry 里的 ..、绝对路径 entry)、symlink/hardlink entry、Unicode Path Extra Field(0x7075)与 central directory 名字不一致、嵌套压缩递归解压。
文件名/外部数据被当路径 上传的 filename=../../x、i18n 的 lang=../../etc/passwd(拼 messages_{lang}.properties)、模板名、插件 id(Grafana CVE-2021-43798)。
编码变体 ✓ %2e、%252e、overlong、全角 —— 就是你现在「过滤式」那节写的内容,本质属这一类。
前后矛盾的,这种也很常见
- 删除重组(一次性 replace 的锅):
....//、...//、....//./、....//....//、.././、....\/、..\/ - 编码差(解码次数不同):
..%2f、%2e%2e%2f、%2e%2e%5c、%2e%2e/、.%2e/、%252e%252e%252f、..%255c - 畸形编码(宽松解码器):overlong UTF-8、全角字符、IIS 的
%u编码 - 路径参数(Servlet 的
;):..;/、..;/..;/、%3b - 后缀 / 数据流(扩展名检查 ≠ 文件访问):
%00截断、::$DATA、尾随点/空格、8.3 短名、大小写 - 拼接与规范化:
path.join/normalize把噪声折叠成真穿越;startsWith前缀比较被绕过
overlong UTF-8(重点记它的范围和限制):
- 原理:UTF-8 早期允许用 2~4 字节表示本可 1 字节的 ASCII(overlong),宽松解码器折叠后
%c0%ae变成.、%c0%af变成/;RFC 3629 起解码器必须拒绝 - 种类:两字节
%c0%ae/%c0%af/%c1%9c;三字节%e0%80%ae/%e0%80%af;四字节%f0%80%80%ae - 使用范围:只对「宽松解码 + 会做 ASCII 折叠」的旧组件有效 —— 旧 Tomcat 4.1/5.5/6.0 +
allowLinking="true"+URIEncoding="UTF-8"(CVE-2008-2938)、老 IIS 的%c0%af - 限制:现代 Java、Python 3、Node 都严格解码,直接 400;链路上没有「折叠」这一步时它只是普通字节串,等于没发
- 全角字符(同类手法):
%ef%bc%8e是全角点、%ef%bc%8f是全角斜杠,要等下游做 NFKC 归一化才会折成.和/;没有 NFKC 步骤就不生效
还有就是“前后矛盾”导致绕过,这种还挺常见的
https://fushuling.com/index.php/2025/10/21/%e8%bf%87%e5%ba%a6%e9%98%b2%e5%be%a1%e5%b8%a6%e6%9d%a5%e7%9a%84%e8%b7%af%e5%be%84%e9%81%8d%e5%8e%86%e7%bb%95%e8%bf%87/ replace绕过一些案例
os.ReadFile() 文件读取函数
path.join 参数是传参
个文件的path = strings.ReplaceAll(path, “../“, “”)。函数
https://blog.51cto.com/u_15847702/5808324 - Vite(CVE-2025-30208):dev server 的
server.fs.deny能被 URL 尾部?raw??/?import&raw??绕过,读到 allow list 外的任意文件;成因是”尾部?在多处被剥掉、查询串正则却没算到”;只有 dev server 暴露到网络(--host)时才受影响 - Apache httpd 2.4.49 / 2.4.50(CVE-2021-41773 / CVE-2021-42013):
/.%2e/单层编码穿越,直接读etc/passwd;开了mod_cgi还能进一步 RCE - Go 标准库
path/filepath.Clean(CVE-2022-29804):Windows 上Clean(".\c:")会返回c:,把非法路径”规范化”成合法绝对路径,进而目录穿越;影响 Go < 1.17.11 与 < 1.18.3,仅 Windows —— 规范化函数本身就有语义坑
I Doc View 在线文档 文件上传
上传压缩文件包功能
Zip Slip
Zip Slip 是一种通过构造恶意压缩包实现目录穿越的漏洞,攻击者在压缩包文件名中植入 ../ 等路径序列,若程序解压时未校验文件路径,文件即可被写到目标目录之外,甚至覆盖系统关键文件或执行恶意代码
- entry 名:zip/tar 都是用 entry 名拼解压路径,
../../直接落到目标目录外 - 绝对路径 entry:
/etc/cron.d/x、C:\...(有的解压库直接尊重绝对路径) - 符号链接 entry:zip 里塞个 symlink 指向
/etc/passwd,再解压一个普通文件到该链接 - Windows 变体:entry 名用
..\或%2e%2e%5c - 修复:解压每个文件后
getCanonicalPath()必须仍落在目标目录内
漏洞写法 vs 修复(Java 为例):
1 | // ✗ entry 名直接拼 |
- 其它语言同一个坑:JS
path.join(target, entry.path)、Gofilepath.Join(dest, header.Name)、.NETPath.Combine(dest, entry.Key) - 两个细节:
startsWith(dir)要写成startsWith(dir + File.separator)(否则/uploads和/uploads-evil前缀相同);getCanonicalPath()会解析符号链接,normalize()不会
一些案例
- kkFileView v4.2.0~v4.4.0-beta:上传含
../../…/tmp/xentry 的 zip,预览时解压 → 任意文件写入 → 覆盖可被调用的文件 → RCE - Zip Slip 原始披露(Snyk 2018):
unzipper< 0.8.13、adm-zip< 0.4.9、plexus-archiver< 3.6.0、zt-zip< 1.13、zip4j(全版本)、DotNetZip< 1.11.0、SharpCompress< 0.21.0、mholt/archiver(全版本)、commons-compress< 1.17;产品侧 Apache Ant < 1.9.12、Storm < 1.0.7、Spring Integration Zip < 1.0.1 - 同类但不是压缩包:Flink CVE-2020-17518(上传 jar 的文件名穿越)、Struts2 CVE-2023-50164 / CVE-2024-53677(上传路径穿越)
URL请求路径解析实现越权
上面是文件定义的目录解析导致的文件读取上传等漏洞,我们可以把视角拉到中间件,web中间件的路由解析和安全限制也有可能出现,而且这种漏洞更加复杂。
上面的代码要分析的是对要处理文件路径处理不当,接下来的是对请求路径处理不当,代码量差距就不小
在CVE-2021-34429中,Jetty 就有过使用%u002e进行绕过,在Jetty9.4.37版本中,为了符合RFC3986中的规范,选择性地支持可能有歧义解释的URI,默认模式允许URL编码,这也导致我们可以使用UTF-16字符来绕过路径穿越的防护
CVE-2025-41242 Spring Framework MVC(spring-webmvc)