PT-2026-6365 · Crates.Io · Salvo
Publicado
2026-01-08
·
Atualizado
2026-01-08
CVSS v3.1
8.8
Alta
| Vetor | AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:L |
Summary
The function
list html generates a file view of a folder without sanitizing the files or folders names, potentially leading to XSS in cases where a website allows access to public files using this feature, allowing anyone to upload a file.Details
The vulnerable snippet of code is the following:
dir.rs
rust
// ... fn list html(...
let mut link = "".to owned();
format!(
r#"<a href="/">{}</a>{}"#,
HOME ICON,
segments
.map(|seg| {
link = format!("{link}/{seg}");
format!("/<a href="{link}">{seg}</a>")
})
.collect::<Vec< >>()
.join("")
)
// ...PoC
Here is the example app we used:
mian.rsrs
use salvo::prelude::*;
use salvo::serve static::StaticDir;
use std::path::PathBuf;
use tokio::fs;
const INDEX HTML: &str = r#"<!doctype html>
<html>
<head><meta charset="utf-8"><title>StaticDir PoC</title></head>
<body>
<h2>Upload a file</h2>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<button type="submit">Upload</button>
</form>
<p>Browse uploads:</p>
<ul>
<li><a href="/files">/files</a></li>
<li><a href="/files/">/files/</a></li>
</ul>
</body>
</html>
"#;
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(INDEX HTML));
}
#[handler]
async fn upload(req: &mut Request, res: &mut Response) {
fs::create dir all("uploads").await.expect("create uploads dir");
let form = match req.form data().await {
Ok(v) => v,
Err(e) => {
res.status code(StatusCode::BAD REQUEST);
res.render(Text::Plain(format!("form data parse failed: {e}")));
return;
}
};
let Some(file part) = form.files.get("file") else {
res.status code(StatusCode::BAD REQUEST);
res.render(Text::Plain("missing file field (name="file")"));
return;
};
let original name = file part.name().unwrap or("upload.bin");
let mut dest = PathBuf::from("uploads");
dest.push(original name);
let tmp path = file part.path();
if let Err(e) = fs::copy(tmp path, &dest).await {
res.status code(StatusCode::INTERNAL SERVER ERROR);
res.render(Text::Plain(format!("save failed: {e}")));
return;
}
res.render(Text::Plain(format!(
"Uploaded as: {original name}
Now open: http://127.0.0.1:5800/files/
"
)));
}
#[tokio::main]
async fn main() {
tracing subscriber::fmt().init();
fs::create dir all("uploads").await.expect("create uploads dir");
let router = Router::new()
.get(index)
.push(Router::with path("upload").post(upload))
.push(
Router::with path("files/{**rest path}")
.get(StaticDir::new("uploads").auto list(true)),
);
let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
Server::new(acceptor).serve(router).await;
}Cargo.tomlrs
[package]
name = "poc"
version = "0.1.0"
edition = "2024"
[dependencies]
salvo = { version = "0.85.0", features = ["serve-static"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] }
tracing-subscriber = "0.3"Impact
JavaScript execution, most likely leading to an account takeover, depending on the site's constraint (CSP, etc…).
Correção
XSS
Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾
Enumeração de Fraquezas
Identificadores relacionados
Produtos afetados
Salvo