source: Klonkt/test/deref-cache-poisoning.test.js@ b5a0268c

main
Last change on this file since b5a0268c was b5a0268c, checked in by Robin <roboburr@…>, 4 days ago

De negatieve dereference-cache is geen censuurknop meer (shaer-qawr)

De cache onthield elke mislukte dereference op de note-URI alleen, en een
van de twee manieren om te mislukken is volledig door een vreemde te
sturen. Neem de echte note-URI van je slachtoffer, zet je eigen actor op
DEZELFDE host als beweerde afzender en wijs met inReplyTo naar een van
onze publieke notes: de fetch bij de bron slaagt, attributedTo is het
slachtoffer en niet jij, en die note-URI stond dertig minuten op de
zwarte lijst. Het echte doorgestuurde antwoord liep daar daarna op stuk.
Elke dertig minuten herhalen gaf onbeperkte, gerichte onderdrukking van
een specifiek antwoord, voor een verzoek per keer.

De sleutel is nu de hele bescherming, want de twee soorten mislukking
zeggen iets verschillends. Een TRANSPORTFOUT is een eigenschap van de
note zelf en geldt voor iedereen die hem doorstuurt: sleutel is de
note. Een attributedTo-MISMATCH zegt iets over DEZE doorstuurder:
sleutel is (note, beweerde actor). De leugenaar remt daarmee alleen
zichzelf af, en dat was de rem die de cache moest zijn.

Tweede punt uit dezelfde bead: query en fragment tellen niet meer mee in
de sleutel. Met een kale URL waren ?x=1 en ?x=2 losse ingangen, dus
varieren kostte niets en er was helemaal geen rem -- en met vijfhonderd
losse sleutels waren de echte ingangen eruit te duwen.

Bewust NIET gedaan: een rem per HOST. Wie een handvol niet-bestaande
URL's op een host laat mislukken zou daarmee die hele host het zwijgen
opleggen, en dat is een grotere versie van precies de fout die hier
gerepareerd wordt. Dat staat als waarschuwing bij de code.

Drie toetsen, elk met eigen tegenbewijs: met de sleutel terug op de note
alleen valt de censuurtoets en blijven de andere twee groen, en met de
URL-normalisatie eruit valt alleen de remtoets. Volle suite 1237.

  • Property mode set to 100644
File size: 3.1 KB
Line 
1// De negatieve dereference-cache mocht geen censuurknop zijn (shaer-qawr).
2//
3// DE AANVAL. Neem de echte note-URI van je slachtoffer, zet je eigen actor op
4// dezelfde host als beweerde afzender, en wijs met inReplyTo naar een van onze
5// publieke notes. De fetch bij de bron SLAAGT, maar attributedTo is het
6// slachtoffer en niet jij: een mismatch. Werd die op de note-URI alleen
7// onthouden, dan stond die dertig minuten op de zwarte lijst en liep het ECHTE
8// doorgestuurde antwoord daarop stuk. Elke dertig minuten herhalen gaf
9// onbeperkte, gerichte onderdrukking van een specifiek antwoord.
10//
11// Op de cache getoetst en niet via handleInbox: de weg erheen eist https en een
12// echte fetch bij een vreemde host, en dat is in een toets geen bewijs maar een
13// netwerkafspraak. Deze twee functies ZIJN de kwetsbaarheid.
14import { test } from 'node:test';
15import assert from 'node:assert/strict';
16
17process.env.DATABASE_PATH = ':memory:';
18process.env.PUBLIC_BASE_URL = 'https://test.example';
19
20const { _derefCacheForTests } = await import('../src/services/ap-inbox.js');
21const { derefRecentlyFailed, noteDerefFailure } = _derefCacheForTests;
22
23const NOTE = 'https://mastodon.example/users/slachtoffer/statuses/1';
24const AANVALLER = 'https://mastodon.example/users/aanvaller';
25const DOORSTUURDER = 'https://mastodon.example/users/draadstarter';
26
27test('een uitgelokte mismatch blokkeert het echte antwoord niet', () => {
28 // De aanvaller lokt de mismatch uit op de note van het slachtoffer.
29 noteDerefFailure(NOTE, AANVALLER);
30
31 // Zijn eigen herhaling wordt wel geremd -- dat was het doel van de cache.
32 assert.equal(derefRecentlyFailed(NOTE, AANVALLER), true,
33 'dezelfde leugen nog eens kost geen tweede fetch');
34
35 // Maar het echte doorgestuurde antwoord, van de draadstarter, komt er langs.
36 assert.equal(derefRecentlyFailed(NOTE, DOORSTUURDER), false,
37 'een vreemde mag deze note niet voor een ander op slot zetten');
38});
39
40test('een transportfout geldt wel voor iedereen', () => {
41 // De note is niet op te halen. Dat is een eigenschap van de note zelf, dus
42 // die remt elke doorstuurder -- daar is de cache voor.
43 const stuk = 'https://mastodon.example/users/x/statuses/onbereikbaar';
44 noteDerefFailure(stuk);
45 assert.equal(derefRecentlyFailed(stuk, DOORSTUURDER), true);
46 assert.equal(derefRecentlyFailed(stuk, AANVALLER), true);
47});
48
49test('de rem remt: een andere query is dezelfde ingang', () => {
50 // Met een kale URL als sleutel waren ?x=1 en ?x=2 losse ingangen, en dan kost
51 // varieren niets en is er geen rem.
52 const basis = 'https://mastodon.example/users/y/statuses/9';
53 noteDerefFailure(`${basis}?x=1`);
54 assert.equal(derefRecentlyFailed(`${basis}?x=2`, DOORSTUURDER), true, 'query telt niet mee');
55 assert.equal(derefRecentlyFailed(`${basis}#frag`, DOORSTUURDER), true, 'fragment ook niet');
56 // De HOST wel, want dat is een ander adres en een andere partij.
57 assert.equal(derefRecentlyFailed('https://elders.example/users/y/statuses/9', DOORSTUURDER), false);
58 // Het PAD ook, anders zou een mislukking de buren meenemen.
59 assert.equal(derefRecentlyFailed('https://mastodon.example/users/y/statuses/10', DOORSTUURDER), false);
60});
Note: See TracBrowser for help on using the repository browser.