Skip to content

Commit e8cc676

Browse files
authored
test: Locale path traversal file existence oracle in PagesRouter (#10267)
1 parent 7ce293f commit e8cc676

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

spec/PagesRouter.spec.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,79 @@ describe('Pages Router', () => {
11781178
expect(nonExistingResponse.status).toBe(303);
11791179
expect(nonExistingResponse.headers.location).toContain('email_verification_send_fail');
11801180
});
1181+
1182+
it('does not create file existence oracle via path traversal in locale query parameter', async () => {
1183+
// Create a canary file at a traversable path to test the oracle
1184+
const canaryDir = path.join(__dirname, 'tmp-locale-oracle-test');
1185+
try {
1186+
await fs.mkdir(canaryDir, { recursive: true });
1187+
await fs.writeFile(path.join(canaryDir, 'password_reset.html'), 'canary');
1188+
1189+
config.pages.enableLocalization = true;
1190+
await reconfigureServer(config);
1191+
1192+
// Calculate traversal from pages directory to canary directory
1193+
const pagesPath = path.resolve(__dirname, '../public');
1194+
const relativePath = path.relative(pagesPath, canaryDir);
1195+
1196+
// Request with path traversal locale pointing to existing canary file
1197+
const existsResponse = await request({
1198+
url: `${config.publicServerURL}/apps/${config.appId}/request_password_reset?token=test&username=test&locale=${encodeURIComponent(relativePath)}`,
1199+
followRedirects: false,
1200+
}).catch(e => e);
1201+
1202+
// Request with path traversal locale pointing to non-existing directory
1203+
const notExistsResponse = await request({
1204+
url: `${config.publicServerURL}/apps/${config.appId}/request_password_reset?token=test&username=test&locale=${encodeURIComponent('../../../../../../tmp/nonexistent-dir')}`,
1205+
followRedirects: false,
1206+
}).catch(e => e);
1207+
1208+
// Both responses must have the same status — no differential oracle
1209+
expect(existsResponse.status).toBe(notExistsResponse.status);
1210+
// Canary content must never be served
1211+
expect(existsResponse.text).not.toContain('canary');
1212+
expect(notExistsResponse.text).not.toContain('canary');
1213+
} finally {
1214+
await fs.rm(canaryDir, { recursive: true, force: true });
1215+
}
1216+
});
1217+
1218+
it('does not create file existence oracle via path traversal in locale header', async () => {
1219+
// Create a canary file at a traversable path
1220+
const canaryDir = path.join(__dirname, 'tmp-locale-header-test');
1221+
try {
1222+
await fs.mkdir(canaryDir, { recursive: true });
1223+
await fs.writeFile(path.join(canaryDir, 'password_reset.html'), 'canary');
1224+
1225+
config.pages.enableLocalization = true;
1226+
await reconfigureServer(config);
1227+
1228+
const pagesPath = path.resolve(__dirname, '../public');
1229+
const relativePath = path.relative(pagesPath, canaryDir);
1230+
1231+
// Request with path traversal locale via header pointing to existing file
1232+
const existsResponse = await request({
1233+
url: `${config.publicServerURL}/apps/${config.appId}/request_password_reset?token=test&username=test`,
1234+
headers: { 'x-parse-page-param-locale': relativePath },
1235+
followRedirects: false,
1236+
}).catch(e => e);
1237+
1238+
// Request with path traversal locale via header pointing to non-existing directory
1239+
const notExistsResponse = await request({
1240+
url: `${config.publicServerURL}/apps/${config.appId}/request_password_reset?token=test&username=test`,
1241+
headers: { 'x-parse-page-param-locale': '../../../../../../tmp/nonexistent-dir' },
1242+
followRedirects: false,
1243+
}).catch(e => e);
1244+
1245+
// Both responses must have the same status — no differential oracle
1246+
expect(existsResponse.status).toBe(notExistsResponse.status);
1247+
// Canary content must never be served
1248+
expect(existsResponse.text).not.toContain('canary');
1249+
expect(notExistsResponse.text).not.toContain('canary');
1250+
} finally {
1251+
await fs.rm(canaryDir, { recursive: true, force: true });
1252+
}
1253+
});
11811254
});
11821255

11831256
describe('custom route', () => {

0 commit comments

Comments
 (0)