Skip to content

Commit

Permalink
Merge pull request from GHSA-3f39-6537-3cgc
Browse files Browse the repository at this point in the history
This commit implements HTTP header and body validation to fix
[SIF-2023-002](https://advisory.silicon.moe/advisory/sif-2023-002/)

Signed-off-by: perillamint <perillamint@silicon.moe>
Co-authored-by: perillamint <perillamint@silicon.moe>
Co-authored-by: yunochi <yuno@yunochi.com>
  • Loading branch information
3 people committed Nov 14, 2023
1 parent 30bb24d commit 65c5626
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 6 deletions.
3 changes: 2 additions & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
"dependencies": {
"@aws-sdk/client-s3": "3.412.0",
"@aws-sdk/lib-storage": "3.412.0",
"@smithy/node-http-handler": "2.1.5",
"@bull-board/api": "5.9.1",
"@bull-board/fastify": "5.9.1",
"@bull-board/ui": "5.9.1",
Expand All @@ -78,6 +77,7 @@
"@peertube/http-signature": "1.7.0",
"@simplewebauthn/server": "8.3.5",
"@sinonjs/fake-timers": "11.2.2",
"@smithy/node-http-handler": "2.1.5",
"@swc/cli": "0.1.62",
"@swc/core": "1.3.96",
"accepts": "1.3.8",
Expand All @@ -99,6 +99,7 @@
"date-fns": "2.30.0",
"deep-email-validator": "0.1.21",
"fastify": "4.24.3",
"fastify-raw-body": "^4.2.2",
"feed": "4.2.2",
"file-type": "18.7.0",
"fluent-ffmpeg": "2.1.2",
Expand Down
58 changes: 55 additions & 3 deletions packages/backend/src/server/ActivityPubServerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

import * as crypto from 'node:crypto';
import { IncomingMessage } from 'node:http';
import { Inject, Injectable } from '@nestjs/common';
import fastifyAccepts from '@fastify/accepts';
Expand Down Expand Up @@ -108,7 +109,58 @@ export class ActivityPubServerService {
return;
}

// TODO: request.bodyのバリデーション?
if (signature.params.headers.indexOf('host') === -1
|| request.headers.host !== this.config.host) {
// Host not specified or not match.
reply.code(401);
return;
}

if (signature.params.headers.indexOf('digest') === -1) {
// Digest not found.
reply.code(401);
} else {
const digest = request.headers.digest;

if (typeof digest !== 'string') {
// Huh?
reply.code(401);
return;
}

const re = /^([a-zA-Z0-9\-]+)=(.+)$/;
const match = digest.match(re);

if (match == null) {
// Invalid digest
reply.code(401);
return;
}

const algo = match[1];
const digestValue = match[2];

if (algo !== 'SHA-256') {
// Unsupported digest algorithm
reply.code(401);
return;
}

if (request.rawBody == null) {
// Bad request
reply.code(400);
return;
}

const hash = crypto.createHash('sha256').update(request.rawBody).digest('base64');

if (hash !== digestValue) {
// Invalid digest
reply.code(401);
return;
}
}

this.queueService.inbox(request.body as IActivity, signature);

reply.code(202);
Expand Down Expand Up @@ -474,8 +526,8 @@ export class ActivityPubServerService {

//#region Routing
// inbox (limit: 64kb)
fastify.post('/inbox', { bodyLimit: 1024 * 64 }, async (request, reply) => await this.inbox(request, reply));
fastify.post('/users/:user/inbox', { bodyLimit: 1024 * 64 }, async (request, reply) => await this.inbox(request, reply));
fastify.post('/inbox', { config: { rawBody: true }, bodyLimit: 1024 * 64 }, async (request, reply) => await this.inbox(request, reply));
fastify.post('/users/:user/inbox', { config: { rawBody: true }, bodyLimit: 1024 * 64 }, async (request, reply) => await this.inbox(request, reply));

// note
fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/server/ServerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import Fastify, { FastifyInstance } from 'fastify';
import fastifyStatic from '@fastify/static';
import fastifyRawBody from 'fastify-raw-body';
import { IsNull } from 'typeorm';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { Config } from '@/config.js';
Expand Down Expand Up @@ -86,6 +87,13 @@ export class ServerService implements OnApplicationShutdown {
});
}

// Register raw-body parser for ActivityPub HTTP signature validation.
fastify.register(fastifyRawBody, {
global: false,
encoding: 'utf-8',
runFirst: true,
});

// Register non-serving static server so that the child services can use reply.sendFile.
// `root` here is just a placeholder and each call must use its own `rootPath`.
fastify.register(fastifyStatic, {
Expand Down
16 changes: 14 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 65c5626

Please sign in to comment.