Apply rate limits to proxyServer and fileServer

This resolves a DoS / DDoS / request amplification attack vector that is being actively exploited.

Signed-off-by: limepotato <limepot@protonmail.ch>
This commit is contained in:
Laura Hausmann 2024-11-20 04:21:51 +01:00 committed by limepotato
parent ebe2ff0e21
commit d21fb75592
2 changed files with 60 additions and 2 deletions

View file

@ -14,7 +14,10 @@ import { detectType } from "@/misc/get-file-info.js";
import { convertToWebp } from "@/services/drive/image-processor.js"; import { convertToWebp } from "@/services/drive/image-processor.js";
import { GenerateVideoThumbnail } from "@/services/drive/generate-video-thumbnail.js"; import { GenerateVideoThumbnail } from "@/services/drive/generate-video-thumbnail.js";
import { StatusError } from "@/misc/fetch.js"; import { StatusError } from "@/misc/fetch.js";
import { FILE_TYPE_BROWSERSAFE } from "@/const.js"; import { FILE_TYPE_BROWSERSAFE, MINUTE } from "@/const.js";
import { IEndpointMeta } from "@/server/api/endpoints.js";
import { getIpHash } from "@/misc/get-ip-hash.js";
import { limiter } from "@/server/api/limiter.js";
const _filename = fileURLToPath(import.meta.url); const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename); const _dirname = dirname(_filename);
@ -31,6 +34,31 @@ const commonReadableHandlerGenerator =
export default async function (ctx: Koa.Context) { export default async function (ctx: Koa.Context) {
const key = ctx.params.key; const key = ctx.params.key;
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
let limitActor: string;
limitActor = getIpHash(ctx.ip);
const limit: IEndpointMeta["limit"] = {
key: `drive-file:${key}`,
duration: MINUTE * 10,
max: 10
}
// Rate limit
await limiter(
limit as IEndpointMeta["limit"] & { key: NonNullable<string> },
limitActor,
).catch((e) => {
const remainingTime = e.remainingTime
? `Please try again in ${e.remainingTime}.`
: "Please try again later.";
ctx.status = 429;
ctx.body = "Rate limit exceeded. " + remainingTime;
});
if (ctx.status == 429) return;
// Fetch drive file // Fetch drive file
const file = await DriveFiles.createQueryBuilder("file") const file = await DriveFiles.createQueryBuilder("file")
.where("file.accessKey = :accessKey", { accessKey: key }) .where("file.accessKey = :accessKey", { accessKey: key })

View file

@ -9,9 +9,12 @@ import { createTemp } from "@/misc/create-temp.js";
import { downloadUrl } from "@/misc/download-url.js"; import { downloadUrl } from "@/misc/download-url.js";
import { detectType } from "@/misc/get-file-info.js"; import { detectType } from "@/misc/get-file-info.js";
import { StatusError } from "@/misc/fetch.js"; import { StatusError } from "@/misc/fetch.js";
import { FILE_TYPE_BROWSERSAFE } from "@/const.js"; import { FILE_TYPE_BROWSERSAFE, MINUTE } from "@/const.js";
import { serverLogger } from "../index.js"; import { serverLogger } from "../index.js";
import { isMimeImage } from "@/misc/is-mime-image.js"; import { isMimeImage } from "@/misc/is-mime-image.js";
import { getIpHash } from "@/misc/get-ip-hash.js";
import { limiter } from "@/server/api/limiter.js";
import { IEndpointMeta } from "@/server/api/endpoints.js";
export async function proxyMedia(ctx: Koa.Context) { export async function proxyMedia(ctx: Koa.Context) {
const url = "url" in ctx.query ? ctx.query.url : `https://${ctx.params.url}`; const url = "url" in ctx.query ? ctx.query.url : `https://${ctx.params.url}`;
@ -21,6 +24,33 @@ export async function proxyMedia(ctx: Koa.Context) {
return; return;
} }
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
let limitActor: string;
limitActor = getIpHash(ctx.ip);
const parsedUrl = new URL(url);
const limit: IEndpointMeta["limit"] = {
key: `media-proxy:${parsedUrl.host}:${parsedUrl.pathname}`,
duration: MINUTE * 10,
max: 10
}
// Rate limit
await limiter(
limit as IEndpointMeta["limit"] & { key: NonNullable<string> },
limitActor,
).catch((e) => {
const remainingTime = e.remainingTime
? `Please try again in ${e.remainingTime}.`
: "Please try again later.";
ctx.status = 429;
ctx.body = "Rate limit exceeded. " + remainingTime;
});
if (ctx.status == 429) return;
const { hostname } = new URL(url); const { hostname } = new URL(url);
let resolvedIps; let resolvedIps;
try { try {