Skip to content

Commit f196d84

Browse files
authored
Merge commit from fork
Fixed several security vulnerabilities of the `lookupWebFinger()` function
2 parents f8661ef + 8be3c20 commit f196d84

File tree

3 files changed

+93
-2
lines changed

3 files changed

+93
-2
lines changed

CHANGES.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ Version 1.0.14
88

99
To be released.
1010

11+
- Fixed several security vulnerabilities of the `lookupWebFinger()` function.
12+
13+
- Fixed a security vulnerability where the `lookupWebFinger()` function
14+
had followed the infinite number of redirects, which could lead to
15+
a denial of service attack. Now it follows up to 5 redirects.
16+
17+
- Fixed a security vulnerability where the `lookupWebFinger()` function
18+
had followed the redirects to other than the HTTP/HTTPS schemes, which
19+
could lead to a security breach. Now it follows only the same scheme
20+
as the original request.
21+
22+
- Fixed a security vulnerability where the `lookupWebFinger()` function
23+
had followed the redirects to the private network addresses, which
24+
could lead to a SSRF attack. Now it follows only the public network
25+
addresses.
26+
1127

1228
Version 1.0.13
1329
--------------

src/webfinger/lookup.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { assertEquals } from "@std/assert";
1+
import { assertEquals, assertRejects } from "@std/assert";
2+
import { deadline } from "@std/async/deadline";
23
import * as mf from "mock_fetch";
4+
import { UrlError } from "../runtime/url.ts";
35
import { test } from "../testing/mod.ts";
46
import type { ResourceDescriptor } from "./jrd.ts";
57
import { lookupWebFinger } from "./lookup.ts";
@@ -91,6 +93,52 @@ test("lookupWebFinger()", async (t) => {
9193
assertEquals(await lookupWebFinger("acct:[email protected]"), expected);
9294
});
9395

96+
mf.mock(
97+
"GET@/.well-known/webfinger",
98+
(_) =>
99+
new Response("", {
100+
status: 302,
101+
headers: { Location: "/.well-known/webfinger" },
102+
}),
103+
);
104+
105+
await t.step("infinite redirection", async () => {
106+
const result = await deadline(
107+
lookupWebFinger("acct:[email protected]"),
108+
2000,
109+
);
110+
assertEquals(result, null);
111+
});
112+
113+
mf.mock(
114+
"GET@/.well-known/webfinger",
115+
(_) =>
116+
new Response("", {
117+
status: 302,
118+
headers: { Location: "ftp://example.com/" },
119+
}),
120+
);
121+
122+
await t.step("redirection to different protocol", async () => {
123+
assertEquals(await lookupWebFinger("acct:[email protected]"), null);
124+
});
125+
126+
mf.mock(
127+
"GET@/.well-known/webfinger",
128+
(_) =>
129+
new Response("", {
130+
status: 302,
131+
headers: { Location: "https://localhost/" },
132+
}),
133+
);
134+
135+
await t.step("redirection to private address", async () => {
136+
await assertRejects(
137+
() => lookupWebFinger("acct:[email protected]"),
138+
UrlError,
139+
);
140+
});
141+
94142
mf.uninstall();
95143
});
96144

src/webfinger/lookup.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { getLogger } from "@logtape/logtape";
2+
import { validatePublicUrl } from "../runtime/url.ts";
23
import type { ResourceDescriptor } from "./jrd.ts";
34

45
const logger = getLogger(["fedify", "webfinger", "lookup"]);
56

7+
const MAX_REDIRECTION = 5; // TODO: Make this configurable.
8+
69
/**
710
* Looks up a WebFinger resource.
811
* @param resource The resource URL to look up.
@@ -26,12 +29,14 @@ export async function lookupWebFinger(
2629
}
2730
let url = new URL(`${protocol}//${server}/.well-known/webfinger`);
2831
url.searchParams.set("resource", resource.href);
32+
let redirected = 0;
2933
while (true) {
3034
logger.debug(
3135
"Fetching WebFinger resource descriptor from {url}...",
3236
{ url: url.href },
3337
);
3438
let response: Response;
39+
await validatePublicUrl(url.href);
3540
try {
3641
response = await fetch(url, {
3742
headers: { Accept: "application/jrd+json" },
@@ -48,10 +53,32 @@ export async function lookupWebFinger(
4853
response.status >= 300 && response.status < 400 &&
4954
response.headers.has("Location")
5055
) {
51-
url = new URL(
56+
redirected++;
57+
if (redirected >= MAX_REDIRECTION) {
58+
logger.error(
59+
"Too many redirections ({redirections}) while fetching WebFinger " +
60+
"resource descriptor.",
61+
{ redirections: redirected },
62+
);
63+
return null;
64+
}
65+
const redirectedUrl = new URL(
5266
response.headers.get("Location")!,
5367
response.url == null || response.url === "" ? url : response.url,
5468
);
69+
if (redirectedUrl.protocol !== url.protocol) {
70+
logger.error(
71+
"Redirected to a different protocol ({protocol} to " +
72+
"{redirectedProtocol}) while fetching WebFinger resource " +
73+
"descriptor.",
74+
{
75+
protocol: url.protocol,
76+
redirectedProtocol: redirectedUrl.protocol,
77+
},
78+
);
79+
return null;
80+
}
81+
url = redirectedUrl;
5582
continue;
5683
}
5784
if (!response.ok) {

0 commit comments

Comments
 (0)