From bd7984484977f52cbe39bf696f81fa6c1c073a6b Mon Sep 17 00:00:00 2001 From: lbl <1791778603@qq.com> Date: Thu, 20 Aug 2026 22:16:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Web=20=E9=9D=99=E6=80=81?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E7=9B=AE=E5=BD=95=E7=A9=BF=E8=B6=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit static_handler 此前直接 join 未归一化的 URI 路径, curl --path-as-is .../../../ 可读取进程权限内任意文件。 改为逐组件校验:仅允许 Normal/CurDir 组件,拒绝 ..、根路径、盘符。 补充路径校验单元测试。 --- vnt-web/src/service_http.rs | 53 ++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/vnt-web/src/service_http.rs b/vnt-web/src/service_http.rs index 21a546e..2c23a08 100644 --- a/vnt-web/src/service_http.rs +++ b/vnt-web/src/service_http.rs @@ -444,12 +444,28 @@ fn build_headers_for_path(path: &str) -> HeaderMap { ); headers } +/// 将请求路径安全地映射到 static 目录内。 +/// 逐组件校验,拒绝 `..`、根路径、盘符等任何可能逃逸出 static 的路径。 +fn resolve_static_path(path: &str) -> Option { + let mut local_path = PathBuf::from("static"); + for component in Path::new(path).components() { + match component { + std::path::Component::Normal(part) => local_path.push(part), + std::path::Component::CurDir => {} + _ => return None, + } + } + Some(local_path) +} + async fn static_handler(uri: Uri) -> impl IntoResponse { let path = uri.path().trim_start_matches('/'); let path = if path.is_empty() { "index.html" } else { path }; // 先尝试从本地文件读取 - let local_path = Path::new("static").join(path); + let Some(local_path) = resolve_static_path(path) else { + return (StatusCode::NOT_FOUND, "404 Not Found").into_response(); + }; if local_path.is_file() && let Ok(content) = tokio::fs::read(&local_path).await { @@ -1124,3 +1140,38 @@ async fn get_routes(State(state): State) -> Json