Repository: LemmyNet/lemmy (language: rust)

Issue:
[Bug]: get_site_metadata API can be used to DDOS other sites
### Requirements

- [X] Is this a bug report? For questions or discussions use https://lemmy.ml/c/lemmy_support
- [X] Did you check to see if this issue already exists?
- [X] Is this only a single bug? Do not put multiple bugs in one issue.
- [X] Do you agree to follow the rules in our [Code of Conduct](https://join-lemmy.org/docs/code_of_conduct.html)?
- [X] Is this a backend issue? Use the [lemmy-ui](https://github.com/LemmyNet/lemmy-ui) repo for UI / frontend issues.

### Summary

A simple curl like this can be used to initiate a DDOS:
```bash
for i in {1..100}; do
  curl "https://${instance}/api/v3/post/site_metadata?url=${url} -H 'Authorization: Bearer ${token}" &
done
```

### Version

BE: 0.19.8



Discussion / hints:
either you are testing against a different lemmy version than you claim or you are omitting something in your minimal example.
this api is no longer available without an authenticated user since 0.19.6.

once authenticated, it'd be a reflected DoS, not DDoS when used through a single instance.
this is covered under the `message` rate limit, although the limit is up to instance configuration, and due to this being shared with various other resources it may be quite high.
this also shouldn't amplify the amount of requests, as there should only be a single outbound request per user request, which means that this will at best act as a relay to obscure the original source IP.
@Nothing4You Yes it need to be authenticated.

The point is that someone can use curl to make the backend send thousands of requests to a random website. If that site has some kind of rate limiting, it'll block backend's IP, which affects everyone on Lemmy, not just the attacker
We could add a more strict rate limit to it, that seems simple enough.

The default `message` rate limit is 180 / 60 seconds post and comment are 6 / 600 seconds, search is 60 / 600 seconds.

Seems like the search rate limit would work fine. What do you think @Nothing4You ?

Fix patch:
diff --git a/src/api_routes_v3.rs b/src/api_routes_v3.rs
index 8fab1b1485..4c574ecf4c 100644
--- a/src/api_routes_v3.rs
+++ b/src/api_routes_v3.rs
@@ -137,9 +137,13 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
         .wrap(rate_limit.image())
         .route(post().to(upload_image)),
     )
-    .service(resource("/pictrs/image/{filename}").route(get().to(get_image)))
-    .service(resource("/pictrs/image/delete/{token}/{filename}").route(get().to(delete_image)))
-    .service(resource("/pictrs/healthz").route(get().to(pictrs_health)))
+    .service(
+      scope("/pictrs")
+        .wrap(rate_limit.message())
+        .route("/image/{filename}", get().to(get_image))
+        .route("/image/delete/{token}/{filename}", get().to(delete_image))
+        .route("/healthz", get().to(pictrs_health)),
+    )
     .service(
       scope("/api/v3")
         .route("/image_proxy", get().to(image_proxy))
@@ -165,7 +169,7 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
         )
         .service(
           resource("/resolve_object")
-            .wrap(rate_limit.message())
+            .wrap(rate_limit.search())
             .route(get().to(resolve_object)),
         )
         // Community
@@ -198,12 +202,17 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
         )
         // Post
         .service(
-          // Handle POST to /post separately to add the post() rate limitter
           resource("/post")
+            // Handle POST to /post separately to add the post() rate limitter
             .guard(guard::Post())
             .wrap(rate_limit.post())
             .route(post().to(create_post)),
         )
+        .service(
+          resource("/post/site_metadata")
+            .wrap(rate_limit.search())
+            .route(get().to(get_link_metadata)),
+        )
         .service(
           scope("/post")
             .wrap(rate_limit.message())
@@ -220,8 +229,7 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
             .route("/like/list", get().to(list_post_likes))
             .route("/save", put().to(save_post))
             .route("/report", post().to(create_post_report))
-            .route("/report/resolve", put().to(resolve_post_report))
-            .route("/site_metadata", get().to(get_link_metadata)),
+            .route("/report/resolve", put().to(resolve_post_report)),
         )
         // Comment
         .service(
diff --git a/src/api_routes_v4.rs b/src/api_routes_v4.rs
index 48710b0df4..0ae2d4d421 100644
--- a/src/api_routes_v4.rs
+++ b/src/api_routes_v4.rs
@@ -196,7 +196,11 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
           .wrap(rate_limit.search())
           .route(get().to(search)),
       )
-      .route("/resolve_object", get().to(resolve_object))
+      .service(
+        resource("/resolve_object")
+          .wrap(rate_limit.search())
+          .route(get().to(resolve_object)),
+      )
       // Community
       .service(
         resource("/community")
@@ -232,12 +236,17 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
       .route("/federated_instances", get().to(get_federated_instances))
       // Post
       .service(
-        // Handle POST to /post separately to add the post() rate limitter
         resource("/post")
+          // Handle POST to /post separately to add the post() rate limitter
           .guard(guard::Post())
           .wrap(rate_limit.post())
           .route(post().to(create_post)),
       )
+      .service(
+        resource("/post/site_metadata")
+          .wrap(rate_limit.search())
+          .route(get().to(get_link_metadata)),
+      )
       .service(
         scope("/post")
           .route("", get().to(get_post))
@@ -254,8 +263,7 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
           .route("/like/list", get().to(list_post_likes))
           .route("/save", put().to(save_post))
           .route("/report", post().to(create_post_report))
-          .route("/report/resolve", put().to(resolve_post_report))
-          .route("/site_metadata", get().to(get_link_metadata)),
+          .route("/report/resolve", put().to(resolve_post_report)),
       )
       // Comment
       .service(


Test patch: