1
0
Fork 0
mirror of https://github.com/mat-1/metasearch2.git synced 2025-08-02 15:26:04 +00:00

csrf and xss fix

thanks @JorianWoltjer <3
This commit is contained in:
mat 2025-07-07 06:25:04 +09:00
parent 7a3e5f04bc
commit ad1fb8a9af
2 changed files with 35 additions and 11 deletions

View file

@ -6,6 +6,7 @@ use axum::{
response::{IntoResponse, Response}, response::{IntoResponse, Response},
Extension, Extension,
}; };
use reqwest::header;
use tracing::error; use tracing::error;
use crate::{config::Config, engines}; use crate::{config::Config, engines};
@ -42,6 +43,9 @@ pub async fn route(
if res.content_length().unwrap_or_default() > max_size { if res.content_length().unwrap_or_default() > max_size {
return (StatusCode::PAYLOAD_TOO_LARGE, "Image too large").into_response(); return (StatusCode::PAYLOAD_TOO_LARGE, "Image too large").into_response();
} }
const ALLOWED_IMAGE_TYPES: &[&str] = &["apng", "avif", "gif", "jpeg", "png", "webp"];
// validate content-type // validate content-type
let content_type = res let content_type = res
.headers() .headers()
@ -49,8 +53,15 @@ pub async fn route(
.and_then(|v| v.to_str().ok()) .and_then(|v| v.to_str().ok())
.unwrap_or_default() .unwrap_or_default()
.to_string(); .to_string();
if !content_type.starts_with("image/") {
return (StatusCode::BAD_REQUEST, "Not an image").into_response(); let Some((base_type, subtype)) = content_type.split_once("/") else {
return (StatusCode::UNSUPPORTED_MEDIA_TYPE, "Invalid Content-Type").into_response();
};
if base_type != "image" {
return (StatusCode::UNSUPPORTED_MEDIA_TYPE, "Not an image").into_response();
}
if !ALLOWED_IMAGE_TYPES.contains(&subtype) {
return (StatusCode::UNSUPPORTED_MEDIA_TYPE, "Image type not allowed").into_response();
} }
let mut image_bytes = Vec::new(); let mut image_bytes = Vec::new();
@ -63,11 +74,10 @@ pub async fn route(
( (
[ [
(axum::http::header::CONTENT_TYPE, content_type), (header::CONTENT_TYPE, content_type),
( (header::CACHE_CONTROL, "public, max-age=31536000".to_owned()),
axum::http::header::CACHE_CONTROL, (header::X_CONTENT_TYPE_OPTIONS, "nosniff".to_owned()),
"public, max-age=31536000".to_owned(), (header::CONTENT_DISPOSITION, "attachment".to_owned()),
),
], ],
image_bytes, image_bytes,
) )

View file

@ -1,6 +1,6 @@
use axum::{ use axum::{
http::{header, StatusCode}, http::{header, HeaderMap, StatusCode},
response::IntoResponse, response::{IntoResponse, Response},
Extension, Form, Extension, Form,
}; };
use axum_extra::extract::{cookie::Cookie, CookieJar}; use axum_extra::extract::{cookie::Cookie, CookieJar};
@ -69,10 +69,24 @@ pub struct Settings {
pub stylesheet_str: String, pub stylesheet_str: String,
} }
pub async fn post(mut jar: CookieJar, Form(settings): Form<Settings>) -> impl IntoResponse { pub async fn post(
headers: HeaderMap,
mut jar: CookieJar,
Form(settings): Form<Settings>,
) -> Response {
let Some(origin) = headers.get("origin").and_then(|h| h.to_str().ok()) else {
return (StatusCode::BAD_REQUEST, "Missing or invalid Origin header").into_response();
};
let Some(host) = headers.get("host").and_then(|h| h.to_str().ok()) else {
return (StatusCode::BAD_REQUEST, "Missing or invalid Host header").into_response();
};
if origin != format!("http://{host}") && origin != format!("https://{host}") {
return (StatusCode::BAD_REQUEST, "Origin does not match Host").into_response();
}
let mut settings_cookie = Cookie::new("settings", serde_json::to_string(&settings).unwrap()); let mut settings_cookie = Cookie::new("settings", serde_json::to_string(&settings).unwrap());
settings_cookie.make_permanent(); settings_cookie.make_permanent();
jar = jar.add(settings_cookie); jar = jar.add(settings_cookie);
(StatusCode::FOUND, [(header::LOCATION, "/settings")], jar) (StatusCode::FOUND, [(header::LOCATION, "/settings")], jar).into_response()
} }