diff options
| author | John Turner <jturner.usa@gmail.com> | 2026-03-05 20:23:41 -0500 |
|---|---|---|
| committer | John Turner <jturner.usa@gmail.com> | 2026-03-05 20:23:58 -0500 |
| commit | fb2c9f640dbb493f7d8de16498bf673a82e019a4 (patch) | |
| tree | 3f03216a68afe0b560df2c0e96c40fdb2c012d58 /src | |
| parent | f3d6bceeffa043e7cbc626926ce75562d26fda78 (diff) | |
| download | httpd-fb2c9f640dbb493f7d8de16498bf673a82e019a4.tar.gz | |
support traversing paths upwards but not past the server root
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 63 |
1 files changed, 54 insertions, 9 deletions
diff --git a/src/main.rs b/src/main.rs index b422f47..401af02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,6 +78,15 @@ enum InitLuaError { EvalConfig(mlua::Error), } +#[derive(Debug, thiserror::Error)] +enum JoinPathError { + #[error("unsafe path")] + Unsafe, + + #[error("io error: {0}")] + Io(#[from] std::io::Error), +} + async fn handle(handlers: Table, request: Request) -> Result<Response, HandleError> { let method = request.method().to_string(); @@ -128,15 +137,36 @@ async fn response(handlers: Table, stream: TcpStream) -> Result<(), ResponseErro Ok(()) } -fn join_path(root: &Path, rest: &Path) -> Result<PathBuf, std::io::Error> { - root.components() - .chain( - rest.components() - .filter(|p| !matches!(p, Component::RootDir)), - ) - .filter(|p| !matches!(p, Component::CurDir | Component::ParentDir)) - .collect::<PathBuf>() - .canonicalize() +fn join_path<A: AsRef<Path>, B: AsRef<Path>>(root: A, rest: B) -> Result<PathBuf, JoinPathError> { + let mut pathbuf = root.as_ref().to_path_buf(); + + for component in rest.as_ref().components() { + match component { + Component::Normal(path) => { + pathbuf.push(path); + } + Component::ParentDir => { + pathbuf.pop(); + + if !pathbuf.starts_with(root.as_ref()) { + return Err(JoinPathError::Unsafe); + } + } + _ => continue, + } + } + + let canon = match pathbuf.canonicalize() { + Ok(canon) => canon, + Err(e) if matches!(e.kind(), std::io::ErrorKind::NotFound) => pathbuf, + Err(e) => return Err(JoinPathError::Io(e)), + }; + + if !canon.starts_with(root.as_ref()) { + return Err(JoinPathError::Unsafe); + } + + Ok(canon) } fn init_lua(lua: Lua) -> Result<(), InitLuaError> { @@ -214,3 +244,18 @@ async fn main() -> ExitCode { }); } } + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test_path_traversal() { + let root = "/var/www"; + let get = "/../../bin/sh"; + let joined = join_path(root, get); + + assert!(matches!(joined, Err(JoinPathError::Unsafe))); + } +} |
