故事起因
一个和往常一样的下午,听着音乐,敲着代码。突然客户群里报案说系统好像挂了,好多请求都报错。看到消息的我,不慌不忙在客户群里回复了一句"收到,我看看",就连上了客户的服务器,查看日志。
日志里大量请求在报错,有报类未定义的,有报类文件找不到的,还有一些见都没见过的报错。想着最近也没上什么新代码啊,再说了,改出的新 bug 也不至于导致这么多问题吧,所以打算看看当前的 jar 是什么时候上线的。
进入程序部署的目录一看,好家伙,当前路径下文件数为 0。程序包,启动脚本,版本备份,配置文件统统不见了。
由于我们一般都是远程连接到客户的电脑上的,在问题发生之前,组内的人都在忙其他的事,于是就把锅甩给了客户自己,是不是你们那边的人误删了呀。客户也很无奈,让我查查能不能找到操作记录。
一番检索下来,发现只能查看当前登录用户的操作记录,其实也有办法查看所有用户的记录,只是需要提前做监控。
这次问题就以我方重新部署程序而告终。确实摸不着头脑,第一次碰见这样的问题。
故事再来就是事故
就当大家都以为只是谁误删导致的事故时。事故,它又来了。
同样的一个平常的下午,客户又在群里报案,系统好像又挂了,好多请求都报错。我回以一个同样的答复,又开始看日志,嚯,和上次一样的报错。再到程序部署目录一看,又被删了。
这次用户也恼了,究竟是谁在恶作剧。然后提议改系统登录密码,能不能找到操作记录等等。
我只是无奈得回复,我先恢复现场再说吧。
恢复之后,觉得同样的事情发生两次,是恶作剧或者误删的可能性也太小了,会不会是程序删的,或者是什么脚本删掉。
于是想起之前注意到,被删除的目录的最后修改时间为 16:37, 然后就到日志里看对应时间的系统行为。结果,在这个时间点之前,果真有一个删除文件的操作。这个接口接收前端传来的一个文件路径,然后删除服务器上这个路径下保存的文件。16:37 的这次请求里,前端传来的路径是一个空字符……
功能背景
系统需要实现一个公共盘功能,具有对文件基本的 CRUD 操作。具体实现是,将公共空间挂载到 AP 服务器的目录上,这样程序直接操作本机文件系统就可以实现对公共盘的操作。
其中有一个删除文件的操作,代码大概是这样的:
public void deleteFileByPath(String url) {
Path path = Paths.get(url);
Files.list(path).forEach(f -> {
// 递归删除文件
})
}
这段代码有什么问题吗?就算会递归删除指定的路径,那前端不可能知道后端程序部署的路径的啊。是怎么把部署所在目录下的文件删除的?
于是开始 debug,把空字符串传进来,一步一步。
path.toStirng() => ""
, 没问题;path.toAbsolutePath() => /path/to/user/dir
, 嗯?咋回事,咋就是我本地程序的根目录了呢?
破案了,根本原因找到了。
哭笑不得,赶紧对传入的参数进行校验,规则如下:
- 不能为空
- 不能包含
..
- 必须以指定目录开头,如:
/ftp
于是赶紧发布修复版本。
事后复盘
本次事故的直接原因就是没有对传入的路径做任何的校验,更别说是否是安全路径的校验了。严重点,可以被传入系统根目录或者带 ..
的上级目录,而导致系统被删除,那就真的是 rm -rf /
从删库到跑路了。
线上已经修复了,相关开发者也沟通过了,就要看看为什么 Paths.get("")
能够返回当前的运行目录,这不是很不合理吗,是不是设计失误?
翻翻代码
public static Path get(String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
可以看到,获取到默认的 fileSystem 类,通过 fs 的 getPath 获得路径。兵分两路:
- getPath
Mac 上找到 FileSystem
的实现类 UnixFileSystem
, getPath
的实现如下:
@Override
public final Path getPath(String first, String... more) {
String path;
if (more.length == 0) {
path = first;
} else {
StringBuilder sb = new StringBuilder();
sb.append(first);
for (String segment: more) {
if (segment.length() > 0) {
if (sb.length() > 0)
sb.append('/');
sb.append(segment);
}
}
path = sb.toString();
}
return new UnixPath(this, path);
}
可以看到,当执行 Paths.get("")
时,这里的 path 变量实际的值依旧是空字符串。剩下的就是 UnixPath 类实例化时的一些细节,看下来和程序根目录没啥关系。
- FileSystems.getDefault()
于是又在 UnixFileSystem 实现类里发现了这个:
private static final String USER_DIR = "user.dir";
private final UnixFileSystem theFileSystem;
public UnixFileSystemProvider() {
String userDir = System.getProperty(USER_DIR);
theFileSystem = newFileSystem(userDir);
}
@Override
public final FileSystem getFileSystem(URI uri) {
checkUri(uri);
return theFileSystem;
}
真相大白啦。
标签:String,删除,后果,路径,校验,参数,path,sb,报错 From: https://www.cnblogs.com/xiawanxw/p/17204997.html