-
Notifications
You must be signed in to change notification settings - Fork 703
Vulnerability bigquery rest traversal #9188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b9dc2a4
6f73654
3686a0d
100757a
4ae9487
ab44abd
1ff5899
06f2424
a05fd58
83f4a6b
a92100b
b51c0b3
a451317
af8a85d
d083db0
812fc63
ce24d58
f93b18e
647558d
a501715
766df9b
873359f
94f4a1d
8ac4dfc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1024,5 +1024,131 @@ class ProgressStream extends Transform { | |
| } | ||
| } | ||
|
|
||
| // Validates a single path segment matched by a single wildcard (*). | ||
| // Checks that the segment is not exactly '.' or '..' (directory traversal indicators). | ||
| export function validateUriPathSegment(propertyName: string, value: string): void { | ||
| if (value === '.' || value === '..') { | ||
| throw new Error(`Invalid value ${value} for ${propertyName}`); | ||
| } | ||
| } | ||
|
|
||
| // Validates a multi-segment path matched by a double wildcard (**). | ||
| // Splitting by slash, it checks that no individual segment is exactly '.' or '..'. | ||
| // This segment-by-segment check prevents directory traversal while allowing | ||
| // legitimate resource names containing dots (e.g., domain-scoped project IDs). | ||
| export function validateUriPath(propertyName: string, value: string): void { | ||
| if (value) { | ||
| // Split by slash and check for exact segment matches of '.' or '..' rather | ||
| // than using a simple string.includes('.') check. This avoids rejecting | ||
| // valid domain-scoped resource segments (e.g. projects/example.com:project-id). | ||
| const segments = value.split('/'); | ||
| if (segments.some(segment => segment === '.' || segment === '..')) { | ||
| throw new Error( | ||
| `Value for ${propertyName} must not contain segments that are exactly . or ..`, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+1039
to
+1051
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
export function validateUriPath(propertyName: string, value: string): void {
if (value) {
const segments = value.split('/');
const hasTraversal = segments.some(segment => {
try {
const decoded = decodeURIComponent(segment);
return decoded === '.' || decoded === '..';
} catch {
return segment === '.' || segment === '..';
}
});
if (hasTraversal) {
throw new Error(
`Value for ${propertyName} must not contain segments that are exactly . or ..`,
);
}
}
} |
||
|
|
||
| /** | ||
| * Percent-encodes a string according to RFC 3986, preserving only unreserved | ||
| * characters (alpha-numeric, '-', '_', '.', and '~'). All other characters, | ||
| * including slashes ('/'), are percent-encoded. | ||
| * | ||
| * This is necessary because encodeURIComponent natively encodes URL-unsafe | ||
| * characters like ?, #, $, &, +, etc., but preserves !, ', (, ), and *. | ||
| * To ensure strict compliance, we manually encode those preserved characters. | ||
| * | ||
| * @param {string} str - The input string to encode. | ||
| * @returns {string} The percent-encoded string. | ||
| */ | ||
| export function encodeWithSlashes(str: string): string { | ||
| return encodeURIComponent(str).replace( | ||
| /[!'()*]/g, // Characters preserved by encodeURIComponent | ||
| character => '%' + character.charCodeAt(0).toString(16).toUpperCase(), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Percent-encodes a string according to RFC 3986, preserving unreserved | ||
| * characters (alpha-numeric, '-', '_', '.', and '~') and slashes ('/'). All other | ||
| * characters are percent-encoded. | ||
| * | ||
| * @param {string} str - The input string to encode. | ||
| * @returns {string} The percent-encoded string with slashes preserved. | ||
| */ | ||
| export function encodeWithoutSlashes(str: string): string { | ||
| return str.split('/').map(encodeWithSlashes).join('/'); | ||
| } | ||
|
|
||
| /** | ||
| * Encodes each path segment in a URI string while preserving slash (`/`) and | ||
| * colon (`:`) delimiters, and validates that no path segment is `.` or `..` to | ||
| * prevent path traversal. | ||
| * | ||
| * @param {string} uri - The URI path to encode. | ||
| * @return {string} The encoded URI path. | ||
| */ | ||
| export function encodeURIPath(uri: string): string { | ||
| const processSegment = (segment: string): string => { | ||
| if (segment === '') { | ||
| return ''; | ||
| } | ||
| let decoded = segment; | ||
| try { | ||
| decoded = decodeURIComponent(segment); | ||
| } catch { | ||
| // Fallback to raw segment if decoding fails (e.g. malformed '%') | ||
| } | ||
| validateUriPathSegment('path segment', decoded); | ||
| return encodeWithSlashes(decoded); | ||
| }; | ||
|
|
||
| const parts = uri.split('/'); | ||
| return parts | ||
| .map(part => { | ||
| if (part.includes(':')) { | ||
| return part.split(':').map(processSegment).join(':'); | ||
| } | ||
| return processSegment(part); | ||
| }) | ||
| .join('/'); | ||
| } | ||
|
Comment on lines
+1092
to
+1116
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
To fix both issues, each segment/subpart should be decoded using export function encodeURIPath(uri: string): string {
const parts = uri.split('/');
return parts
.map(part => {
if (part === '') {
return '';
}
if (part.includes(':')) {
const subparts = part.split(':');
return subparts
.map(subpart => {
if (subpart === '') {
return '';
}
let decoded = subpart;
try {
decoded = decodeURIComponent(subpart);
} catch {
// Fallback to raw subpart if decoding fails
}
validateUriPathSegment('path segment', decoded);
return encodeWithSlashes(decoded);
})
.join(':');
}
let decoded = part;
try {
decoded = decodeURIComponent(part);
} catch {
// Fallback to raw part if decoding fails
}
validateUriPathSegment('path segment', decoded);
return encodeWithSlashes(decoded);
})
.join('/');
} |
||
|
|
||
| /** | ||
| * Encodes the pathname of an absolute URI string using `encodeURIPath`, | ||
| * preserving any query parameters, hash, or trailing slash formatting. | ||
| * | ||
| * @param {string} uri - The absolute URI string to encode. | ||
| * @return {string} The formatted and encoded absolute URI string. | ||
| */ | ||
| export function encodeAbsoluteURI(uri: string): string { | ||
| const url = new URL(uri); // Isolate pathname from protocol, host, and query. | ||
| const encodedPath = encodeURIPath(url.pathname); | ||
| url.pathname = encodedPath; | ||
| let res = url.toString(); | ||
| if (!uri.endsWith('/') && res.endsWith('/')) { | ||
| res = res.slice(0, -1); | ||
| } | ||
| return res; | ||
| } | ||
|
|
||
| /** | ||
| * Trims slashes, encodes path segments to prevent path traversal, and joins | ||
| * URI components into a single relative path. | ||
| * | ||
| * @param {string[]} components - URI components to encode and join. | ||
| * @return {string} The formatted and joined URI path. | ||
| */ | ||
| export function joinURIComponents(components: string[]): string { | ||
| return components | ||
| .map(uriComponent => { | ||
| const trimSlashesRegex = /^\/*|\/*$/g; | ||
| const trimmed = uriComponent.replace(trimSlashesRegex, ''); | ||
| return encodeURIPath(trimmed); // Encode and prevent path traversal. | ||
| }) | ||
| .join('/'); | ||
| } | ||
|
|
||
| const util = new Util(); | ||
| export {util}; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validateUriPathSegmentdoes not decode the input segment before checking if it is.or... If a user passes a percent-encoded path traversal sequence (e.g.,%2e%2e), this check will be bypassed. To prevent path traversal bypasses, the segment should be decoded usingdecodeURIComponentbefore validation.