CVE-2026-42180
Description
Lemmy is a link aggregator and forum for the fediverse. Prior to version 0.19.18, Lemmy allows an authenticated low-privileged user to create a link post through POST /api/v3/post. When a post is created in a public community, the backend asynchronously sends a Webmention to the attacker-controlled link target. The submitted URL is checked for syntax and scheme, but the audited code path does not reject loopback, private, or link-local destinations before the Webmention request is issued. This lets a normal user trigger server-side HTTP requests toward internal services. This issue has been patched in version 0.19.18.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
lemmy_api_commoncrates.io | < 0.19.18 | 0.19.18 |
Patches
11f06693b7080[0.19] Add IP check for webmention (#6444)
2 files changed · +35 −21
crates/api_common/src/request.rs+33 −19 modified@@ -37,7 +37,7 @@ use serde::{Deserialize, Serialize}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use tokio::net::lookup_host; use tracing::{info, warn}; -use url::Url; +use url::{Host, Url}; use urlencoding::encode; use webpage::HTML; @@ -64,24 +64,7 @@ pub async fn fetch_link_metadata( return Err(LemmyErrorType::InvalidUrl.into()); } - // Resolve the domain and throw an error if it points to any internal IP, - // using logic from nightly IpAddr::is_global. - if !cfg!(debug_assertions) { - // TODO: Replace with IpAddr::is_global() once stabilized - // https://doc.rust-lang.org/std/net/enum.IpAddr.html#method.is_global - let domain = url.domain().ok_or(LemmyErrorType::UrlWithoutDomain)?; - let invalid_ip = - lookup_host((domain.to_owned(), 80)) - .await? - .any(|addr| match addr.ip().to_canonical() { - IpAddr::V4(addr) => v4_is_invalid(addr), - IpAddr::V6(addr) => v6_is_invalid(addr), - }); - if invalid_ip { - return Err(LemmyErrorType::InvalidUrl.into()); - } - } - + validate_link_ip(url).await?; info!("Fetching site metadata for url: {}", url); // We only fetch the first MB of data in order to not waste bandwidth especially for large // binary files. This high limit is particularly needed for youtube, which includes a lot of @@ -161,6 +144,37 @@ pub async fn fetch_link_metadata( }) } +/// Resolve the domain and throw an error if it points to any internal IP. +pub async fn validate_link_ip(url: &Url) -> LemmyResult<()> { + if cfg!(debug_assertions) { + return Ok(()); + } + // Resolve the domain and throw an error if it points to any internal IP, + // using logic from nightly IpAddr::is_global. + + // TODO: Replace with IpAddr::is_global() once stabilized + // https://doc.rust-lang.org/std/net/enum.IpAddr.html#method.is_global + let mut ip = vec![]; + match url.host().ok_or(LemmyErrorType::UrlWithoutDomain)? { + Host::Domain(domain) => ip.extend( + lookup_host((domain.to_owned(), 80)) + .await? + .map(|s| s.ip().to_canonical()), + ), + Host::Ipv4(ipv4) => ip.push(ipv4.into()), + Host::Ipv6(ipv6) => ip.push(ipv6.into()), + }; + + let invalid_ip = ip.into_iter().any(|addr| match addr { + IpAddr::V4(addr) => v4_is_invalid(addr), + IpAddr::V6(addr) => v6_is_invalid(addr), + }); + if invalid_ip { + return Err(LemmyErrorType::InvalidUrl.into()); + } + Ok(()) +} + fn v4_is_invalid(v4: Ipv4Addr) -> bool { v4.is_private() || v4.is_loopback()
crates/api_crud/src/post/create.rs+2 −2 modified@@ -4,7 +4,7 @@ use lemmy_api_common::{ build_response::build_post_response, context::LemmyContext, post::{CreatePost, PostResponse}, - request::generate_post_link_metadata, + request::{generate_post_link_metadata, validate_link_ip}, send_activity::SendActivityData, utils::{ check_community_user_action, @@ -178,7 +178,7 @@ pub async fn create_post( mark_post_as_read(person_id, post_id, &mut context.pool()).await?; if let Some(url) = inserted_post.url.clone() { - if community.visibility == CommunityVisibility::Public { + if community.visibility == CommunityVisibility::Public && validate_link_ip(&url).await.is_ok() { spawn_try_task(async move { let mut webmention = Webmention::new::<Url>(inserted_post.ap_id.clone().into(), url.clone().into())?;
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-3jvj-v6w2-h948ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-42180ghsaADVISORY
- github.com/LemmyNet/lemmy/commit/1f06693b708020c5c3a3752bd2f1c6006a75e9bcghsaWEB
- github.com/LemmyNet/lemmy/releases/tag/0.19.18nvdWEB
- github.com/LemmyNet/lemmy/security/advisories/GHSA-3jvj-v6w2-h948nvdWEB
News mentions
0No linked articles in our index yet.