这两天尝试自己对着通告和commit自己分析,看不懂之后上网搜搜参考, 看到这篇,终于理解了。


在此之前需要先知道:

  1. windows下,new File("a.jsp").exists(),如果当前目录有a.Jsp而没有a.jsp,也是返回true的。
  2. windows下,new FileInputStream("a.Jsp").read(),如果当前目录有a.Jsp而没有a.jsp,能够读取a.Jsp的内容。

当我们访问/a.jsp时候,tomcat内部是怎么找到a.jsp这个文件的?

断点打在DirResourceSet#getResource

image-20241226132215829.png

此时的调用栈

image-20241226132138188.png

仔细看当前getResource方法的逻辑后,就知道,file方法不能返回null,否则返回的是EmptyResource。

进入file方法,他首先new一个File对象,然后:

  1. 是否以斜杠结尾,是否是个文件
  2. 是否可读
  3. getRoot().getAllowLinking()是否为true
  4. 是否是合法windows文件名
  5. 获取abs path 和 can path,看是否equals。

以上几个步骤,都必须为true,才能够返回一个File对象。

关键的是第5步,如何获取can path?调用的是file.getCanonicalPath()。

跟进,会进入到WinNTFileSystem#canonicalize

关键代码

if (!useCanonCaches) {
return canonicalize0(path);
} else {
String res = cache.get(path);
if (res == null) {
String dir = null;
String resDir = null;
if (useCanonPrefixCache) {
dir = parentOrNull(path);
if (dir != null) {
resDir = prefixCache.get(dir);
if (resDir != null) {
/*
* Hit only in prefix cache; full path is canonical,
* but we need to get the canonical name of the file
* in this directory to get the appropriate
* capitalization
*/
String filename = path.substring(1 + dir.length());
res = canonicalizeWithPrefix(resDir, filename);
cache.put(dir + File.separatorChar + filename, res);
}
}
}
if (res == null) {
res = canonicalize0(path);
cache.put(path, res);
if (useCanonPrefixCache && dir != null) {
resDir = parentOrNull(res);
if (resDir != null) {
File f = new File(res);
if (f.exists() && !f.isDirectory()) {
prefixCache.put(dir, resDir);
}
}
}
}
}
return res;
}

总结file.getCanonicalPath 获取can path的步骤

  1. 从缓存里取res,若取到,则直接返回 res
  2. 若没取到,则从prefixCache取缓存的路径,然后和文件名拼在一起作为 res(这一步不重要)
  3. 若还是没取到,调用canonicalize0,结果为res,然后把res放入缓存

canonicalize0怎么计算can path

windows下,

若存在a.Jsp这个文件,canonicalize0(“a.jsp”) = “a.Jsp”

若不存在a.Jsp这个文件,canonicalize0(“a.jsp”) = “a.jsp”


总结一下,查找文件a.jsp时,调用getResource,getResource会调用file方法,file方法必须返回File(“a.jsp”)对象。

file方法返回File(“a.jsp”)对象的条件是,File(“a.jsp”)的can path和abs path相同。

abs path默认是a.jsp。

can path的获取,先从缓存里找,若找不到,则调用canonicalize0找。

windows下,

若存在a.Jsp这个文件,canonicalize0(“a.jsp”) = “a.Jsp”

若不存在a.Jsp这个文件,canonicalize0(“a.jsp”) = “a.jsp”

所以利用思路:

先发delete请求,删除a.Jsp文件。

第一步,GET /a.jsp,加入缓存,令 a.jsp -> a.jsp

第二步,PUT /a.Jsp,写入恶意文件

第三步,GET /a.jsp,解析恶意文件。(要get多次,后面几次才会解析,超过30秒后就再解析不到了。)