Finished /upload endpoint

This commit is contained in:
Random936 2024-12-23 17:47:33 -08:00
parent 0b2a4f130e
commit 7570739ba4
5 changed files with 103 additions and 8 deletions

40
Cargo.lock generated
View File

@ -114,6 +114,7 @@ dependencies = [
"matchit", "matchit",
"memchr", "memchr",
"mime", "mime",
"multer",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustversion", "rustversion",
@ -269,6 +270,15 @@ dependencies = [
"winapi", "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]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -572,6 +582,23 @@ dependencies = [
"windows-sys", "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]] [[package]]
name = "object" name = "object"
version = "0.36.4" version = "0.36.4"
@ -830,6 +857,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"
@ -1032,6 +1065,7 @@ dependencies = [
"serde", "serde",
"serde_yaml", "serde_yaml",
"tokio", "tokio",
"tokio-util",
"tower-http", "tower-http",
] ]
@ -1041,6 +1075,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.9.0+wasi-snapshot-preview1" version = "0.9.0+wasi-snapshot-preview1"

View File

@ -6,11 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
axum = "0.7.6" axum = { version = "0.7.6", features = ["multipart"] }
clap = { version = "4.5.18", features = ["derive"] } clap = { version = "4.5.18", features = ["derive"] }
expanduser = "1.2.2" expanduser = "1.2.2"
if-addrs = "0.13.3" if-addrs = "0.13.3"
serde = { version = "1.0.216", features = ["derive"] } serde = { version = "1.0.216", features = ["derive"] }
serde_yaml = "0.9.34" serde_yaml = "0.9.34"
tokio = { version = "1.40.0", features = ["full"] } tokio = { version = "1.40.0", features = ["full"] }
tokio-util = "0.7.13"
tower-http = { version = "0.6.2", features = ["fs", "trace"] } tower-http = { version = "0.6.2", features = ["fs", "trace"] }

View File

@ -1,5 +1,5 @@
use std::{io, fs}; use std::{io, fs};
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use std::collections::HashMap; use std::collections::HashMap;
use expanduser::expanduser; use expanduser::expanduser;
@ -12,8 +12,8 @@ pub fn load_config() -> Config {
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Config { pub struct Config {
download_path: PathBuf, download_path: String,
upload_path: PathBuf, upload_path: String,
pub web_port: u16, pub web_port: u16,
pub shell_port: u16, pub shell_port: u16,
shells: HashMap<String, String> shells: HashMap<String, String>
@ -32,11 +32,11 @@ impl Config {
} }
pub fn get_download_path(&self) -> PathBuf { pub fn get_download_path(&self) -> PathBuf {
self.download_path.clone() expanduser(self.download_path.clone()).unwrap()
} }
pub fn get_upload_path(&self) -> PathBuf { pub fn get_upload_path(&self) -> PathBuf {
self.upload_path.clone() expanduser(self.upload_path.clone()).unwrap()
} }
pub fn get_shell<S: Into<String>>(&self, key: S) -> Option<String> { pub fn get_shell<S: Into<String>>(&self, key: S) -> Option<String> {

View File

@ -6,7 +6,7 @@ use tokio::net::TcpListener;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use axum::{ use axum::{
middleware, middleware,
routing::get, routing::{get, post},
Router Router
}; };
@ -15,6 +15,7 @@ mod print_dir;
mod config; mod config;
mod shells; mod shells;
mod logging; mod logging;
mod upload;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -30,7 +31,7 @@ async fn main() {
print_dir::print_interface(&args.interface, &port, &args.directory); print_dir::print_interface(&args.interface, &port, &args.directory);
let app = Router::new() let app = Router::new()
//.route("/upload", post(upload_handler)) .route("/upload", post(upload::upload_handler))
.route("/shells/:shell", get(shells::shells_handler)) .route("/shells/:shell", get(shells::shells_handler))
.nest_service("/download", ServeDir::new(conf.get_download_path())) .nest_service("/download", ServeDir::new(conf.get_download_path()))
.nest_service("/", ServeDir::new(cwd)) .nest_service("/", ServeDir::new(cwd))

View File

@ -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())
}