Description
The application implements SSRF protection in src/lib/ssrf-protection.ts by checking resolved IP addresses against a list of private IP ranges (PRIVATE_RANGES).
To perform this check, the ipToNumber function parses the IP address string and converts it to a number using bitwise shifting:
function ipToNumber(ip: string): number {
const parts = ip.split(".").map(Number);
return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
}
In JavaScript, all bitwise operations are performed on 32-bit signed integers. When parts[0] is 172 (or greater), shifting it left by 24 bits (parts[0] << 24) overflows the signed 32-bit limit and results in a negative integer (e.g. 192.168.1.1 becomes -1062731519).
The range check is performed as follows:
PRIVATE_RANGES.some(({ start, end }) => num >= start && num <= end)
However, the boundary values in PRIVATE_RANGES are defined as hex literals:
const PRIVATE_RANGES = [
{ start: 0x0a000000, end: 0x0affffff }, // 10.0.0.0/8
{ start: 0xac100000, end: 0xac1fffff }, // 172.16.0.0/12
{ start: 0xc0a80000, end: 0xc0a8ffff }, // 192.168.0.0/16
{ start: 0x7f000000, end: 0x7fffffff }, // 127.0.0.0/8
{ start: 0xa9fe0000, end: 0xa9feffff }, // 169.254.0.0/16
];
In JavaScript, these hex literals are represented as positive numbers (e.g., 0xc0a80000 is 3232235520). Comparing the negative signed integer representation of 192.168.x.x or 172.16.x.x (which is negative) to these positive bounds fails the check completely (num >= start is false).
Impact
As a result of this overflow, the SSRF filter evaluates 192.168.x.x and 172.16.x.x IP addresses as safe, permitting custom webhooks or other outbound requests to be dispatched to these internal network segments. Attackers can exploit this to access internal network services and resources.
Affected Files
Steps to Reproduce
Run the following JS code:
const PRIVATE_RANGES = [
{ start: 0x0a000000, end: 0x0affffff },
{ start: 0xac100000, end: 0xac1fffff },
{ start: 0xc0a80000, end: 0xc0a8ffff },
];
function ipToNumber(ip) {
const parts = ip.split('.').map(Number);
return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
}
function isPrivateIP(ip) {
const num = ipToNumber(ip);
return PRIVATE_RANGES.some(({ start, end }) => num >= start && num <= end);
}
console.log(isPrivateIP('192.168.1.1')); // Output: false
Suggested Solution
Use unsigned shifts (>>>) or simple arithmetic to construct the IP number, ensuring it is a positive integer:
function ipToNumber(ip: string): number {
const parts = ip.split(".").map(Number);
return (parts[0] * 16777216) + (parts[1] * 65536) + (parts[2] * 256) + parts[3];
}
Labels: gssoc, quality:exceptional, level:critical
/assign
Description
The application implements SSRF protection in
src/lib/ssrf-protection.tsby checking resolved IP addresses against a list of private IP ranges (PRIVATE_RANGES).To perform this check, the
ipToNumberfunction parses the IP address string and converts it to a number using bitwise shifting:In JavaScript, all bitwise operations are performed on 32-bit signed integers. When
parts[0]is172(or greater), shifting it left by 24 bits (parts[0] << 24) overflows the signed 32-bit limit and results in a negative integer (e.g.192.168.1.1becomes-1062731519).The range check is performed as follows:
However, the boundary values in
PRIVATE_RANGESare defined as hex literals:In JavaScript, these hex literals are represented as positive numbers (e.g.,
0xc0a80000is3232235520). Comparing the negative signed integer representation of192.168.x.xor172.16.x.x(which is negative) to these positive bounds fails the check completely (num >= startis false).Impact
As a result of this overflow, the SSRF filter evaluates
192.168.x.xand172.16.x.xIP addresses as safe, permitting custom webhooks or other outbound requests to be dispatched to these internal network segments. Attackers can exploit this to access internal network services and resources.Affected Files
Steps to Reproduce
Run the following JS code:
Suggested Solution
Use unsigned shifts (
>>>) or simple arithmetic to construct the IP number, ensuring it is a positive integer:Labels: gssoc, quality:exceptional, level:critical
/assign