diff --git a/src/routes/getUserID.ts b/src/routes/getUserID.ts index 480280e..b450e47 100644 --- a/src/routes/getUserID.ts +++ b/src/routes/getUserID.ts @@ -12,15 +12,17 @@ export async function getUserID(req: Request, res: Response) { } // escape [_ % \] to avoid ReDOS - userName = userName.replace('\\', '\\\\') - .replace('_', '\\_') - .replace('%', '\\%') - + userName = userName.replace(/\\/g, '\\\\') + .replace(/_/g, '\\_') + .replace(/%/g, '\\%'); + // add wildcard to variable - userName = `%${userName}%` + userName = `%${userName}%`; + // LIMIT to reduce overhead + // ESCAPE to escape LIKE wildcards try { let rows = await db.prepare('all', `SELECT "userName", "userID" FROM "userNames" - WHERE "userName" LIKE ? LIMIT 10`, [userName]); + WHERE "userName" LIKE ? ESCAPE '\\' LIMIT 10`, [userName]); if (rows.length === 0) { res.sendStatus(404); return; diff --git a/test/cases/getSegmentInfo.ts b/test/cases/getSegmentInfo.ts index de6e1a3..58c61e2 100644 --- a/test/cases/getSegmentInfo.ts +++ b/test/cases/getSegmentInfo.ts @@ -3,20 +3,20 @@ import {db} from '../../src/databases/databases'; import {Done, getbaseURL} from '../utils'; import {getHash} from '../../src/utils/getHash'; -const ENOENTID = "0000000000000000000000000000000000000000000000000000000000000000" -const upvotedID = "a000000000000000000000000000000000000000000000000000000000000000" -const downvotedID = "b000000000000000000000000000000000000000000000000000000000000000" -const lockedupID = "c000000000000000000000000000000000000000000000000000000000000000" -const infvotesID = "d000000000000000000000000000000000000000000000000000000000000000" -const shadowhiddenID = "e000000000000000000000000000000000000000000000000000000000000000" -const lockeddownID = "f000000000000000000000000000000000000000000000000000000000000000" -const hiddenID = "1000000000000000000000000000000000000000000000000000000000000000" -const fillerID1 = "1100000000000000000000000000000000000000000000000000000000000000" -const fillerID2 = "1200000000000000000000000000000000000000000000000000000000000000" -const fillerID3 = "1300000000000000000000000000000000000000000000000000000000000000" -const fillerID4 = "1400000000000000000000000000000000000000000000000000000000000000" -const fillerID5 = "1500000000000000000000000000000000000000000000000000000000000000" -const oldID = "a0000000-0000-0000-0000-000000000000" +const ENOENTID = "0".repeat(64); +const upvotedID = "a"+"0".repeat(63); +const downvotedID = "b"+"0".repeat(63); +const lockedupID = "c"+"0".repeat(63); +const infvotesID = "d"+"0".repeat(63); +const shadowhiddenID = "e"+"0".repeat(63); +const lockeddownID = "f"+"0".repeat(63); +const hiddenID = "1"+"0".repeat(63); +const fillerID1 = "11"+"0".repeat(62); +const fillerID2 = "12"+"0".repeat(62); +const fillerID3 = "13"+"0".repeat(62); +const fillerID4 = "14"+"0".repeat(62); +const fillerID5 = "15"+"0".repeat(62); +const oldID = `${'0'.repeat(8)}-${'0000-'.repeat(3)}${'0'.repeat(12)}`; describe('getSegmentInfo', () => { before(async () => { diff --git a/test/cases/getUserID.ts b/test/cases/getUserID.ts index f51f353..21c27f8 100644 --- a/test/cases/getUserID.ts +++ b/test/cases/getUserID.ts @@ -12,6 +12,11 @@ describe('getUserID', () => { await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_04"), 'repeating']); await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_05"), 'repeating']); await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_06"), getHash("getuserid_user_06")]); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_07"), '0redos0']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_08"), '%redos%']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_09"), '_redos_']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_10"), 'redos\\%']); + await db.prepare("run", insertUserNameQuery, [getHash("getuserid_user_11"), '\\\\\\']); }); it('Should be able to get a 200', (done: Done) => { @@ -128,7 +133,7 @@ describe('getUserID', () => { }); it('Should be able to get with public ID', (done: Done) => { - const userID = getHash("getuserid_user_06") + const userID = getHash("getuserid_user_06"); fetch(getbaseURL() + '/api/userID?username='+userID) .then(async res => { if (res.status !== 200) { @@ -150,7 +155,7 @@ describe('getUserID', () => { }); it('Should be able to get with fuzzy public ID', (done: Done) => { - const userID = getHash("getuserid_user_06") + const userID = getHash("getuserid_user_06"); fetch(getbaseURL() + '/api/userID?username='+userID.substr(10,60)) .then(async res => { if (res.status !== 200) { @@ -195,4 +200,103 @@ describe('getUserID', () => { }) .catch(err => ("couldn't call endpoint")); }); + + it('should avoid ReDOS with _', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=_redos_') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 1) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "_redos_") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_09")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should avoid ReDOS with %', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=%redos%') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 1) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "%redos%") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_08")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should return 404 if escaped backslashes present', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=%redos\\\\_') + .then(res => { + if (res.status !== 404) done('non 404 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should return 404 if backslashes present', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=\\%redos\\_') + .then(res => { + if (res.status !== 404) done('non 404 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should return user if just backslashes', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=\\\\\\') + .then(async res => { + if (res.status !== 200) { + done("non 200"); + } else { + const data = await res.json(); + if (data.length !== 1) { + done('Returned incorrect number of users "' + data.length + '"'); + } else if (data[0].userName !== "\\\\\\") { + done('Returned incorrect username "' + data.userName + '"'); + } else if (data[0].userID !== getHash("getuserid_user_11")) { + done('Returned incorrect userID "' + data.userID + '"'); + } else { + done(); // pass + } + } + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should not allow usernames more than 64 characters', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username='+'0'.repeat(65)) + .then(res => { + if (res.status !== 400) done('non 400 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => ("couldn't call endpoint")); + }); + + it('should not allow usernames less than 3 characters', (done: Done) => { + fetch(getbaseURL() + '/api/userID?username=aa') + .then(res => { + if (res.status !== 400) done('non 400 (' + res.status + ')'); + else done(); // pass + }) + .catch(err => ("couldn't call endpoint")); + }); });