前言

目录穿越blog编写,记录一些逻辑和危险方法和现有历史漏洞,记忆总结的东西比较多,不难,但是值得一写,毕竟好多白盒漏洞都是这方面的问题

文件路径解析实现绕过

文件操作功能

新建文件 /上传文件功能
例2 前端接受了文件名且未考虑文件名中的../直接路径与文件名则存在目录穿越漏洞。 可构造文件名进行利用../filename.txt覆盖任意文件
读取文件 - 下载文件功能。
例3 前端接受了文件名且未考虑文件名中的../直接路径与文件名则存在目录穿越漏洞。 可构造文件名进行利用../filename.txt读取任意文件
修改文件 - 编辑文件功能。

  • 三个功能的共同点:文件名 / 路径由前端传入,后端直接拼到目录后面;只要过滤是「字符串式」的就可能被绕

“过滤式”路径处理

「过滤式」指只在字符串层做检查:replace 删字符、正则匹配、解码后比对,不做路径规范化。文件 API 和容器事后还会再解析一次,两者的差就是绕过空间

绕过方法按「差异点」分六类:
  1. 绝对路径(不用 ..) ✓ /etc/passwd、\Windows\win.ini、//etc/passwd、\host\share\x(UNC)——子路径是绝对路径时,父目录base 直接被丢弃。

  2. 盘符相对路径(Windows 特有) C:..\..\x —— c:foo 是”盘符相对”,不是绝对也不是相对,很多过滤只拦 ^[A-Za-z]: 后面的第一层。

  3. 拼接函数把 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 的代码,语言/函数选错就是一个洞。

  1. 规范化函数自身的语义坑 Clean/normpath/normalize/realpath 各不一样:normpath 不解析符号链接、realpath 解析、Go 的 Clean 在 Windows 上把 .\c: 变成 c:(CVE-2022-29804)。

  2. 前缀比较(startsWith) /data/storage 与 /data/storage2 前缀相同;大小写、缺末尾分隔符都能绕。

  3. 符号链接(不写 .. 也能出界) 预置软链(部署/容器常见)、解压时创建软链(zip/tar entry type=symlink)、软链 + 后续写入的两步穿越。

  4. TOCTOU(检查完再打开) realpath 检查通过后把文件换成软链,再 open —— 可预测文件名(/tmp/xxx)场景经典。

  5. 服务器把 URL 当路径(映射式) nginx alias、Java ServletContext.getRealPath()、Python SimpleHTTPRequestHandler.translate_path()、PHP $_SERVER[‘PATH_TRANSLATED’]、IIS 的 URL→路径映射。

  6. 容器的 URL 语义被带进路径 Tomcat 剥 ; 路径参数后的 ..;/、%2F 处理策略(Apache AllowEncodedSlashes、Tomcat encodedSolidusHandling)—— 这类只在”URL path 来源 + 容器参与”时成立。

  7. 同一请求的多形态解析 nginx $uri(已解码+规范化)vs $request_uri(原样)、Java getRequestURI() vs getServletPath()/getPathInfo()、WAF 解一次框架再解一次。

  8. 归档 entry(Zip Slip 之外) Tar Slip(tar entry 里的 ..、绝对路径 entry)、symlink/hardlink entry、Unicode Path Extra Field(0x7075)与 central directory 名字不一致、嵌套压缩递归解压。

  9. 文件名/外部数据被当路径 上传的 filename=../../x、i18n 的 lang=../../etc/passwd(拼 messages_{lang}.properties)、模板名、插件 id(Grafana CVE-2021-43798)。

  10. 编码变体 ✓ %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
2
3
4
5
6
7
// ✗ entry 名直接拼
File f = new File(destinationDir, e.getName());

// ✓ canonical 化后比较,注意带分隔符
String dir = destinationDir.getCanonicalPath();
String dest = new File(destinationDir, e.getName()).getCanonicalPath();
if (!dest.startsWith(dir + File.separator)) throw new ArchiverException("outside");
  • 其它语言同一个坑:JS path.join(target, entry.path)、Go filepath.Join(dest, header.Name)、.NET Path.Combine(dest, entry.Key)
  • 两个细节:startsWith(dir) 要写成 startsWith(dir + File.separator)(否则 /uploads 和 /uploads-evil 前缀相同);getCanonicalPath() 会解析符号链接,normalize() 不会
一些案例
  • kkFileView v4.2.0~v4.4.0-beta:上传含 ../../…/tmp/x entry 的 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)