http: verify TLS certs by default + cap the response body — set_verify(NONE) unconditionally let a MITM read/forge the account-registration & captcha traffic (usernames, emails, plaintext passwords) POSTed to operator-configured https URLs; now the connector's cert+hostname check stays on (opt out with http_tls_verify=no), and read_to_end is bounded to 4 MiB so a hostile endpoint can't OOM the worker

This commit is contained in:
Jean Chevronnet 2026-08-19 00:37:50 +00:00
parent 6682227f81
commit 801614605f
2 changed files with 19 additions and 4 deletions

View file

@ -10,14 +10,22 @@ use std::time::Duration;
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
/// Largest response body we'll buffer. A hostile or compromised endpoint could
/// otherwise stream unbounded data and OOM the worker thread (and, via mimalloc,
/// the whole process).
const MAX_RESPONSE: u64 = 4 * 1024 * 1024;
/// POST `body` to `url` with `content_type` and extra `headers`. Blocking. /// POST `body` to `url` with `content_type` and extra `headers`. Blocking.
/// Returns `(status_code, response_body)` or an error string. /// Returns `(status_code, response_body)` or an error string. `verify` turns on
/// TLS certificate + hostname verification for https (the safe default); pass
/// `false` only for a trusted private endpoint with a self-signed cert.
pub fn post( pub fn post(
url: &str, url: &str,
content_type: &str, content_type: &str,
body: &str, body: &str,
headers: &[(String, String)], headers: &[(String, String)],
timeout: Duration, timeout: Duration,
verify: bool,
) -> Result<(u16, String), String> { ) -> Result<(u16, String), String> {
let (scheme, rest) = url.split_once("://").ok_or("bad url (no scheme)")?; let (scheme, rest) = url.split_once("://").ok_or("bad url (no scheme)")?;
let (hostport, path) = match rest.split_once('/') { let (hostport, path) = match rest.split_once('/') {
@ -47,18 +55,23 @@ pub fn post(
let raw = if https { let raw = if https {
let mut b = SslConnector::builder(SslMethod::tls()).map_err(|e| e.to_string())?; let mut b = SslConnector::builder(SslMethod::tls()).map_err(|e| e.to_string())?;
b.set_verify(SslVerifyMode::NONE); // APIs are usually behind a trusted reverse proxy // Default: keep the connector's secure verification (cert chain + hostname,
// applied by connect(host, ..)). Only downgrade when the operator opts out
// for a trusted reverse-proxy / self-signed endpoint.
if !verify {
b.set_verify(SslVerifyMode::NONE);
}
let connector = b.build(); let connector = b.build();
let mut tls = connector.connect(host, stream).map_err(|e| e.to_string())?; let mut tls = connector.connect(host, stream).map_err(|e| e.to_string())?;
tls.write_all(req.as_bytes()).map_err(|e| e.to_string())?; tls.write_all(req.as_bytes()).map_err(|e| e.to_string())?;
let mut buf = Vec::new(); let mut buf = Vec::new();
let _ = tls.read_to_end(&mut buf); // close => EOF (Connection: close) let _ = tls.take(MAX_RESPONSE).read_to_end(&mut buf); // bounded; close => EOF
buf buf
} else { } else {
let mut s = stream; let mut s = stream;
s.write_all(req.as_bytes()).map_err(|e| e.to_string())?; s.write_all(req.as_bytes()).map_err(|e| e.to_string())?;
let mut buf = Vec::new(); let mut buf = Vec::new();
let _ = s.read_to_end(&mut buf); let _ = s.take(MAX_RESPONSE).read_to_end(&mut buf);
buf buf
}; };

View file

@ -471,6 +471,7 @@ impl Server {
headers: Vec<(String, String)>, headers: Vec<(String, String)>,
) { ) {
let tx = self.event_tx.clone(); let tx = self.event_tx.clone();
let verify = self.conf_bool("http_tls_verify", true);
std::thread::spawn(move || { std::thread::spawn(move || {
let (status, body) = crate::http::post( let (status, body) = crate::http::post(
&url, &url,
@ -478,6 +479,7 @@ impl Server {
&body, &body,
&headers, &headers,
std::time::Duration::from_secs(10), std::time::Duration::from_secs(10),
verify,
) )
.unwrap_or((0, String::new())); .unwrap_or((0, String::new()));
let _ = tx.send(crate::ircd::Event::HttpResult { let _ = tx.send(crate::ircd::Event::HttpResult {