1

From "A Case for Oxidation: The Rust Programming Language" Sergio Benitez says,

Here is a static file server written in Rocket. It is exactly four lines and it guaranteed to not be vulnerable to directory traversal attacks.

Those four lines are:

#[get("/<path..>")]
fn files(path: PathBuf) -> Option<NamedFile> {
    NamedFile::open(Path::new("static/").join(path)).ok()
}

The bottom of this slide says,

  • FromParam* implementation for PathBuf verifies path safety

I get how a type can guarantee safety by validating input (in the same sense that any object can in a constructor or how the input to a function can be wrapped with a validating function.

dangerousThing(validateSafety(input))

Many languages provide this. I also understand how you can make this simpler by putting it into a constructor for a type or class,

class Path {
  constructor(path) { this._path = validateSafety(path) }
}

But I'm confused at what (if anything) Rust is doing differently here. Is there anything more to this?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468

2 Answers2

3

PathBuf provides no such guarantees. It cannot because there's no concept of "directory traversal attacks" in all domains where a PathBuf is used.

What the author means is that the implementation of FromSegments for PathBuf performs the traversal attack check and never calls the handler if it fails.

FromSegments allows for failure cases by returning a Result:

pub trait FromSegments<'a>: Sized {
    type Error: Debug;
    fn from_segments(segments: Segments<'a>) -> Result<Self, Self::Error>;
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • So it's just a constructor for a type that takes in input, checks for `../` (or a parent-notation), and fails to create the type in runtime if one is detected? – Evan Carroll May 29 '19 at 19:06
1

We can iterate over parts of path with components(), so we can check .. usage like shown as below :

let p = PathBuf::from_str("/tmp/../etc/password").unwrap();
if p.components().into_iter().any(|x| x == Component::ParentDir) {
    return Err("directory traversal");
}
Mesut Tasci
  • 2,970
  • 1
  • 30
  • 36
  • 1. You don't need .into_iter(), `Components` is already an iterator. 2. While this example makes sense in isolation, it doesn't really address the common case where are joining relative path components to some base dir, because `.push()` replaces the path on joining another absolute path. Instead, finish constructing the path, canonicalise (e.g. with `fs::canonicalize`), and then check you are still inside your base directory. – Chris Down May 11 '23 at 16:47