Skip to content

Commit 28e5904

Browse files
authored
Merge pull request #473 from DCjanus/master
fix: check directory traversal attack without file system visit
2 parents b2ed4d7 + 3a744ea commit 28e5904

File tree

1 file changed

+25
-29
lines changed

1 file changed

+25
-29
lines changed

src/server/serve_dir.rs

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{Body, Endpoint, Request, Response, Result, StatusCode};
44
use async_std::fs::File;
55
use async_std::io::BufReader;
66

7+
use std::ffi::OsStr;
78
use std::path::{Path, PathBuf};
89

910
type BoxFuture<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + 'a + Send>>;
@@ -22,43 +23,38 @@ impl ServeDir {
2223
impl<State> Endpoint<State> for ServeDir {
2324
fn call<'a>(&'a self, req: Request<State>) -> BoxFuture<'a, Result> {
2425
let path = req.uri().path();
25-
let path = path.replacen(&self.prefix, "", 1);
26+
let path = path.trim_start_matches(&self.prefix);
2627
let path = path.trim_start_matches('/');
27-
let mut dir = self.dir.clone();
28+
let mut file_path = self.dir.clone();
2829
for p in Path::new(path) {
29-
dir.push(&p);
30+
if p == OsStr::new(".") {
31+
continue;
32+
} else if p == OsStr::new("..") {
33+
file_path.pop();
34+
} else {
35+
file_path.push(&p);
36+
}
3037
}
31-
log::info!("Requested file: {:?}", dir);
38+
39+
log::info!("Requested file: {:?}", file_path);
3240

3341
Box::pin(async move {
34-
let file = match async_std::fs::canonicalize(&dir).await {
35-
Err(_) => {
36-
// This needs to return the same status code as the
37-
// unauthorized case below to ensure we don't leak
38-
// information of which files exist to adversaries.
39-
log::warn!("File not found: {:?}", dir);
40-
return Ok(Response::new(StatusCode::NotFound));
41-
}
42-
Ok(mut file_path) => {
43-
// Verify this is a sub-path of the original dir.
44-
let mut file_iter = (&mut file_path).iter();
45-
if !dir.iter().all(|lhs| Some(lhs) == file_iter.next()) {
46-
// This needs to return the same status code as the
47-
// 404 case above to ensure we don't leak
48-
// information about the local fs to adversaries.
49-
log::warn!("Unauthorized attempt to read: {:?}", file_path);
50-
return Ok(Response::new(StatusCode::NotFound));
51-
}
42+
if !file_path.starts_with(&self.dir) {
43+
log::warn!("Unauthorized attempt to read: {:?}", file_path);
44+
return Ok(Response::new(StatusCode::Forbidden));
45+
}
5246

53-
// Open the file and send back the contents.
54-
match File::open(&file_path).await {
55-
Ok(file) => file,
56-
Err(_) => {
57-
log::warn!("Could not open {:?}", file_path);
58-
return Ok(Response::new(StatusCode::InternalServerError));
59-
}
47+
let file = match File::open(&file_path).await {
48+
Err(error) => {
49+
return if error.kind() == async_std::io::ErrorKind::NotFound {
50+
log::warn!("File not found: {:?}", file_path);
51+
Ok(Response::new(StatusCode::NotFound))
52+
} else {
53+
log::warn!("Could not open {:?}", file_path);
54+
Ok(Response::new(StatusCode::InternalServerError))
6055
}
6156
}
57+
Ok(file) => file,
6258
};
6359

6460
let len = match file.metadata().await {

0 commit comments

Comments
 (0)