From 7570739ba43a16fd3f6fc1d6b277e7099d26cd5b Mon Sep 17 00:00:00 2001 From: Random936 Date: Mon, 23 Dec 2024 17:47:33 -0800 Subject: [PATCH] Finished /upload endpoint --- Cargo.lock | 40 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 ++- src/config.rs | 10 +++++----- src/main.rs | 5 +++-- src/upload.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e20e10..f8d91b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,6 +114,7 @@ dependencies = [ "matchit", "memchr", "mime", + "multer", "percent-encoding", "pin-project-lite", "rustversion", @@ -269,6 +270,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -572,6 +582,23 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + [[package]] name = "object" version = "0.36.4" @@ -830,6 +857,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strsim" version = "0.11.1" @@ -1032,6 +1065,7 @@ dependencies = [ "serde", "serde_yaml", "tokio", + "tokio-util", "tower-http", ] @@ -1041,6 +1075,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index fe06d2c..69db679 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axum = "0.7.6" +axum = { version = "0.7.6", features = ["multipart"] } clap = { version = "4.5.18", features = ["derive"] } expanduser = "1.2.2" if-addrs = "0.13.3" serde = { version = "1.0.216", features = ["derive"] } serde_yaml = "0.9.34" tokio = { version = "1.40.0", features = ["full"] } +tokio-util = "0.7.13" tower-http = { version = "0.6.2", features = ["fs", "trace"] } diff --git a/src/config.rs b/src/config.rs index a8a6c81..682a942 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,5 @@ use std::{io, fs}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::collections::HashMap; use expanduser::expanduser; @@ -12,8 +12,8 @@ pub fn load_config() -> Config { #[derive(Deserialize)] pub struct Config { - download_path: PathBuf, - upload_path: PathBuf, + download_path: String, + upload_path: String, pub web_port: u16, pub shell_port: u16, shells: HashMap @@ -32,11 +32,11 @@ impl Config { } pub fn get_download_path(&self) -> PathBuf { - self.download_path.clone() + expanduser(self.download_path.clone()).unwrap() } pub fn get_upload_path(&self) -> PathBuf { - self.upload_path.clone() + expanduser(self.upload_path.clone()).unwrap() } pub fn get_shell>(&self, key: S) -> Option { diff --git a/src/main.rs b/src/main.rs index 83129f2..731c09c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use tokio::net::TcpListener; use tower_http::services::ServeDir; use axum::{ middleware, - routing::get, + routing::{get, post}, Router }; @@ -15,6 +15,7 @@ mod print_dir; mod config; mod shells; mod logging; +mod upload; #[tokio::main] async fn main() { @@ -30,7 +31,7 @@ async fn main() { print_dir::print_interface(&args.interface, &port, &args.directory); let app = Router::new() - //.route("/upload", post(upload_handler)) + .route("/upload", post(upload::upload_handler)) .route("/shells/:shell", get(shells::shells_handler)) .nest_service("/download", ServeDir::new(conf.get_download_path())) .nest_service("/", ServeDir::new(cwd)) diff --git a/src/upload.rs b/src/upload.rs index e69de29..7ea3e95 100644 --- a/src/upload.rs +++ b/src/upload.rs @@ -0,0 +1,53 @@ +use std::{ + fs::File, + io::{BufWriter, Write}, + path::{Path, Component} +}; +use axum::{ + http::StatusCode, + extract::Multipart, + response::IntoResponse +}; + +use crate::config; + +fn path_is_valid(path: &str) -> bool { + let path = Path::new(path); + let mut components = path.components().peekable(); + if let Some(first) = components.peek() { + if !matches!(first, Component::Normal(_)) { + return false; + } + } + + components.count() == 1 +} + +pub async fn upload_handler(mut multipart: Multipart) -> impl IntoResponse { + + let conf = config::load_config(); + + while let Some(field) = multipart.next_field().await.unwrap() { + let file_name = match field.name() { + Some(file) => file.to_string(), + None => continue + }; + let data = field.bytes().await.unwrap(); + let path = conf.get_upload_path().join(&file_name); + + if !path_is_valid(&file_name) { + return (StatusCode::FORBIDDEN, format!("Directory Traversal: {}", path.display())); + } + + let Ok(file) = File::create(&path) else { + return (StatusCode::FORBIDDEN, format!("Failed to create file: {}", path.display())); + }; + + let mut writer = BufWriter::new(file); + if let Err(e) = writer.write_all(&data) { + return (StatusCode::INTERNAL_SERVER_ERROR, format!("An error occurred when writing to {}: {}", path.display(), e)) + } + } + + (StatusCode::CREATED, "Successfully uploaded file(s).".to_string()) +}