mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-08-18 00:17:17 +02:00
fix(security): preserve hardened reverse-proxy origin handling
This commit is contained in:
+68
-13
@@ -64,8 +64,73 @@ function isPrivilegedApiPath(pathname: string): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizedHost(value: string | null): string {
|
function normalizeHeaderHost(host: string | null): string {
|
||||||
return (value || '').split(',')[0].trim().replace(/^"|"$/g, '').toLowerCase();
|
return (host || '').trim().replace(/^"|"$/g, '').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hostnameFromHeaderHost(host: string): string {
|
||||||
|
const normalized = normalizeHeaderHost(host);
|
||||||
|
if (!normalized) return '';
|
||||||
|
try {
|
||||||
|
return new URL(`http://${normalized}`).hostname.toLowerCase();
|
||||||
|
} catch {
|
||||||
|
return normalized.replace(/:\d+$/, '').toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrivateIpv4(hostname: string): boolean {
|
||||||
|
const parts = hostname.split('.').map((part) => Number(part));
|
||||||
|
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const [first, second] = parts;
|
||||||
|
return first === 10 || (first === 172 && second >= 16 && second <= 31) || (first === 192 && second === 168);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInternalProxyHost(host: string): boolean {
|
||||||
|
const hostname = hostnameFromHeaderHost(host);
|
||||||
|
if (!hostname || hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
!hostname.includes('.') ||
|
||||||
|
isPrivateIpv4(hostname) ||
|
||||||
|
hostname.endsWith('.internal') ||
|
||||||
|
hostname.endsWith('.docker')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function forwardedHostCandidates(request: NextRequest): string[] {
|
||||||
|
const hosts = new Set<string>();
|
||||||
|
const directHost = normalizeHeaderHost(request.headers.get('host'));
|
||||||
|
if (directHost) hosts.add(directHost);
|
||||||
|
|
||||||
|
// Only honor forwarding metadata when the direct Host itself looks like an
|
||||||
|
// internal reverse-proxy/container address. A public/localhost request may
|
||||||
|
// not promote attacker-supplied forwarded host values into trusted origins.
|
||||||
|
if (!isInternalProxyHost(directHost)) {
|
||||||
|
return [...hosts];
|
||||||
|
}
|
||||||
|
|
||||||
|
const forwardedHost = request.headers.get('x-forwarded-host');
|
||||||
|
if (forwardedHost) {
|
||||||
|
for (const value of forwardedHost.split(',')) {
|
||||||
|
const host = normalizeHeaderHost(value);
|
||||||
|
if (host) hosts.add(host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const forwarded = request.headers.get('forwarded');
|
||||||
|
if (forwarded) {
|
||||||
|
const hostPattern = /(?:^|[;,])\s*host=(?:"([^"]+)"|([^;,]+))/gi;
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
while ((match = hostPattern.exec(forwarded)) !== null) {
|
||||||
|
const host = normalizeHeaderHost(match[1] || match[2] || '');
|
||||||
|
if (host) hosts.add(host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...hosts];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSameOriginOrNonBrowser(request: NextRequest): boolean {
|
function isSameOriginOrNonBrowser(request: NextRequest): boolean {
|
||||||
@@ -90,17 +155,7 @@ function isSameOriginOrNonBrowser(request: NextRequest): boolean {
|
|||||||
}
|
}
|
||||||
if (!originHost) return false;
|
if (!originHost) return false;
|
||||||
|
|
||||||
const candidates = new Set<string>();
|
return forwardedHostCandidates(request).includes(originHost);
|
||||||
const directHost = normalizedHost(request.headers.get('host'));
|
|
||||||
if (directHost) candidates.add(directHost);
|
|
||||||
const forwardedHost = request.headers.get('x-forwarded-host');
|
|
||||||
if (forwardedHost) {
|
|
||||||
for (const value of forwardedHost.split(',')) {
|
|
||||||
const host = normalizedHost(value);
|
|
||||||
if (host) candidates.add(host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return candidates.has(originHost);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function proxy(request: NextRequest) {
|
export function proxy(request: NextRequest) {
|
||||||
|
|||||||
Reference in New Issue
Block a user